From feb0d342130c94908a8ecc63f919ee023c9c18af Mon Sep 17 00:00:00 2001 From: Wade Carpenter Date: Thu, 14 Apr 2022 14:27:46 -0700 Subject: [PATCH 001/342] pre-push: fix stdin line splitting when has whitespace From the `pre-push.sample` file: > Information about the commits which are being pushed is supplied as > lines to the standard input in the form: > > When `` is not simply a branch name, but a more general ref (see git-rev-parse(1)), it could contain whitespace, and that breaks the split() call that expected only 3 spaces in the line. Changed to use `rsplit(maxsplit=3)` since only the is likely to have embedded whitespace. Added a new test case for the same. --- pre_commit/commands/hook_impl.py | 3 ++- tests/commands/hook_impl_test.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 18e5e9f59..f315c04de 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -114,7 +114,8 @@ def _pre_push_ns( remote_url = args[1] for line in stdin.decode().splitlines(): - local_branch, local_sha, remote_branch, remote_sha = line.split() + parts = line.rsplit(maxsplit=3) + local_branch, local_sha, remote_branch, remote_sha = parts if local_sha == Z40: continue elif remote_sha != Z40 and _rev_exists(remote_sha): diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py index b0159f8e3..3e20874e3 100644 --- a/tests/commands/hook_impl_test.py +++ b/tests/commands/hook_impl_test.py @@ -242,6 +242,18 @@ def test_run_ns_pre_push_new_branch_existing_rev(push_example): assert ns is None +def test_run_ns_pre_push_ref_with_whitespace(push_example): + src, src_head, clone, _ = push_example + + with cwd(clone): + args = ('origin', src) + line = f'HEAD^{{/ }} {src_head} refs/heads/b2 {hook_impl.Z40}\n' + stdin = line.encode() + ns = hook_impl._run_ns('pre-push', False, args, stdin) + + assert ns is None + + def test_pushing_orphan_branch(push_example): src, src_head, clone, _ = push_example From 07554e952539e9a85e6e8c33987108911d49fb15 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 16 Apr 2022 14:59:46 -0400 Subject: [PATCH 002/342] add additional info to healthy-after-install check --- CONTRIBUTING.md | 7 ++-- pre_commit/languages/all.py | 40 ++++++++++----------- pre_commit/languages/conda.py | 2 +- pre_commit/languages/coursier.py | 2 +- pre_commit/languages/dart.py | 2 +- pre_commit/languages/docker.py | 2 +- pre_commit/languages/docker_image.py | 2 +- pre_commit/languages/dotnet.py | 2 +- pre_commit/languages/fail.py | 2 +- pre_commit/languages/golang.py | 2 +- pre_commit/languages/helpers.py | 4 +-- pre_commit/languages/lua.py | 2 +- pre_commit/languages/node.py | 7 ++-- pre_commit/languages/perl.py | 2 +- pre_commit/languages/pygrep.py | 2 +- pre_commit/languages/python.py | 35 +++++++++++++----- pre_commit/languages/r.py | 2 +- pre_commit/languages/ruby.py | 2 +- pre_commit/languages/rust.py | 2 +- pre_commit/languages/script.py | 2 +- pre_commit/languages/swift.py | 2 +- pre_commit/languages/system.py | 2 +- pre_commit/repository.py | 10 +++--- testing/gen-languages-all | 12 +++---- tests/languages/helpers_test.py | 4 +-- tests/languages/node_test.py | 9 ++--- tests/languages/python_test.py | 54 ++++++++++++++++++++++------ 27 files changed, 137 insertions(+), 79 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index adce08f91..fa1678ca1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,15 +117,16 @@ get_default_version = helpers.basic_default_version `python` is currently the only language which implements this api -#### `healthy` +#### `health_check` This is used to check whether the installed environment is considered healthy. -This function should return `True` or `False`. +This function should return a detailed message if unhealthy or `None` if +healthy. You generally don't need to implement this on a first pass and can just use: ```python -healthy = helpers.basic_healthy +health_check = helpers.basic_healthy_check ``` `python` is currently the only language which implements this api, for python diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index cfcbf686b..cfd42ce20 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -34,7 +34,7 @@ class Language(NamedTuple): # return a value to replace `'default` for `language_version` get_default_version: Callable[[], str] # return whether the environment is healthy (or should be rebuilt) - healthy: Callable[[Prefix, str], bool] + health_check: Callable[[Prefix, str], str | None] # install a repository for the given language and language_version install_environment: Callable[[Prefix, str, Sequence[str]], None] # execute a hook and return the exit code and output @@ -44,25 +44,25 @@ class Language(NamedTuple): # TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018 languages = { # BEGIN GENERATED (testing/gen-languages-all) - 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 - 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, healthy=coursier.healthy, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 - 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, healthy=dart.healthy, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 - 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 - 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 - 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, healthy=dotnet.healthy, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 - 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, healthy=fail.healthy, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 - 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, healthy=golang.healthy, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 - 'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, healthy=lua.healthy, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501 - 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, healthy=node.healthy, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 - 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 - 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 - 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 - 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, healthy=r.healthy, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 - 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 - 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 - 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 - 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, healthy=swift.healthy, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 - 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 + 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, health_check=conda.health_check, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 + 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, health_check=coursier.health_check, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 + 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, health_check=dart.health_check, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 + 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, health_check=docker.health_check, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 + 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, health_check=docker_image.health_check, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 + 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, health_check=dotnet.health_check, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 + 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, health_check=fail.health_check, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 + 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, health_check=golang.health_check, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 + 'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, health_check=lua.health_check, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501 + 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, health_check=node.health_check, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 + 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, health_check=perl.health_check, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 + 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, health_check=pygrep.health_check, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 + 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, health_check=python.health_check, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 + 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, health_check=r.health_check, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 + 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, health_check=ruby.health_check, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 + 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, health_check=rust.health_check, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 + 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, health_check=script.health_check, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 + 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, health_check=swift.health_check, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 + 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, health_check=system.health_check, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 # END GENERATED } # TODO: fully deprecate `python_venv` diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 88ac53f33..f0195e4f7 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -18,7 +18,7 @@ ENVIRONMENT_DIR = 'conda' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(env: str) -> PatchesT: diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index bb3e0b848..9fe43ebd8 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -17,7 +17,7 @@ ENVIRONMENT_DIR = 'coursier' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def install_environment( diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 65135f80a..55ecbf4fd 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -21,7 +21,7 @@ ENVIRONMENT_DIR = 'dartenv' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index af1860c5b..eea9f7682 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -16,7 +16,7 @@ ENVIRONMENT_DIR = 'docker' PRE_COMMIT_LABEL = 'PRE_COMMIT' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def _is_in_docker() -> bool: diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index ccc1d6782..daa4d1ba3 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -8,7 +8,7 @@ ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 9323f4079..3983c6f0c 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -18,7 +18,7 @@ BIN_DIR = 'bin' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index 4cb95af5a..00b06a9a9 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -7,7 +7,7 @@ ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 759c26849..a5f9dba02 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -21,7 +21,7 @@ ENVIRONMENT_DIR = 'golangenv' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 808082665..05a71651e 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -88,8 +88,8 @@ def basic_get_default_version() -> str: return C.DEFAULT -def basic_healthy(prefix: Prefix, language_version: str) -> bool: - return True +def basic_health_check(prefix: Prefix, language_version: str) -> str | None: + return None def no_install( diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index 38bdf54b8..49aa7308c 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -18,7 +18,7 @@ ENVIRONMENT_DIR = 'lua_env' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def _get_lua_version() -> str: # pragma: win32 no cover diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index b084e8f89..39f300065 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -73,10 +73,13 @@ def in_env( yield -def healthy(prefix: Prefix, language_version: str) -> bool: +def health_check(prefix: Prefix, language_version: str) -> str | None: with in_env(prefix, language_version): retcode, _, _ = cmd_output_b('node', '--version', retcode=None) - return retcode == 0 + if retcode != 0: # pragma: win32 no cover + return f'`node --version` returned {retcode}' + else: + return None def install_environment( diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 0eee258d7..78bd65a2b 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -16,7 +16,7 @@ ENVIRONMENT_DIR = 'perl_env' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def _envdir(prefix: Prefix, version: str) -> str: diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index f2758c588..2e2072b08 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -14,7 +14,7 @@ ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 668ba3587..19fa247ef 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -163,27 +163,44 @@ def in_env( yield -def healthy(prefix: Prefix, language_version: str) -> bool: +def health_check(prefix: Prefix, language_version: str) -> str | None: directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) envdir = prefix.path(directory) pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') # created with "old" virtualenv if not os.path.exists(pyvenv_cfg): - return False + return 'pyvenv.cfg does not exist (old virtualenv?)' exe_name = win_exe('python') py_exe = prefix.path(bin_dir(envdir), exe_name) cfg = _read_pyvenv_cfg(pyvenv_cfg) - return ( - 'version_info' in cfg and - # always use uncached lookup here in case we replaced an unhealthy env - _version_info.__wrapped__(py_exe) == cfg['version_info'] and ( - 'base-executable' not in cfg or - _version_info(cfg['base-executable']) == cfg['version_info'] + if 'version_info' not in cfg: + return "created virtualenv's pyvenv.cfg is missing `version_info`" + + # always use uncached lookup here in case we replaced an unhealthy env + virtualenv_version = _version_info.__wrapped__(py_exe) + if virtualenv_version != cfg['version_info']: + return ( + f'virtualenv python version did not match created version:\n' + f'- actual version: {virtualenv_version}\n' + f'- expected version: {cfg["version_info"]}\n' ) - ) + + # made with an older version of virtualenv? skip `base-executable` check + if 'base-executable' not in cfg: + return None + + base_exe_version = _version_info(cfg['base-executable']) + if base_exe_version != cfg['version_info']: + return ( + f'base executable python version does not match created version:\n' + f'- base-executable version: {base_exe_version}\n' + f'- expected version: {cfg["version_info"]}\n' + ) + else: + return None def install_environment( diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index c736b3863..40a001dbf 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -19,7 +19,7 @@ ENVIRONMENT_DIR = 'renv' RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(venv: str) -> PatchesT: diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index ae6449270..6c5cff280 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -21,7 +21,7 @@ from pre_commit.util import resource_bytesio ENVIRONMENT_DIR = 'rbenv' -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check @functools.lru_cache(maxsize=1) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 39e36281c..01c373061 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -19,7 +19,7 @@ ENVIRONMENT_DIR = 'rustenv' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check def get_env_patch(target_dir: str) -> PatchesT: diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 2844b5e5d..d5e7677f9 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -7,7 +7,7 @@ ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index c6309531e..4c687030c 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -17,7 +17,7 @@ ENVIRONMENT_DIR = 'swift_env' get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check BUILD_DIR = '.build' BUILD_CONFIG = 'release' diff --git a/pre_commit/languages/system.py b/pre_commit/languages/system.py index 9846c98ba..c64fb3650 100644 --- a/pre_commit/languages/system.py +++ b/pre_commit/languages/system.py @@ -8,7 +8,7 @@ ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version -healthy = helpers.basic_healthy +health_check = helpers.basic_health_check install_environment = helpers.no_install diff --git a/pre_commit/repository.py b/pre_commit/repository.py index ac5d294bd..4092277a8 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -57,7 +57,7 @@ def _hook_installed(hook: Hook) -> bool: _read_state(hook.prefix, venv) == _state(hook.additional_dependencies) ) and - lang.healthy(hook.prefix, hook.language_version) + not lang.health_check(hook.prefix, hook.language_version) ) ) @@ -79,11 +79,13 @@ def _hook_install(hook: Hook) -> None: lang.install_environment( hook.prefix, hook.language_version, hook.additional_dependencies, ) - if not lang.healthy(hook.prefix, hook.language_version): + health_error = lang.health_check(hook.prefix, hook.language_version) + if health_error: raise AssertionError( - f'BUG: expected environment for {hook.language} to be healthy() ' + f'BUG: expected environment for {hook.language} to be healthy ' f'immediately after install, please open an issue describing ' - f'your environment', + f'your environment\n\n' + f'more info:\n\n{health_error}', ) # Write our state to indicate we're installed _write_state(hook.prefix, venv, _state(hook.additional_dependencies)) diff --git a/testing/gen-languages-all b/testing/gen-languages-all index dfd92c0ed..05f892956 100755 --- a/testing/gen-languages-all +++ b/testing/gen-languages-all @@ -3,15 +3,15 @@ from __future__ import annotations import sys -LANGUAGES = [ +LANGUAGES = ( 'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail', 'golang', 'lua', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust', 'script', 'swift', 'system', -] -FIELDS = [ - 'ENVIRONMENT_DIR', 'get_default_version', 'healthy', 'install_environment', - 'run_hook', -] +) +FIELDS = ( + 'ENVIRONMENT_DIR', 'get_default_version', 'health_check', + 'install_environment', 'run_hook', +) def main() -> int: diff --git a/tests/languages/helpers_test.py b/tests/languages/helpers_test.py index 259cb97c9..f333e79d5 100644 --- a/tests/languages/helpers_test.py +++ b/tests/languages/helpers_test.py @@ -67,8 +67,8 @@ def test_basic_get_default_version(): assert helpers.basic_get_default_version() == C.DEFAULT -def test_basic_healthy(): - assert helpers.basic_healthy(Prefix('.'), 'default') is True +def test_basic_health_check(): + assert helpers.basic_health_check(Prefix('.'), 'default') is None def test_failed_setup_command_does_not_unicode_error(): diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py index fb5ae7172..b69adfa67 100644 --- a/tests/languages/node_test.py +++ b/tests/languages/node_test.py @@ -62,7 +62,7 @@ def test_healthy_system_node(tmpdir): prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) - assert node.healthy(prefix, 'system') + assert node.health_check(prefix, 'system') is None @xfailif_windows # pragma: win32 no cover @@ -78,10 +78,11 @@ def test_unhealthy_if_system_node_goes_missing(tmpdir): with envcontext.envcontext((path,)): prefix = Prefix(str(prefix_dir)) node.install_environment(prefix, 'system', ()) - assert node.healthy(prefix, 'system') + assert node.health_check(prefix, 'system') is None node_bin.remove() - assert not node.healthy(prefix, 'system') + ret = node.health_check(prefix, 'system') + assert ret == '`node --version` returned 127' @xfailif_windows # pragma: win32 no cover @@ -101,7 +102,7 @@ def test_installs_without_links_outside_env(tmpdir): prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) - assert node.healthy(prefix, 'system') + assert node.health_check(prefix, 'system') is None # this directory shouldn't exist, make sure we succeed without it existing cmd_output('rm', '-rf', str(tmpdir.join('node_modules'))) diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index 616066965..54fb98feb 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -93,11 +93,11 @@ def test_healthy_default_creator(python_dir): python.install_environment(prefix, C.DEFAULT, ()) # should be healthy right after creation - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None # even if a `types.py` file exists, should still be healthy tmpdir.join('types.py').ensure() - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None def test_healthy_venv_creator(python_dir): @@ -107,7 +107,7 @@ def test_healthy_venv_creator(python_dir): with envcontext((('VIRTUALENV_CREATOR', 'venv'),)): python.install_environment(prefix, C.DEFAULT, ()) - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None def test_unhealthy_python_goes_missing(python_dir): @@ -119,7 +119,12 @@ def test_unhealthy_python_goes_missing(python_dir): py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name) os.remove(py_exe) - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'virtualenv python version did not match created version:\n' + f'- actual version: <>\n' + f'- expected version: {python._version_info(sys.executable)}\n' + ) def test_unhealthy_with_version_change(python_dir): @@ -127,10 +132,15 @@ def test_unhealthy_with_version_change(python_dir): python.install_environment(prefix, C.DEFAULT, ()) - with open(prefix.path('py_env-default/pyvenv.cfg'), 'w') as f: + with open(prefix.path('py_env-default/pyvenv.cfg'), 'a+') as f: f.write('version_info = 1.2.3\n') - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'virtualenv python version did not match created version:\n' + f'- actual version: {python._version_info(sys.executable)}\n' + f'- expected version: 1.2.3\n' + ) def test_unhealthy_system_version_changes(python_dir): @@ -141,7 +151,12 @@ def test_unhealthy_system_version_changes(python_dir): with open(prefix.path('py_env-default/pyvenv.cfg'), 'a') as f: f.write('base-executable = /does/not/exist\n') - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'base executable python version does not match created version:\n' + f'- base-executable version: <>\n' # noqa: E501 + f'- expected version: {python._version_info(sys.executable)}\n' + ) def test_unhealthy_old_virtualenv(python_dir): @@ -152,7 +167,21 @@ def test_unhealthy_old_virtualenv(python_dir): # simulate "old" virtualenv by deleting this file os.remove(prefix.path('py_env-default/pyvenv.cfg')) - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == 'pyvenv.cfg does not exist (old virtualenv?)' + + +def test_unhealthy_unexpected_pyvenv(python_dir): + prefix, tmpdir = python_dir + + python.install_environment(prefix, C.DEFAULT, ()) + + # simulate a buggy environment build (I don't think this is possible) + with open(prefix.path('py_env-default/pyvenv.cfg'), 'w'): + pass + + ret = python.health_check(prefix, C.DEFAULT) + assert ret == "created virtualenv's pyvenv.cfg is missing `version_info`" def test_unhealthy_then_replaced(python_dir): @@ -170,9 +199,14 @@ def test_unhealthy_then_replaced(python_dir): make_executable(py_exe) # should be unhealthy due to version mismatch - assert python.healthy(prefix, C.DEFAULT) is False + ret = python.health_check(prefix, C.DEFAULT) + assert ret == ( + f'virtualenv python version did not match created version:\n' + f'- actual version: 1.2.3\n' + f'- expected version: {python._version_info(sys.executable)}\n' + ) # now put the exe back and it should be healthy again os.replace(f'{py_exe}.tmp', py_exe) - assert python.healthy(prefix, C.DEFAULT) is True + assert python.health_check(prefix, C.DEFAULT) is None From 392bc33466178dfe32ddcbfb728f398ff0fbf72e Mon Sep 17 00:00:00 2001 From: Jamie Alessio Date: Wed, 13 Apr 2022 10:19:57 -0700 Subject: [PATCH 003/342] Update ruby-build to v20220412 --- pre_commit/resources/ruby-build.tar.gz | Bin 72271 -> 72569 bytes testing/make-archives | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz index e248c57ce6fc23ae3ac0f6794ba02f4270da89c1..8edb3caa3c27f1ccff9f0e0dc36e9863845c4efa 100644 GIT binary patch delta 63322 zcmV)TK(W8iv;_IK1h9!De=APO%xWZDU2Ykq-Qe67O5C^K^q4D2p5`+`XH^k11il`s?_Wbii)Yyyi<$I%e>uhoRt;rJt+vvjth;P zaV6W%sqK2vB#cP5C#nmO_lBWn-QnI6%UXQ-c5h4U?r9URbbM5RR1r-%R1evydAjJF zn%|mwBnPy7kNC$8ee{WjHv9>kL(+gN}ID(PA1C)-XmzP|j5?XxI zMh=?~Ng(R7e=KATM-g7fum>x(!ls}9#Ci3KsqKZ1SRojDg($XUpL8k_(l=8?Z=6$> zGMsj{4ULvLnnmR9#{<~}=gtcT<0xJm1^%TwUh4*aG;-H?YPEaS3o7adozNY$rHo1K z6e;>i5$#BC_HuJ)x3#TLJ6pyH(dlAE92QtymXoBdlOZ=Co`DUDc{A}& zbhTv+a;{jYN9m{$(4m0o2SkIwEhO z1H1dLsGyk<&T9DOGBXe9x}6vg-Cghln2UF^f1)O%6?@S(DP-q&T~M|F0r1=&W>}5a zlM)yyj{F8*YaL39I<;R>Hk2m<)^KV_DQv>Gg4sKX2*KvtbcYkt=ZyynBD5>ZnzJ@$ z^_{l_L|VGSQur}otX4q9A;MzQYYHi2*2Ng2bmxO!{qaH~}~yl2QYMH;H3K zexCWHN>G>i*iQPH5w_6`pB z@zVSK!=v{8-tOLGpjOjh?wADBo`UrLeM+(7k;azC{vm?TfCLKowE!bP>iZT@L*KA7 zwTABC&qDFu%tS^etP|d^Tc~SNta2)=e}NQp>8EW@<0cr>9SVeXc(k!r{~{beT$eWa zN>laX>%qhIpg>-|RH+oI$Tuii({!M}X%A2oZwQH>elIAfY)Ypz+8~O=Ff&7&Dld}~ zmefJO+98%d-4ZZ<2N=F)e*y?i zS;7*|_^_L`P`Jje3B{tmWh>r3n^9SfiJa8QFX^II%DmujvNy>qrDaayOH?rvrj9Pz z3WZbCE4xQ}U?NbH>e?L}3=WcV;vt zy2s0qN-C0?S=T5%(qRu0R4y+k*O@FK|7JDPWMpV1QyG)_7cm}KqO3q^e@^|#V8K3P znbE4;LQk1mQSvF%n#}7gfh^a)?^hpQdMGfXbNI&k?E_XX8I3FlAU&K(FZ`wz14-WPZkAVSZ!Q5;5 z8ilPr%YynwT&0-_?GY+0e^ExZ3NqGZnga41zDKh^)2^8^Sh*$gw-sTEsrkw44_7~U z_AB+`!Kr#g%aynADqtJ3_bBAK8{%Zs`j0bq;#$9-aSc~RVkbcf61vK~BLWM=SVRa9 z-&GG%dh}UJbSC=h-NIDg?uF)BTu~K3RfFzuGbPDCG}UUkw335Fe;T2hT<5D*9x8{7 zLKIx(O6uKo4Bcd)ZZ=j4m0+wF>(A z9!2L8LT2q1W{D}+f11avQMk;iHIdCF!F*9ALFIGl0-9==eW&B#$~2;%8_M}R1vl#h zN!+Sa%P3+8^FV0qBnOMsu=`<5X}A!1WQPG|D9AWT<~0jjrhBx;E+R1gr+f4`vLyU^ zJRYW)PCpo5Mv5z+NUz|OuZY_PG-LUByE6xgwKwRu!dqm$e{ma|Z+pEE&xw`5)aPC- zc@8_bcFvMf@;ML+yzlqzSqlOxyT)+=1 zY|fCnNmMN_CLZ#^B-Vj&^9u~XsI7GsFndawy-&~A;2ZYglqX<36?w-`&-D~_D%qb5 z>QK`r*1nCZe_Wea6*C|PX<7d0)*4L)@NE!TXtz_K62rrCGA;T&_ay^*;qoO=pe@3Qji_XJ@Sm+{(kMe+}yMSvh zfZ$gGV#PyX@FS?IavUCXU+b0Z0=F=Q3*=GLF8u~Zn|3wuly~@iO5M>E z8q8*eoXrnM8f(^o%7AX|QQ>qUqvC1$kX~?MbwTX|>Alp;Jb%8hDm|)+GcY#^VAdai z#f37Of6k4t`F8hM)FYv(j|Ydd%8$y}=}eld>@2&Tn#^H_*Cw61FDz!If(+a5Jv1II z8RC?mLvQurnh8VW0c3nx0fV^j$9(sDcN$eRhs5GnD^Cu73%&hMoRDv&E)ZaH;+ z4~Hf8AfLW*M|id8x+tp>c7BU7xiQh4mfz&`f1HW|3l_aFmEaY#{?H)hv*$3#dmH-B z=P#MA#XI@E@O$A`x!+^R4TfM62W@%sNz$n{Nr|4GXm93}*Fe>nfX_-$<0A&W6(=IG9u|p&ViCbmq);;$-N`t zf9?Wvm9w8$b&B+5P-fZ*l_lX6i^HkLT#D_~Q+~>CCD}abUz2*O6)|%>SOV$v9uY6X z<5O+jvzYA?21|DHhL{(>pt3PcZ`&ktXV#d63_%qOgUi`!+F?u6*UdZVM9-h1I<;h@ zT7Diy!+9!EJ`v>#yL=;DlQkt_U>_pPf8rjyVWzb$}aNTezY!5%Zuc^=d_6+W(0vKw`_{@D6?u(}nNVqa56&`j(s9 zuvO4vvf8<`TF4!&+CrHPPEz9=UR5*@#)SCIt84jJD=ALd9YaG1Ok|+{2)D^es(Wh4Cu-` zxT9!0i8OeOwxQ%SZR11G7P7RBQ%c!|CK!h#YUUC+yqiJb_{NJcM9*9l&6wirX z6J8H`cw@lKM(FJyXy^$Ge{(eHsJx_4TAS3S_0u=Zg#j!M2^RBY6?cz=-N^6I9r@;T z#9~rtaWye{QlS8xbEhqFZFL1_R%sZ7u1Yu7w9$pp=7$rq7K-$(3MW3MeUP0|W`LQR z9NhJjn|(P34=Milm=usd=keN4#CQR?J*_gyWGXzeQ^xvEVM@|De}OqG5T4XiN`D)<1HXQo{Rum4ERmIcqaLVvW8&DaZ9H=#jA zSyT_9{$0XoKMjcWD_dMn#Wb#%nhAO&HWPo@e6yRm3~r6(f7}qa#z2nQQzHdJQ0m)k z3Vr_Ek{MNab?K;7ZRox@3G+UAJzY}^hRUz26?mXsIpYsC-j+-85Dd<2o&t%N6wD_L zQpbTDIO_ESH6Q9nrciDd_?471#zF7$e892{MfV~0WHn0k>|93u0K=c!;&@8H&(?#j zGzx-F3>0>Ce*uq#TZ<#=vLYYexQ_QSO8)Lyy7Mz=LuJfTL*!rB6oC6wtYAWZTU$cF z@)&<+;j#XrQ_O8-Ys16+109mykVJ`fOS@t5UPXp&=)=-bdEHt}g#oft6+W2*Tw841 zpG!YyGi%&uj8shxCtl4Nl$mNXgIm~~dc^X|-A}nHe`m1Syc7kGZ+d+oW6CEe{YJ)!E|M{7WympP`wXbn>OZ9X^X4*+804TRwq1Uf4RK1 z@Eun##dG1BmM`v8sP=HHDxP40y?`R1b@*&6>VwJ~cETPCc}gT9YN_0@Nv?3=!kZx|@7ZF`Yh|gZ zMi*s@Ny}r}kx?1iy}*8sMps}4h^yy0>hdekf7(1rd`P;Y8(;E#X_f_b$MVt|ix-us z+P}g>MVOL9XUPQsM7a(MSghdK4f-P1$1Wr(3@r;MHeK6%ph>2Nmto|OEpX@EQ&5LC z&459^3i{}~l!tVQrg?h)%Z+PLxT8^lKl~W?pqeudDdSe10YbD2f)NKcPI;Q{}=@XZrO+ zWY^btHWGe69E8{lBSi-}EqX&{bq_fYF5oq@g;jMeUruo(o^H&jxeP0kX;;z(r$$2M z;OV0yaxZ$pb*_l;qTwxPBc7JlXZ%*_>f6#q?2>M;4)`o(>I#Fxjb$eJIA7YZYXG97 z%^+ff@ZxgZyXDiR-KU#%_9l_pQ?vf7Mxwd1K6}o^T>h$Kop;%feD=*d`CDojRH)r^ zLI1p@)7Cuc3@FVY*KT6kDvS-zYIxcBGU$Nq80%*a(@^BP%i0<{3^oQ zR8O+8BH0e;Q6eHPA+No{YWBF_GC!r{u~Jb)h>O^8%V$fc#U+jgnG`|Pc2;G;d<<4- zcibyxIxJqdSJxL#pQSr-iXcu4RDa!+4H_z@Mm#_unP8P^N*qnPT~N;w%%IcXZ7zoB z@+G~DW+^ZJZ6;gHXNuXGe?-kufG!My?2;el({%qt z17`k+lGlU;+$PWiLD4uHjDx{AF1(KVLADQEqeE1t<~(cLRrgv!Cs+XX%Z#6s0A`D`{WNC0=L&O%y7o8tBadulrLc0K}yKy-z`Q?G&t z1^WzRM=+a5`J`~K+5?ciM?zWI3F zF@5W}WBNu61r-)Ne~tJML8WrQWE+$@{keTLrFm&!rqf4iGOvXpzU!FYT+0~Aj(KfL zn~SG}HR$4w@FYns-f72rAxLNiJM` zm&E;{A`%RHu1D7kYIg+a;PsSiW+tn7&V_=}$b~i0r%zm|e_U|NjV8$~el24BKpc`F zeSZBl-GYIE8@N3dbjNt~f#e>~jIT}YbY4q=TT|pEn5jUJaU{K!8bOUCq9LyK+u}hk zpqV7vo^m7*fZW5|DQ5uMJsZx{B|ndp%`=|TJ_c#D=|M|DUzo!D?LGI9NJc?r;76uR z>NV*gqCvike@x2>-aCR-v{Y9~T(2lCAIpRP3(u0;iNThn;<#o@|5$jolu>Lzd7B2Ql+er@l@Cqf ztr(4wcwm65cJzTJF)WD@{J=7&SB)%6EuG(AQ%Br|e-AiS0a`Uvl0WWQ=p0608O*?c ze$PsFafzQm8u^hYzJ+<9eF|IvOw9Uo*^gRSk81VFI|+NWAwmk^NdMhc%~kaGb>eqr|-dB+?g~9 z`Cyy}z@Yq{{asJ5Pvs>P2BiEKzjvFq&Lah1Q{V4$)IfcFZi)__Hi@w$u= zFQ!t>yYae&gQwqemV>|41C|%~^-0KzI%1n6f2KpaYM<#m~vU34Smr z8&QTr&FO7k9wyxXgjWw7xSgJ{)Tx1Wc6>=eD?IH-AMc;)1S01ts1M|Df3>y6=T1xI z>P3^DKUf;4!Qr_Ey3-=$DMe4up?9a};&MHD#BVGaF`o3zW(VnLF=#6dG8HqHy6aOr zf5|ar*JOvj-8$af**(f;j)xHFQc<(&u2R1-^y5d#| zxqMl;sntwW!>I%e}{i9 zA~Kd@T3Sh)gmz<~Rx?`{MUV)!TW9)P)zu0!sT-2YU`l_e*v*-#WwTwHO$^+Z3h(0} zw_ozK_3V!27!g;&U=WNiM?pCLy%5Fk5Ctr?M^}(%8?>27&UNKrd1m>40Yte8VN`T$ zlIB8~J<4$k_a4Q0po{^oKcXaIe`A7V#BX3_w?vfZE=JrOM^P`%LPgO+Tt;F5-EWyw zkxi8(n=lJcakdm$Wp;yPcEKE@ED53EGDr6)9UEK%L+mKe=KyCb@~A+%`*ONi)+_crd68cuoFVyF+!w+f2N-5NuW{! z7QfvoQv+Xd7b~`!-HJ>>Bw5VwyK=A8{omO%#kwA$4`gcSbd5d!6YGy-p2C_>2)%_F zLGvjI?adO=}9~Hso!y*{zaizvhS%nKogSw4HlYyCR)U2E(n4tzN{^VSGuM!Qd zKaW$Bt0e)-SBf0-VFhRke|1xq0nf@sl`Jbay{x8EC#@)5@WGa#yf}`jsq&=2lT6N) z0@IYnA<$H68e%W;Ba0-S@IX_vokvNMZ=0KW`>}LbeATjuHB9ZEi=8IDL3IZ!V`aO< zk}TipXmjo?&TJi1#DK*^9jHNoVDa523&Z3C%cmVIr%Wf@)xmF-f4!2odQz>OX$@1kp*Uqz=Ce`z@wLdmE8jWgQ2R2L8%G-TJ6sBl^&&v<#4r{gU(t_z_gNlHE*ZxpieOImkJ#UBHdDwF9i+y^ z?^L&^LC+lCLu%I}e;O4b!h!k8V#AN zQx|@w36y+=w6&ReqgXrN(@Yc66n_&P%c0lgJ_P+N{j@m4e^kYQ>2dqk?aQdx$9Sy_ zA;KyuYKfMZ$~U90T+)iS(7oVFYr`ea`Su!_h0se1aW)I>%Wn%O=HZy6!OoiBKqjntY z5chKMHkXl@IdZCOB!eEUq;7t_6D6KMw^#4E3(j#eeWhDix|niaoea<<1o;fa#V^Vh zPaHCMXaJK^(Da-w_J_Z96 zxt0L-VM7y@W{phGI+D=M)G-fjbMz@}Dm9-O{`oNXQL8#THTuI0Xf_KLAi2jxgMUE0Yt`iY9@(_xsjacG1G(xz^33JpWBB= zM@PF1f2?b}Vg7BE@@eIi+YMo^prDxuohKneHp+Gi!P1i;HYwv?E^dgjKRCAY1nI<~ zJWV>uS^QY(lw!iNWnDT=soSZ+P_t6)L?>^u34!?omr`n@R&mM97k$~7&n&JjB(A!M z9PpiFn;+)G16>!h*J5VV8+S0qxeuL~jD*jSn>UFE?cm2(g22@D zf9ABnBhLUVhYCxOuD1To`eNphoR!xYUM2a!_|hHY;&WjRxB)hH2jmy^P3zTHeo*XVWv0(L>Cqlb7-+7FO+0lBdb-j9GV`2g2g9O1$uWQ>U!;YHK9+7HYHe>Q;- z9yrIv4@yx#M6NJ~S7CCZ=6sC;s%eviT?Mz^r5n<5A$tf9_$@$m;GJJ3tDQ~``V<3} zn)(95#hP^JU!DU;60-0O-aZa$EuJ2+hODql=YehuKZ<7K!V%xBQT@Q+3)#?67D+7mtitypeUe+UJg=#O?5#2P4Wgd5Ma2(V)5wGiIVG>g|ykzo`i zKeFHsT->(Yz^aGe+WKtoJ2HknZp0krAF(XIM1Wm+v`tFe3@o87cY7^vT zQDx->UY-Fe{2GOWWs{lG&wIuugI*BF61Giw)Blz`Gh2%F%R_p=e}4V-BvB$?TyxYB zPM>d+XYnQI7IayaES?Qm79BQ|BTtwL(_FEsYCPvxZ=q_Z0X2Oo$SO5j0KA;i;^=)emo#mw$DKWy0Z9Ssd*GUj>htaI^+o&a8XhQ6Pi^-cPg9Pbs0Rmp&qe@>{sXWTi`hyu-fj4x%u z+zid;;?}_d&nm$!znyHgoa2>pdsx)mFsEy=1a3C-va+Su{FNR+TwVOFBS0( z7-koegR@$H#ravfahT2^iX-HzA7LD!H;qosw-t}O1x`TNNk$X4=uLclK1&3aCCi; z$m73wcl_pn#}4y5%2if>$pyz+Qu*&`qn$%B^>fr1w>j*gy0gI*03Hzf8(aE^Wm(KE zEHS+*vIMP)lk^Xm;$refXK5P%kMgjxz`i3=YkMIJy$;EwgRRE z47=h*e?J1fdk6x3OqZK$ZW|L%B1CbB4hv4euXc{x+#{eJK$qz-xSb4Gf;R|a69P66 zN6xksOjRLiO5+3iAaLP6tW6&@!BN;jDjkEEWUpJB+YdN7GmYKpzZb6W)dfjej}}W8 zUAGpeuUF?m6O%YO96$&3K;K;|{9~c;?6+^@e}a8_=d6`Z?+b-*;jjPgaEb1TDT*-W z-#G=42qi^CUUIO&xBHN?l%F4;w6#0t)ro3!xopEc?d{-A;W(N4(SDWiU%Wrc2Ga;f zUmAkg=Ih1NZ&IN(cuh1gi?#~KdJn2%_?tEcIx3UC^j;MX-l2>_o%V2Rjc0)lZq{It zf3L}yZAH6FAfIXAeK7yVE+#WlZGDajIL(|t*hLX1bJ$H+Dy(K`A*qkZu@t|zR}1go zFQwkdlMQHKS|bl5&d*jCm-ufnDd~sMz~WMN9Yp?^HlJ&KMP{wgj2g3NJyV=AfjDb6fB@zfzf*cyqfS{YD zOT1P5#5yb3{DN_Nc)EOYvhIgKi0fx(rz-{fcl*>S;JZcm4qsm3_tQav6_N?v-D(5l z*x2Hox5hTJu9&mt;Gfc3X@GyrYi0UdS*y_B>RNT60PuAxoY-dom->WLZ?FE&e+u;T zAJ8p&LjOXy`lImFVbHUr6Yk9wTe=eiK3R0m*v4WB7OAzjP1w486`~AYV*jWmer2-#i>E4Cr}({d@KYuE zS~HB90s-ZQ+aV)KSZPHQh6`4`f59X4@(F)qc`^S5OR{r%%K$4|F@Um(Ll6K^X< zCIoN@_%8M7Sa|o+d49;NJH?KKRwOd^h|B_)7|&*ceLFOT<|EJ{eKwLLk#JY=yaSrB zkTtVHBuNjTSL{)XSk~04<*?xV>Q_`9&uYkKGYGJuLNs}WYpMW1J5e0Se>wt%vSo)< zmQ*KMQXx{dY=z2(!|RF|2clTEP)!!9C538JJ(MOv&H`kHPZ}K0@Iu@E3!8_p-eM3g zUunveTjDaGFPX!W*lMCgs@_RWIuVovMrO+DmX;1vWXycVkK8>_=?_Uz8J7XY(=hM= zV5ZTjOoTb3egcpO0)>p^f8vuz2A#K&nUF?uLkC`zH$9A0ZjBOGBr#AVvx%21p_5+1 z^ttGVmM^^(xKOy~u{ARQWH%Afvdf;b$9^U~0fUJvJPHOp*)R*^{?Igpc}`Erwt}M1 z3j)Rh+mZn>{0$wgp(`8dfC(ybxK(l)fl8emz=>@GV7HS88!5Amf0n{RNoX%Fn#Lf) zg6PG9W;90=R3N)+98D;(a~htk(WZ5I*y*Z0Q139|k- ze4aW?nyD*iXd0LJlx$!}ab8faiId?2OPeHlv57<};gN*-eQ->C6OX3GG32S*Y~PX) zK3-H`Ep`CGGY+I6_7tg3glZ#869Z|%9%#^wyv-#lpt#?F7&|Na7Fqr`!&$sr= zI8g%Jn%tI*-EM%%2@u=R1&z(vd~14{bFNuq9xm*HfO7t zeRgZz@;sDdfA5%=#BDLF_23ldAI9QdUg^Aq0X+m_Q7dgG3^zdyo<}o5i4O`$MC=jB zgVK54Y29DLXf9B20$y7#{V>fBt&4_m;yzJ~PHvWbBK8bc5Kb zXn~*R6Y+@Z2$jv=g7&FEja^hmmTyz5`QY_V58a;sr3oDFqU z&;RYFe=+F)>istSbx^mxUNrH$qi8U;2LT-jo_V_?hzBvI87<7XwDCDl+AtgL?DabO zapyqYElHa0v-}I3^?R@r6(~FPNuSt*vq>FFZeGodOuv$K@eVD4Z<2i^RR2lm7w>A> zcXSmiOo+M^HH$^bWvL1{8Xg)vgaZ)N1*aJZf7->T2?SW&iiKQXhXW65{2`c?N3GU2 z+QYbc&&+S0|MI;5+<*T1`J3lQ<_alLyZcGdQ%1|O7w@qCz1@p0Ohd*xY~-Xc64^Mm zex}BTH$EPT&n1HVTm3ZSFY(qB5#P!FJ;z?$YyNtHwyCQ54bjTe)>7p=)bAun6L=Md zejJ6%BWVt*Lhy^W|`*DglN8n|P+ZeXz5=vG@#2ja<-0uaM&|$~PuE z+Ih9JcZ`3ITZeC0>+d_qqKAa_V&@hl-tKhbGgxQw9+}bf24ALz+UC2Hg%Bwc5(0$M ze(mnOFbwK5-|Q4&8?D1bL({DFiH)NPf4>_lYe*ULCWee{e=k@xsidO}G-}$S_Wts( z^GWDUOa1wY+bF`PUait!_+SXjf+?J9{cmaZ=w@$vo)-WXv?D zm(^cdeO9bhDlFR!)zBgSbI?ETUJl2FPv6F4^gs38sg&#Wzp(0@{=dNb^NXZEEC*FYr9`TytP|9W!Yj;l+8e-uWD%R$(8Tl;U`>>OK%n|rTX-$P-!iJU6TLzbUd z7?xlhD}fZQO6>9aFJ?-ed$E z|F`(4yR$pXtS(@VbCua+3KmeK#N&2?5?N&hNz3uAG-=$jwD@t)z@K(k@_L)~AkEhzcdA!1Q~OpKZ{c z@4UIZt&|qOjS}0VWUJ`;f9z8j6nZ|@{mUAR{CuRiq?hjCBJfigY$*aL>~=BVAt*Xz z15+?)w`)PUaWLB0+xSl3nCzE4lIOoFqxbUIj~ zfObmTy@YpnTCT{5`W^djIn`Sp)ni#eLDM_M7;mz8w?GByMi&vXf2*BYERxTh$7aAx zQl=K{<+_F8PWJ@b@9i)ZMt}H$z=qDiJqjBf0$>d-z@MyOZ+xR9w4H{g@}^? z`Xm9=**Gdf`uGprg23c8VpkC{0aL~<&sqzbtQEDHsG;T^)LmFz5W9Vx5b)Xah1Gj? zhBd`WX52e3v2#=Se^6y{j;K=yPsP29Xw&nOdO4MTQjSwNxa7|%?*`vgS+{UI4YcL5 z72gg#I~9}2BvDHsg=83_Kn~x?khMPcsoV*Pr$g)Z6!pFxViV!u0&i9ofh;kL%M81W zxAb_ujp3xz3q6aIf`w-!Boz)Ki}KlFaF5=O8JE`!g~hwge>a=|vA@?oDg#&F`muHV zxBDbOH-{5G9pg~4O=61E{L?XxPt;G;U37mdmWC}SSR&Rlv-p_>`mi-cBoia0Eb(Gx zWdd~miUNjE(C6KA^awLk(}Vmh#eQ-*S>crKp%6qLfxU(UUXV~YUrH{v9dyG{A7oT9 zZ&9^nOYVZpe`Lp+xlB)H0zk&g%c>{ee6zBWPx;&_CWu}MC>b^{^VHa8vIwc0Q5IDb zg+-bd#czmDdwr2<*AT%z@PHzHgzEQRs&#z1V~RLQV!%at5%c}+w}y2IB`pipF)FI` zGD8bZfpV+qx9P1}DnWrOf0=(igZ(GA-aeoGSE_@tf1k1c>b26`{`*?&KYTH?cU)O(Dpl!FAI>Na7c>b{)e&otYoEw0@I^45CqZ0Taw8g-X!+ z;Oamj?QuUCUEt9KFh@u!`r%IQ(vyPDZ+f7lf8q*uu4xG}@R3s!Cdp6k69Mv9GuU9h9lf zp{Wh?dht#x4-%Q;)uTzwU-OaX9@+qPvIA&xy-^gdYR$O&Z4w4!krZD+ySHtuNY)P~ zf9EHKWah-|;i*{{FYcmaFLI4}pvzJQa^C!Cw=G#MP-36`V|n@v$i|BO&9l=}=hRHk z!>ReqBCPjk9RpISu{9i zqN`xbxFJeUUc!l|E2VQ$q49A#D?K8K)-|7w8u;in7waG^P6y^VX?@B2Eq00q+vDg7Z&gM?7<^FCkacX z?O-Z{RXTl=fs-|7{EzYfe_uZz|1Z|cbN)Z){~yHvf6X!nT?+7*J{VM52@A%kvSFWHCnt6F>`~bp;;8A#s1t4rfstJ_2+!A zC0|;oLQjVr!ZP7H0PcFGW>tkdH1>e|HWc9{;O85f6wE8zXbm$B9{z< zUrlLEM&VNO2sh4=mo24Bfnv2}5VOX!Y{*%WvC1?_(u;1XCtlRIMHVS`-YljGM_-r&L%m)KPk9K2t^wWBH_9sh-&FrHDM0pMomBoj91L(+|27A-#e52j5u>x@b!}D~1oE=x-QP&C?-@ zsw>p0%$SCo)*Fe^|L9s|RzSU7NLy3l@*OT0paGFrczu04f4qQ|TwgzWy$NsZ%ix_) zPxy-A7QnJv6s?adyw}>{_6A1zNU#WVXN>FUn%S9*XIOTH9D?9a|=$DF}YQ5?@rE0NStkjD^qu6K$75|Uf zIoYW;F$1bLe@piZ3yCNO^uWVDa)#hLO)<^v6i%!$^Tu4cd*6M}y7tW|90%H5-y4`% zaxMku@_$PHYgz67Z2PZPbJFs^1kdLF|GEA5fc>W@T})Ylt`B-a&mH5&nTRx#KV`6T zVkqWAfv>Vk;z1NKcR9C{MkCkj1$vKU#6+yFCM{g|f2~=jqZFop)F~)Zh-9rl#-;?$ zjLFM0`?R7w5(`1z=i8adJfCXG46Niwf5qgHWv7d>EJl~@hJ(pFao^txHJ~pdCVRs7 zZr{gi2y(~BA-HH6KL~;sm;q#)?if`4U@Y$%lN{I}+oSlw?ol+NenH6FfjDIz_zFAM zwOKKZe^k`D41oI+tna7rf9qo8x}bjjS^U3V%IyD?oSHMY|K|MvY5e~ifu4Ur4oC=I zV4;)DadZ)ox1Tgy8VFDY4J1#O^G_rc@@DiA;m{UU=0YN0Nbn16zdk9U^M9hTKY_=5 z#Q9&RUY(!+{oM9nmdAd%^{GgN)e}-MPhK^JIy`A{eNZjU-@|YuPFKlf-k#4Bj{A>ey3jQ z)*IbU-K{sffzzm0y4|2#DZ2Gyv(l{xe~lv0kk{!pn-#ZNs+1}fzd5J>FF^m*FF^lu z|IgP(|J9GD|Ei+@a;MsXw_;O1WuZsSwrH>{5YDrPQQx7T~AJqR^*K3v=wOYMi32HU3TCH`1YQu4gm2TDZyH2eH z?4VLEy3L?juLYG_(*eRO&Z+-%f75^M6ZwB1r2q2N`9GNHIsJb<^k4fZ+OH|PFLu2e z$a4_>ej@-i9yR@vTdlbz*K@p5xms$J-Eys1cRQU%y;d$lg}UQ(%H2j#_Wk;t?!OrQ zqxn|+lj%QC|DT`#`kLsUu;AmE0993-pwVy}ZmsOQL9^sEyKcGBb={g*e=dSi06Kx| zfx!2iYPaS$y0u27;Z>@oPSb0aicX{K)aL%bFG2rb!v3G@|6d#ZJ0DN~iT&TGdbMVy zRtI7(H=KrBEjG#>uj|)pZcuJCyB=tRoocb>H{p-#S8CmIy;f;99M35gyPeLQ{=WeI ze~JCSx&QxbqyNgs(|@x6e^+t6qF*f4opPz-RYAB{OHQ#{uUCU=*8}-qu7kqgaGheO zS@UZR&+i67r(AQqTDMjK#bHkWpPl|oj7ySbkd@TJ#RYm``Vz~|?zvy(m za@Vg1UB|1`JDrMKs`&MKv+0#;K?yeq8h)+m`S7^t`d$Z&gKpKSf6P4rUxNM{wa-ug zmD-&Czb^W3)IOU28;K{-Z{T{Dn#E$^I8~thzy-y>=~lc_wOj*Rpj4?;8l|FJ^_`Ae zbW6p$>y$kYcK~XQ&Yb?g1pU{)0R7kJ^#7I7fBoa>zpm)t^_;2;^ijvq-@t2nUOhm^ zo>Oz3O1avob$q{Cf2(!8My=}AI`vx9Euw}{?v$J5nitIJ|FhG7`H$`ae$@W|-2VT% z=)e5Y;=i0of4A9b)}3mpT&*@h0q9^@Z^I3OZnfKV8-BUeY?LaUVg+P>(e0Mpa?$P7 zD}LE2mAyvMnMeP88TxO`<^P=iYmJX*0F5L9sNe<_w~PPYL>RIij8%~HMY zf#P2)gF)cbE3WT0yX9ihFM)|rbSov#^F42V{^v{3fAjOxe|5h9|FzM7^W*8isp!Ak zsWpMPI}Nw!2h~#5ae_vr7Pwy5amtl)sZ%YL>TaV}56T`Cbo^SiUaw+6VBnPNcm`ok z|DT)w8(-l3e{W^3|9@rl-za}H{Wp^Hzh1fN`}LsYpbwx_srto+-*oG4*{@(``Ff|> ztv73Kr4#tgZnM-4{A#i2`qf6KQ!V?BJE#9IK>uGr|F6#Df4(;Q|9Jhs(a_?5%C(Nu zs5s?rsZy`j@aBk8z1k^PYrfm<*1W(46#(=AJSbdgf7ZGcw+m!es|1Y>s10)uz!#wZ z=9i%VdH#>DjQ*P+Pyfw?{+o?v(W?fXMzh&Ys*PKe(snk8E8I+sNqCcnqFF^la!v3Gf|9oxq|MB*JGqL|mMb9mg%ik?Io?EIm z9Jg6-e>&Ypx9NI8+4X&|S?c)Bdc*6MYu!?}S@oP^v)HJ*)uKD!|Ns2-UnzZw_`kXR z|8>znV8O@IKU7upUk!YqlTI_}c%7=#al6%Kr%|ev>y1X>H3P5V)~ekOJSsJ6ezRKg zp?IfO3rZ!w=@y&aIsJcL`mcY2Ha_b7?>zqRe=DN@j|=@tqJDs;OKzi90v$g9a&o=8 zi}Jnefd1dXTO+IGN>Hyjz!F@i+wIhW)}3%+*-4NrvNMdoD+P0`v0Rkz&!r%zXSc(Kbrg#J)m1ER=sMYRu47e|XgZji3oEpkDK8pm~E<*qKxR7oqJIAO0gJt^}r8moo=yNuDji)@7G*t z$*uhV?7dBsBu9>>Ip_Wrl#)5@j7X6H6i}$n$ewzSds>k_t*lU>P-r)2SjpOCe|=1k z%#UAKl9e8eOg0(jnPHic>ZFhn?rsrg9`?KdRPhmsTr<-OTLCWM|AP8|-s}IL8vlHr ze}1t3U%IwY=RkLT)A@TrnaDV?(y9w}w(3Qcm)3c#uSK8W%||D=YyW@qe~y2;r2ps6 z|NXT1CpwEi-THsBYEr_`f^HFp zy+YyVU;BzxW@sjSMT9GkK;vyPIKy%l9|vd%d;>%M-s-k;y&`(KY9kLz6P z^?E*^uW{W<>=yf4|FY*_DdJ)HL?6q_T0_M?0W8~)4~R2}1tA&03N|?L*L=*XG`Mt3 zbofwzvh$gXEFTI6-Ju~ar*w%0)!#JlojK1Y+WlAnMB*e7N(Zx&m%2eX1*_Oi9Vn-l zQgx<{3&FZ{TMo(7!!Hjc%JQ64&!@v{eaye6p&anE!!;e;UqlI)EiOX4fc$k&D)c_F zSJOT9vS-gt8lx$yhH?;_}S=p4z+Mn1{oRZ}+8}TIv0p(YwKS2&_BaJpl=Wu_e55=fayy5t7eTqJAe+B zS_TcHKwSpzT&7^RGl5hs>sBX?sHUijQ!ZBMIOh>1zMiGDr8yP9UoyuoentzA>E6$B znj`}=XcKCKK`8)3dy6>A@Pxa{^gF9aMh9TK}!+& z$LpStbOS~Tx*r5?fM93Jsz2<`Ni&lO=}735#eHVd9Y{;+2;<$jFHYm+?4c@jEpW!& zyT++ox5&JIf8Yj;O$UuEJveX@Fx8Xq4%o%CT~j%8$@)Rp%<-rzD(4h0J+FJkMr$eC zsJ*yeCl)F>g_bf0<1i@rhRr~OK_(pbC7!lz(}YlUn<4=Ti1wa{X2cB7 zkxS|Ms<~W0TR0gN0rw{KdH+zzb9*Q(D66GHX~&+pTq}<=$~BLjiX5iu0lfO9xpsS)MiM z>K`=c>=gf*?0xTJ>kSAQ)dD!lP#^%eX8{GC3@1%~HZ;$&>(6tD$$#)$DLO6yE$L>u z+r}#VIoTvYPh)c~lXCQ8#{zQi^RwH%UjpIY^It(66i7%3CZ@h+IFAlyfLHf$?5HeU zx1U>?5pM`--HK8Z9Jt0O&F#PTiOgO$qP^!i1tvBj#8?9k?tq{@aFVop>*3`NRg$&3 zN&DNpdpxfwH#nrB+Tt8T?&eg3&H#BnhcRx5wK@R{2GQjYFf5Ghne{s+gG)v{%Buncf{-)z1|5q_vN8W-6EQUQ61yos-j!>rGOg@e~K&qO*EpPh*1X!*HtQshPhf56Fh=|`;#^yw15w(aI8 z53DUquLXxLE?`yVz&HTMiBh{;&^QxRCA?f)wlENv1Vt2x=@`wrUD%!Y%{i8h2<>k`b)g zNtxa#YX+Ye6el6Y*z?#=O@M9jV9d=T8;?zy$yQRunvQ9OE^p7(`S0Y&ly&*;AXd4u z?gnv3*Ib>>&nS4SfiPV3oH$$C9tTWb2Y432A>J#Ks;cWr8AG__`#f(np2rz_XEL($ zI!I0*(hp7fSao`+sDV!?#43#y!01*_FbYfNgaNtG`qA%R6z4j#%KbH@OPZLBY1cc_P%8 zLDQLAaV}vhTGq7fLgl07GYS)|7fy7}>zXMLOg*0QnL?S0YF#0zb9kdYsnJdBC{P3j z^Vqj@phjG&t%Nfz+#>tc$vfY6F4yZ8-WUp!y_f9p;Bn8_o=-d9`&!N^q)7F{IApN| zu)o;Fe~ZnsGB__Zaw&l0gpet8?y5j5&SgMY$-}rb>rQ3<>FY^py*KX*2}-?!K0O0X z@j=dPQ1tg3>E=ALtF8_< z=76A_j^h3%H=*Aj3K*s?ZF13mm5J~A@{RwQG0#sCfH@*!xUs;CtM%zRzuSB$EBcu{ z-&R-aHo5mYZ3q+474n4*GHm?|;5Z*p)(wGpqRz^F&Xep-i6U$g<(bbTG!yO$`WdBq zb%`mg5>oVz(;Mb%eR$GUXmpk9{38w0FMnF{R-SrDBz!EH1rL!SD}X5m{zVzXKovid z`!Ud+E~el9W@=u|@T$|7q9O;Or!s}S9JIv>mu@{Waq1Is9w%Xiq;x^l$l@q~)PVac z7$jhw)QW)rQn>i#V*W(=!sx8hlRwYW7^{kM_$jSjxm32${Yrden)6Y#?EA5f5kU33 zMw*oD15KcT0G`6NI1=Pyg#J`en3VTYAOPj#Lt|N+REqV?J;?m!nfI{vw3ga%%?(Kf zrqMmt%9b66RZzBqLRY|O$Oi@^;QT5Ig9eLCtAIAAa>iKVOX8SJ21iy+TXuxQExxBY zH;C4Krq3U|8@s#XgBRm~y`;XJRi6PO^{G zYjeKUw9;nyzW#tE^O%6dszq{|L0rB}wGTRYrwK;RKo4v|bX3Gz~-HyY?495C`8qYtUm-*p}^F#F%eVsI%@HW@u9tsCAG9#mCK@6wP7& z!3|O=7n0Hf%N7KTeJ}V$f{ehjIu+|pf9n3)0|wp3v<^Py0IhYx4a*BsMk|K1&&@O} zeQoJi`a8XD$`b^b*4h`#Kp9oI7wWLu{zHtbqksayAL3qq>A$R(h?GRD;tlI?1RSk@1tN9^=iTnx}{aXcrnML07=|GBe_j-eILzrd0jQPtK<<3VAJCyPUdd z?ZX;8dz=(aXX#s_&9cZ}|Mg_d=imG8qyum_Ked2K9H{x<687E6ifS;-&r6pepUqpf zT&*(iJ!hd2`1Q6}nO6un|55=Z-w(=lmF7~gVkOWd#)7!oPdW4ahXI-XJj$Xf?jQ8pED zr0R}TP)EjQGB*GmVhhXv^o2QD=&fTjNp}z3j)C^BUB&X2{WY*BjH-gH|{h7SW@Hz28ir&l6nri4} zk+;`s(XKg5F<({TunQ?<^@5Gg2g59 zk&Jbx0vZxj16ZipAZn3ULHd~KwnEsAiZ?%H`O1y?4|N%76 zP_{|%ASVgCyKfKs*8oqFutPWf&-JSuW?0Z3BtN;v5bJaC08}JUoG?W0ztt6OmM5wD zz{2*u+djDEfcZE%c*lbvG|&`K46b{x0#DV6Bl$uwe_04+i#)`;mj;tAC&TbA7NX*! z+;f-Pg<#UyY&yCb2-OR<&qdddOxZ3x{g5fgdCZ?xov-MYi-S`~JlFeMc{{&MBZmXJ zrDXngLzpfsm3SZArH6+@C_atwG`79SU8r%#pSJ`eu1m4Aj-03w4EjP`!NT4_e^Hd> z^C!G1i>pD~>?i@;x!d!Ty|;)w6o>*FFfRRp4H{r_0C0EtT&Y|>?@C{^T^^Qbb>iEp z#N^Huw_7AAJ0`r-@QdJTiklv>!jrwU2-2FxJIa$pyMxY`xq^*kQrt-)3vdn5AAiVD z@Ae+lOqwk!gExf$5d$`d0eKjZ(C9XRNZc~8J&Xc4(9HIwz>ec+bLF{H>`gbS{^xHsKAKNqrdFMUMt(@YknvqQ^J^z$eYHB1i-A`Jys-x%mN5e8n+Hh- ziqzgpndL4ow*dh$M_=BKOVESm39Hz==nL5IpJ zHo_MaG{NKu-Jr$6qmx91n$xJ-@(jz==0?AWocnHnO*=2h>U~f*7JH@q!qrhwqURJi zpsVXyMVN5wEQzxM5V&4qn+a3^LW#*);K!&1wO4a;1$(QNa?*xPye_6AYRvWRL`XM# zFz<-HPMqlKb7QTDsys~z7hUuD5`?)Xi$ME!7vk@~$XF|YWu+Q4_pCsIBvWwT{aq2q z;0k?z8fWE#!yA{eP{ZvL%ms<-sH&=?rJ8-;zH~g0mKqyD7-M4;=y5LrTQEri5lc|s zAPMdMU{&p$zKQA~6MME5qK41inrHd9&fkcC?=RX}$-e+YXPj!DB1|t%!1fZY8wSyw+|3LA)pfCfE5}|2P$FjY@ zaiGn+<+b);=Tc4;XQih|!YNw6M|h=}2l2{Y=OH7~cQJEm%T?kR(f8o13$umPEmeS2 z7ZYQx5Rk;0fmF%w;J>YNQ12_h1M!g)cuaRz`uAyayv{pE9%+ZvubOoN23<3^^v5ET zG(OL*YZkq+D^dZThzePTun1~Xg%FexAx-wKLfF5cXXJL@3pFami8)6P3W2My=&4rq zrxdgDEPQAxV`cX;?v$o2h!;Ub2nmEx7k&P3gPtlSAn1gpo8?7=!?7YmE+t&z2?sK1 zeWsLeFz3y@v}Ae}-^JB=q)~BrI*@w*-}JNlcZkd`PIWK9K?rM=e~@p|E|R7#WF71$ zUVskZBE(QZE4=i^yjKzj_^)DII(YI7QpO%^AfO01soc${!To>#mc^rp%Kx#>d6Bb@ z!Pnj;a%F4EXQpIGzzaxk7z*w|so+TgMMMMP>z$hL`>`eXz$W`6JqkC6D z39Oh@#Z_5+mNApv$2HFn68kyy0jI?YV0!cO~Q=ZFhC*1i%ODjmdC@X5C$jIQN%BSwauI0qyLI4B26P)eU}6=8tPy*O zYA|whUrQA+a8!+OWDesu*`Az4-57E>z*@sRKv#KlgID&F|1;J%Sz%IA4te%J#WWDF zRMmJPquNSwAMwRwjv(Alz?^r2-KuuR$9E3qR4-fYaluXfV`!>`Eke|P|CUi7?;)Uu z)!2MToNT0GUG{*ub$;&zBolnn7z-`9IP z6rPO33az5lzJq66j@y^E$*f1xtDST?lkrMcw*7c{PVH)r>Ci`8JP+s&DZIG%zuP+Y zn+1>g2Mee1C{#)%2@}~o3}nN@kuM+CDOi3hYRhd5^+?$BwBafgubRM9ZmgC=3};+- z7^IOf12M8d0tOWT?~?J*D1;clEFJ?v@U8=Ok%>4dYpM*Fa*9MQVpv?o)}<5E_vRm9B2NuwS5U@52JqxM2#kTT;t$*w^Q!;ka#jsgT{La# zIUvw(ofmzSk#ONXROTIHlCOyoOPf<<&EhZdlc;12{xU#P+2bzZ-o&n=6o4-dQ@d^f zCEI z0db>tB<&QJVNl{&fY^ni_o8W6In`3)w}9>?NtM@Y;vZ5z;U+UM;NvKy z0E9w;50ck(A$*ukNjAeV_8Fm;!|ZOmb%pmX23Bg zPrdt+!eu|&zx!pH&e>3F2|$g6X$%UT+!|j+#q9!Ti9nb<0=Ub7zd9C8-bx=gtR4Dl z!8M*>QW2TM;r&F}xkQxsBe1T1aWYfX-^$_QYv>uL++`Xzqu@;PWG|tK+8H<5vtbMB zVp>K31`l{ifTeL3$hiW*M}-bj!PZp(QRL4C{mP*UNe5Beh<}U&@=*%*7*U8_;Il6d zWf{?LZ*=0Rb$~^sfr;lw3&(_{WbNOfsGM7)*k!4vrjf>0vZ%Z!Tq6o$U%`+N5ZVpE-sL^sX-HKj@_ zo0%b)cpCQag(fMJ?I_F)7$F36+B_ezjyqiTR95>X&CyZwIOKm{@*q|^pVR>e3RNCXxe7SiS63AvAv{ZB`A-JEz2UY}a7;7`vB zq^YT*8I^A#MNMIC+rR|)g-i&kwL!pbCH`=hk@j&J1sey`b>k-@BQnMoI?+E)SN7JE z?$Yo(9xtN3$E)1P3KK-(T}pB{2&Sxx!0t8}rdL9m89~n9h>pXC<$^>Ue`QC7?5!V{ zdYi>7GG()*bEnTI`J5TWGQ;C0lno+#Iw0@47wloZgU^K>)U@2SxB0UG;Y}raRmIv$@y$(xZ*inweoTG7@&*AGuMmw^5tq998ZlugSXPfA$Z{HQUdw zc{*;iM*R0k2Zrux-FG%EEI0al%ej#TUREMu8zJD1)KRdJP=KBbCuzZFsHFO(=4a#3 z39|G2qg|Z@%Ns`^t<4*vEw}DGF>MKFVKb38k5akK?axa==>Ya=UlOzim!*S2Dr)H} zU?=^dqMCy>+y9P3Z$D4seCJ%s7ItN@)a1RvIE>~x-W><}$xw^^n$R#Lw?@TwgLgaN zs!L2{Y)eCle6jIesgmAMK^YhLZuR&qZ;Efb*_2(b*99K>7mkzrIzZCqu zv4F`)MJS<`2rg3wgDI1;PXZq$o0?iAnGKG;oRH4as}aKv3AR&@M8QKV9``cRBW zIMX2GY~>ut83meozN_HDI6_BF6x91Fn0pB&UJ~bAH+J{n?RAHM0v=uY>5)gKytcI3 zCD1KWM^wrO&#JS17dW$^F>)xj4af~a8zf9OXrBW7WOnTjPmN1n)jVIlm9CK~taq{8 z_9R;0GmhqomNH*4o=#SQ`Hn=IBB9e9G<<-Qk$mFlRy5s0X&-6rsE#2 z{N|<_e-M8Wlext5asdX--AniWSeyS=is{?aFZHEMxjzF(0ydE94&fjn5cCpQQQbA3 z7zI`ahXNwo@qXW~IAE>ugBPJ<@ES3CksmZq~lPl1R z5f8QqfzV5Kb*a^bB~yJtu`<%J&uFFsx{O_x4x;{I!G&O-C1PrtgR%6Og2P3*nFE%C03gOksMDY++~mZh#oDfxf%{dY8@|K>aKs7SIY zC;x~09Wm8A$B!4GYmQ*_-Bhr{56@+7vY4{=wPg%`K=12>hJp zNiN1+SgiGq?k{rme);8EfCN;f+SPVj_~i+uud>dsN6o^Cjzhp^eh+y~06%|HVYfwES}Uytfe zL_AIc_T~I*-+JLE@ApiCl4|&qi7Q;^sB~ko{!wgpxyC90mr~G0+wAM%y$(^)!r3+ka5z6dSvM8xjW&fziQccNp`Hc zk-m@Dzc9MKW!vyc(i~$$MSm)VeK&v`jN$-ZLZCK!DjF9i7TI!%59U5zYN+Gc2^Gv7 zg3_K^|1q;gD^~jcN%>h-ipA^=5bkfiTlB-Mf zwmOlItyAZGD>`_dJKriunh@}SJ}Ec;GpS!hM6#0iq!4@@236XIgXwYChFzR+4s%k~ zZ*FRQ_~PWPy+V6^(ZTZ-i??9L=;?P<$2pL&x&rO&OF4CzcpTJ{gfLAS8Tkp?s<#h3 zt2%PeGwJX!Q(}Mm;|GH6PcI9ESFxX7X}Wv8+?D|(4}&!V7O9T}Q#$vp5vAWx{Jy4m zT?q5SjJ3!lv0yjV?C3JPlQR9-MV`FT>G}Y5Gi9Sd-W}v@F#kK7fPKGVFSK}Jp>6w( z&ql{aa;T`<@?ef&2JLQR2iIdf<5MLsljx&Q%NIJI1H^Csx5@wPSh_bkX&ypp#YL!_ ziU0AC)<075sHC;{4UZ=6tBK-(_|nc(QrpNL5o@f(Ax3~$<3BrAN()HH$@9exOT6)l z&C2sY`n*#Je`5Ibax8IQR}-daocv8}c!$t5fTBtSjJrsgx*dXXD}ws1+rgevUEhz=uD*DAZYi!G03(p2gTl%e*5$ z^{JwSo5Zee8xD+&{^~U3D^_qhq+v-hH`2Vf>@PqA7p+2D9p84h{+{@~FzmdUPC+Lk zCNxPwfOPQ~5j5h!WG~PnflYrz=sKOcWEvwZ@>3(P zY9^Ez+X!oL_R;-`4YU7^YM`K-B>|GFMP&Fti^gXPpR9;tO}i24r>Bu_94%J!y-rAX z@ey8;%;A zdoNQwdABR58|wGb)`#vtqPI)1@4Np4XdwyreMd+J=eq%!k;J|T62snJ4?ytb2l&L+ z!h{0@_1x?>xH>4}C*2DF;Qb99HRv0Sz$3{G+f`TddfGhh_|n7Ck|XN$jC&IBvM3q5 z`p<5lhrc#z#+W1qsw43`=~u3c7xH!+hsr;@v1|hw3A)Z(ZHFYD%bK3J`^Au&JPJ;) z?Q1Ky|0%(LL-TJfSZY=;y&fMVEWz?m>iX$Sl$4=QcaGmWZ&#{msm;vt!z_&ZGYZL^ z+U}FlKP_hpI+s89a+sL%Q!n7$^FDpec9zk7c}o8_k5nUz;JqO2X3H&btZ{{LET zUpyjO4oNQ6YNC19d+~ecC9~y&{n17*{n+GgiT@nucO9-^J#pQ;x}TJ(5Q?~euMx1a zL&OmT`u5cvK`42u^4sIHn-{5xx`ST^rAM2$i_)aHPn3U^VR9fH)FtoINrlg?o zmdy&CBnc{37K0fSQss{)FmY%pGn>Szhw&aaw=YHL1%q&1?LOCag7_5>5(c-Du%sa&=U#q1-SL_*dPreAtJ^QZaB8$5IK*@d5iTf|{mw5T z;DtmQI8e;WzN>gq=he&^E{nR<1)wD*nbxZiv_Ped5V!sfZFE zJ56cpzRu^3G?l|lT7(>$%z=J}F8;GQp}$5te}aZ$`2Hez2ye`y|Dp6}t#(pVt_4P( zK{=ag@Ua;Ox_bDgD)OSr=Fd0hTlZXk9(=yY@&aHA_J<*mb_o!U1H=qNZ#!Hed|+e_ zI-QynB>D-uAM80G5rFY;jtSx_Q0_jIPn7besJH?30t)1x{~r~Bsecg(4rG5ARevRO zZn=Wcs~l)+=#<;G7^`(aTwJ0|J|bJ76gqWx`eSZ$Gy(g6L_ID73w43k`6efgbVtWx zOjoaHJwE1GdGYY6{!W=S$TKD}t>x~G6BjrVKTc_FLP-5!Vjq&)A=JLZ-T%sCI51W_ z@pp4GhRZ(HF!nv>kj>R>`PPqVtnte>g?LfbAg#uydsuY?D*NXQ@QDb@DWe5LUyO4XfpF$Y`FQ|(OqeoEeXZv0+Y5_c<9>17+bES^gV`r-jL7J$+U zRJTUNqyu&{aN7X+8C1|UN0h-7;9Y^9@BT{$)q?X#eL|UTV+2&m6md&<4l{@LMCaPM zZ8|5)UA)}VZ|c&2xlc~jJkhn-k@(A#u;ob#6zh$tyWx%dXchy!K zN<~^#Q-a?p;u#fi3B2d~`*P-(UaE}9=f#CQvHbRyhxJX`j{&(1aXI$iRnuq}J9UM%vLt>@V=PXGP4n_k3=AZXu< zCO`KJWXreo$Ol+HK4A(CP~C0rde}z`wiFG`FnBMe1bCJIIGH&Yxp~kCT!fbo!L|GR zgn^X1LHDYVJ$ zk)Z~FXeT6E`2{l-1<$<;Qx*__>LWkI@;GwFmM`BW=0Q5)T68TA;?t*OURx?NZ(i8} z8IG`2e+D9S5wpYS)A&h)TD)Lg`P0%A+eXViy)GI1f|Z~%@UZM<9dB3f?iX+RpWI>_ zVCDNiR{BrPKs~!q`BTbiV{Df@M^7CwF-G~3&DzO$#|v79qxDM;S{U ze*`+M+Yl=L7a`TIK~{e;l}QR*$8=Evfzu2%jJL1G{Xoadoe9kn&?wBTRQV|*Dc;I3 zr?qu@Z?WjVwjuJ~A`(B4Ao12mWy%4)QY^HOH)z`N2HJ;M2=Y%FbLGk(ZB6eeuJd?) zD$b$VW89Q7+rmQoT|>KD`?8b(mXsI2^uI6_i`BP2xviJ<=-IAA%7}uAX$q<8uw#`^ z70ac3`Y&{WM<}Dnltk42TFa1u-N2^rnKRShX*oA2%K6OiMlTo6)TVlc9u`9V#WVu` zh^1S?G!#Zc-j6lC1oiu%+J?yZtGnpkO_~PDO&9ZlEY^+G_hkyo8z(Pf81q)tHp4 z-?=BuCw#C>6>^zlydSbkxWEy>OF<6-hh8LGj9CX+=Sk4gI@k(DBiI=>vTkvX2t3LGkU!0+j;}e!~MBEL22BZ1EqHim2poeBGfhFrhIV* zy5h#{nOM2{;uagU3+pGS7_=2JjsHf2# zxv2UfyyI$`2+f+p4=*F9qM4$$``y5iit<6iMnpFK=fVM1Bv(`Ui}!@nEHS*nN<<_{q|p#YXf=ep6Z2rGwswa?FP2<@LspmxmrsB$j`P zf&cRH|HNM{30N@U`u~id$&~Efoa8FasUPk8{D4pFJO5{~GU=H@a?z#oYqm3A|K%{i zKJX$^03lCgpmMk#rlWb~;?gnIUe*eT%WcHjL{gt?Zr)$&^kCuPRK_1A+FjefaOmb| zi|>KP9|Gc|>_0%d)$Z;##vMjX7$y0+{Oi{$cP z`&FE{Z>T@*DC%rBDVmjR_`xsZOlVK+IkxQd1AVz4rN_1v%=#U&INEeYW)HlQC!hbt zfv+V(&nVEqJEXQq1e+ApDFBX;z<1EAfACC%iKQjpf%)g<8OCon3k~(I7g;C0EMkAc z+bX8?;kbRFp;z$ODh-kXLo#X2?o{{x6b%x(xPOwWR(zwx9M~K6+}xZ=lW*6##p5Ea zfIp_R-%Z00H?|UL%zWa*0|NRRNyz5EC;-|wuUb%2E7f;bJG8qwbH}zMHPR(ox+k+) zcgXQ()rDSYXTh<5Q})h-<|k6&;4vrCsNlPQok$QcUqVez;u45EI&rpI?A9QoOU+kn z|E`-SOGM_6NgH0ZPf$JvM~wWSIBxbLIK5wd3Z~#hV4_g+E7eNnu>8PYs5Pg6p!mT! zv*hzoKrD1&}SqRqzAKyc&e-{fD_E+qbcfs z`R89PP6l<$6j&Gpmi=`6^?eFjd9`F5!SqW;p!^_~11{?TzxKV>6=u))4nDsFI0V9k z_lxC-oNJ__UPA!acVy2I?wvj&ExsPrboYXE-`y2H=`;@B&9_a5?+aTtnU*W(nys{7 z@8d!~;fNat?W;RrhlKnn;?VXWX@^tZ{DeTfdW!z2!eY5Xv`&J8NS5Nv^4Si$kIoXt zv;wEK+tks|0Q3t++Uxg;%AjBmA$79~4~^UbC}3NyiW&LaHQx%?#Qu^hWG?Y3Jjgjx zd-A2jVJDlfBYKSt@Us65__Fa03Ce*bEVPjc4#3!-${P~O^^rF+ij5>*`eIJe3aI4g zCHP$IS`_Ki8|!|wB-eWRtxK%s`U@D`MZsrnL31Av+Mph#2b0}E?kmvR-4AipAPTAm z_7Wu(xKDWQSwWee0=&P31VZz>=9h71d?i}KUp=~Z&0RjlY7`YNo(;^icvtj!`dm;N z+=6Y#Ho={~a2YXma+#iT0GMAD#FbSoLa5HxzwLV~DA*kec7$G*!F#h1X{Eso;GvM+ zl`=4kq7Is&n>0uN#`+PUKUm5t(ymmyJN~QIoxHGE8;nzKj-%W_h2WZ~XG18RinvHD zmIN~e`o&QxBPck|6k9@R$2OBT%C>JHJx7;PQE+Dw7VerBY7Pk0&6tL$Wyn-NSFzCU z%B4SO+fEGf6?Ze@4$4H`ws|R_%a)8?VElyvl;Ah`1;}+WP{O@*p{OjR9%eDvTST>N zs7(UO;CmALB-kW10ABR88MKE*Q};$ufEYOnU2{|^g&VL>!F*ZSELN4WN}2AqFB3-l z5jZD8Y$;CgAbO|U0ItTQA0m#y+kc86;z~QDFSTv}=BI6fC~Hh8C1@lagOmYhMIaKG zoCu0RcfPa94Cw^IdF@+h^%@vG1Jw3%P<=^4oufPF$rx1QM=aRWwhsvypGHwNuZ2(+ zx;0Q(+2%mPAp0k`4CcqJrA3Mk4e4cW1$=e4PdsKGK!uvKy)T96TOLcUmkkLf_#ek)`>*h zduB)0wJg6qc_NUnkQpjdF5Nm*Kd`DO2q_I>(;%f*tm+K< zq1*A;dlwvYQ!&qMJBIV#teIOIk9xT0MQb=`9)4F|O1am+0QBIE4tlZ#%+fMT zKGL!ij?-919N9;1Tgi-1%qv5BhTq z&qw^?XmaDMdkUcYwjf6l(IewRYSFPaxco=7lyyT)zT^gr z@fIsbNB?!&XF}J8d5@=X+NM)jh@;4RpHaxR8=EnW7TPKUQ`lt8spAF#E@fv`@{~SU0oH^Kmjx5^>J-VghaP>6ECwQI&?4x0Vo? zB%nvP%dktBZ53i~c*tk$#Mt|(sw?lJy(PKtKBdiUmSuP~;Z3J_ubk&SSLUXa zEtL=CI^v+Sz}F;*!3FSb zAL=3jA!raZfL{#TxP|F$5-1TpKEgTBR+jXzx%_?6x%nJtZAO!OXO^LLog#0K>+zGS zlR;jGEc$mX_upaRwvjXriVUq6Hs+?Z0W*5C)IN|JY90ivihSAy^)d# z?+|UeBi<^km}%f{qW}c4%+8Iv)~JM{2iT{IN*5MJ#8O>cqc;T9Fa z7AtPMkuNU(fA30Mc@|-pW!aG1y%F!>96$UzNua;^aW7PGqnhiCxq~~cZ97U$1>yFM z$4DC|*t@jdUTcq15*&s-FAZt343vk{l^GT^IHQl7$f@fpZLUAH>0FW5(`H)`VN3^| zds4kfw-ms>-7hU~r+`|C%SpVXgu_LBqUy@DxHSdY9fLVF*85W0*Oy6(!_TIIk26O{ zGkMEO(OS2okYrIH0FU?U1C$hC#lmquP+c%JGEz4Kcn zULairx|1WQqc3eG^R6M5=V`0*i_#eByG6r08iG?NEcPS zl~33b4M$swr8L-e-7c>yk8BjUFSTTb{H>v_hylRhtqOWeWsz6 z)yK-UJ|(b(tg!y_|g~pog>r7lx6NiQc&QYHH+61FWosZ>#3zG zT^mbt(xLrFQrIHeJ`n)zf`+${nif|59&lsH)U7)hTZ-XmzpRB1dru4198doBDj{@a zIR#U^Om(?l-XVRjV%@cY*d9Tty}t-80-GL4r40(~0ws*!%PkLe(=8=N>gUb0&j^|h z=~ei}sO#zPmNDlFPx1S$QGW0_Bnkj@I|Mz1z5k6xwLwr91f3x2Ob-|1cnCl7zUJP9 zgX@K=e!sljpmXI1Jn4O|J1#j?Yqi(urly0UJ*@j7N(R7LY(t{d4UajXux2ndF3a@3 zdtohcpt9Ylf%`cn|MTPCx+xyIE9c(XY}aHOb*yDCOStZp&4FL5`*kS-rInX#5Vr~~ zxl!4zHnL)WD#>S-?`=L+A*D&!P!pv_G=AFH|Aa1>~fA=az9Q_Gw_(p|qRL+^;p{259z9+sgZiY`xxYXc_d4!A3F7Zyz|KfcDr$f%qHg^+|feCnUwbOsH|hDP5+ENV+%I z+idC=fA-t>Hv0cYB3TQ_8tBZM0yO6>?ZjuMJSNFPCxR|l|9Z{!R6^Mf^XyO10*iEZ z*7KHfk$NIF8X%87#v!EIf$RNmDDrmanRv#9YO|Yhla?YGhQUL69T9Fk;Re&TMHcZF z;Ro<3X~8#%*pz zGg>M70x|kgOa1R0)ixmTzo9rtYH>g90eMh`no596r8eIVo*2nwANQU8WqZYuy?ZgJ zg~D?K!G6u&kG)n)G{P%79*q&9(Jeqhs!IRBWgZO9QlNlzuz~A7X@F6PJ&n$2Pbs@| zY}A-%-Ma0iU)e}hxGtMSS_am~N-NguX*WW3lhqVo#EHg15fL>EBUP-G$?-OnTc3rq zHt9u3`b|EQlZ^w@f8r%I%09mAv{qicZgXoz-XYW?du?t~pSXz7^#33GZz$H>TU$05 zPY93bt}DD4YGj38=Hbyau9#~9PY?KQJ|ugb z1tbd6J%=qKj>=I`w$#9ey-^LOvojJJ(C$&WbFEIU)m3;J$|I`$+0#Jo5J}Y9wVT&1 zmeZwM4TsZ%$8k7>#!45k@1}vrZk*3JWwE-0qvg<@OI8;&A2=W7`q?SF>b=YmF`{iU zaIKZQ`X;XLMK_3`>>{UyfIv5dY6Y|FLhk8MouEk5pTMl1y3p`LHzlL0p(`;OIegt? z0hJd98+w9XFB~~VqcFAuL}BviBMR;aQEIP;;x4WNBfYpt0~JJF_YNXYuDRy}yqNPQ zPGWP&D-^>Xn8}Pn;e1X$*DaCNxdyd_i4n^u&PN^pcuM(%gTTk~nqRD$(<%LtmQtq5 z?ITYYXTd58dzhk2VS&{5lR?lE3icDD;PaOK*RFd6%$obw3LDYAIomx}YLb5!$954* z-jbSBr5s_;0Bv#VXJ&*3Zt>j|D`CI281R(_`I~!@$)QbLLW5Q@4nE8=7F?qIWR@jc zjVM>&X@^aN-tP#bfiuJcGO++XjS<{NHu{Eu!Goztp@ZfKiJbd_ldfRm6mOr)xKVR>-H`4S4m#lzY$_Gp?$tTK`G^s2j~sH93rJ2+h&d z%N6N&r;Qz-2xuKG?&uP|`&Jo+gcW~s%9eQLzOxRW6&_@&;6qw_h|qkT0Nr*aWiWgX zZ8r)w;vmq-sD1=c4x+Rm^&mE27g^yqK=a&w4Z%F?l8{i%a&!PcOMk% z!nm8`F;|cl71i?y#bb5g>3Cu!21%nYVmZ>gJzNj(99Ve$164ATfA31D>f0p4ne`?e zv)u*W6Dgh+KabeEF!nBB)d7sag>P6}T3pbXN(%g`6FotJqt zY0dYf__HB1Hn$XoOpEyo@9a&Ly69dmxJ_GoXr-Er3Nn+dDLBLpR-xTC6e<=9X+fd) zHeMf8iGKe(k9#A(P`}V4>q)}q`L*&f583Z63zqeS6@5z^RkyzxnTyldi7Wec{lFQl z`b}_KXbD=WhVKguHeZzflG`JjJv%hOrQa3A@LC^#rm5hDd6i|%u=F_x{&xEN<>mM3 zRD}V$Yp?Uz7JIc?{9KMOREfgEgPln7kThb8ePPD_83M;b%4tqa^@f8JYhVM$kdc{s zz$g<8oWbO$#4gGS{qu>OtPg*t+~c{IS$6TreK*EvFXAm>T!zw>bKsQu&gISfvmpLx zC}v)0MIEfJ6QR3ww}kG|5JVZ5cqGI9EmGfJ3&H1ct~%d=2KAp&@}=+iiN9M-;p89E z^ceg4yr=#~OXVgSlMd<$kncC(Kp%>EPE&e?<^fyDp>78}bR(bkv|a$wHeigtKh-y! z@5@ZmGBzBymHnB>;5qJ~kr4^``M|aCM$cZP&Ne$cF_Hz*o|iWooibG<%Q7rJ_pOAh zp0+%;auizLVTpctG>5sRpOb&MZsYir|6cq3MZ}ROdBL78ymbng9nB2kXFhX=&gn3V zrQ47!sny|vbZr+!$9~x{c7Zc?n%4Dz?RQ!FCC7Hq+lo>X0ef~ZY7NE&&H_!^zOrKg zOWt8m1RH`4Nz`QR2rFIsxjUy>E5jR7Jry;b>k#Wo7x&M(zB=)eF!UtHXO< z6kH_^Nl&TFVo)&B4^AJ{%d>5QZZO$Z)%%+4TIGxGARjlHtGxQ2sdtxUrxRN5b> zA;e0QC>dMJZPZ~<7XW{M5hn@Ayx3qq@b~8txa0YR+7{N_w7_7>D*b!by z?w1J;7=5O}*}2`d>f!jT_u>QZf(`|T&u{hm81Ft^n*p9%P&f=tmVhQHnCL;l(ZgjV zNF$ufvRLMMfTvHa1IMLTRr&N4k0jPomYZ|m7Ye_;$mp>sllFZ9r0yRv#)k@;_mbPU zq}cb8S%44!=I4u5Df1EoM{cDY-Oj4Yk4zjnr}zGKneyG1fw$Vp84omt>K7?$+{oj@ z{Ub&kOAU$zJ4o03S=a%ma0&uqfBZ+EsNsS*9-1sX?Fi;&Un#npDycD`=GHc6%QvU5 z2ztL_)lo=<4WTKkvwNdVl-2*o)pf^H-M;@r$d;5)*R5h)|a~Rpmuhffpz*Zb0y?5u(>1G>=g>X^xPsg2(#IUEz#6~rd*@hW{*_aN&ZL4d;EVBOV7SY9)fM1^_+#d(gHAwyrlI(J^cZv`#o#TC~ zei`rY28?mYXs^>oRgcS_JZ&ZhoI)z)jN8;K>e zB`7=xST~ZH>e|f2i4aa>>T%mun?F-Mw;G*)@1}yS2A_KWhR{CyI;J|0#K4WpM;chO6^H%s-t z1_!W|Hs^OPhH!f;n^uoVIA`)L+Dei#2C+YEbV9s~1q*w?2>3ua%nML54v->1>l!G5 z;Vq2_W_d&|-0@1D8DZe#{GozUIO3ZNZdQJodM~5jODuXMa(l{JH!>l{J@VRwC|>oK8c2LhU)h?Ere2O)uxlroXI=S zXR-0ICYq%Y?sYPSmKQ%3o5;Txm#p_9@p=|p zs_}XKd5Wv23H15Ir0U^`Cd9ssgv~$r;4{m?@f9gQ2l7u!0S(;RgXzhE|izkg{p40<>K<>$YfieY>EZsnthRWwMY{ z%?w;=UB2dpus?l+&T8ctQ^Qo3q3H|)Q}GVk;%pA-((2jVJR0hqAFow8bZsCuCzaID zB4XuioC5W4hh5?CZ`mRAwvkF_KWFUam0f zP0iNGon{Zkx8j;4-a`_MdZFPd9-Rv2J+~RQLw3s^!4$~2gs?!+;*~(4nMK2>v48@#!urLxmhGgdjSq7j(Zl> z!7i6*a2NpgdyvH@8G3wW{rOJ2FKw^AtgaFhka(+(NE|&2=`_21WK!Q6M85iZ4GZxQ zuvk1CM)`ICW#QH{4%gLiRng+tbt4t4uUh80d3?DfAK~H1!ZDW<8mg!f#wfZ?A}D6w z9Wn$1Gwd$D299tsu-K2=LWeD*1vXIvdivH!6%R(ud6-9f6B3a1$32dz0*=g~GZDA?Qm6ZqKRkUE5YZM^3t!nvLYmly(~Bft9Er_nUI3#x%f zce}q{i(P*8WmcMlSFn<<%{?Fp=r0TXlS=$?^hfd1R1{71sbhCf_ zXc&z8T)i_pv94SFJSA$_;U^Ax5*!S{RWMkLXD`yKR?L2k&7{*dObH7}_}Y{UwH!s0!pM5A8(=nJRzV@3 zgTq)51GnJW9T{2?0}S zyII6x`{5ug4A-JcKwb2!Kg&#Csj(@2l6jCFaZPG=a#@runfkt|8rO+1p{Y9x zS}f!X`4jfB9G-1R|I^?g7Q)3sZ?eB3G4~L*4;|t3jb(K^c2c0I0=-GT-WkXeO|Rfxuh8b0VL;@LD3D32UAJ zyR!ZnBtnNj;fk9UR@`Mr)8rteKYm^d6H}!e;IK6-vi>O3Yj>8*6jtEdCU3F^a|n>2 zO#COAFfr?BpKUyos)MMXd)80{8*aLxR$DoD)q3kqy(^=|%L z8O6wLr{YI&zg9#h8>Vt5e?!ufIcVJcuYwXFWIDtIx1E9m*MwfkAG@sC&4hi8`jJ|K zKAMdj1&4>4zDFD-C9wbG9O_HzI?8YV{pX_xn`1anscB$zG%NMge3|zV0D_>A4vSfU zuJ^W|uqS98L9-BHGm&sYO0`BL?!db(5sGr&{sr(qUmO*0?5_!9mk*DG%Np{A7P)?& zx}=2dUj9%g6@F#BIfxd=+Ko2Q`@p#iNsuL!gdPshTxgx1I4QE5 z2eU_iy41OE31sm?mi#p7jLdT0hQv3Y4Ya?9i)c2rq^8htpNpzrY5r_LUkTs%_2csS z%q)7_R$;h7R7S7aj~)4jh%Q8gO)GV5AV$Kq_--uY(MoFh{^s#pr|vxvDi;(c?@NS=D4#@h$cZ0}BK5 z^DxO74(BpZH4BWkV$hEuKv%L4WMf8#W0s7sGVVOjC(5Lboo^YCi{0rXoBGc4Wq_P) zV7j54njR+l1q|*3jvfNtA>;`_*%l}U3+<=_xMKJ2crV#rRR<{86j9|o_`1IZhdu(f zN`dOwE_wkLX`)~u)tRuhN`-sLaR+f|-rP3+n)86r30=YMmO%KeFkVbF!W+MJq|c7> zJ2>Qc7PRrec!c#876=Zgbyb_)XML6}8?ek=#L1o$aMpb3p1z2B-CU0DKwA=!UY-5I zgz{}L#eFpe=JnoK%xoRf`1-yJ(Nk3j|8o?B_V*D%#fTnmZgTMY`)(5A%AcU*lUxW* zy-%UK;!WobB)d`stDE?n&a=7%_6pNq94l)(^BpwHEnY2I(k!_TiznCP z*J!aNQq!<`n+mqECA^y`%r;amLsAzJR~nvR!S1^9`LXiP!ujRbT5NVyUfFMR)t3!| zM`Z53n_PPbM+Fi7PY9S60#X>(E`lI9>EXb4P67Gm!|tB4ei}|%Wd)BbMAsh?q5O7E zVB&+rgl{=2P#5@hxPNG&50AP_$7Mdr%aBVk>}x(owH2)(%$9dmNQ!5viWRi*t`WdX zY})E^lm?*H3DXRlVRTDj`y1zWznbXDYVw~if8+3$M`B3NrCP*jkQ-SssS4fyChGm( zGMJ6FMG~jgj+_jFML86Mo8{e#;n<2<$8y}_v>vECWtk!;PN*8$F1@4M*%y`BZcJ0{ zu~VN)LRQ{y+eExwfrVrVOEB)>fzMo;d>nKb3wifKsmj??g`z&eCAxK%^WEQB+AaA^ z$5}J!%4mnhJ&WJn-Wh->?Ji5hnt3}C69nD@3^%|%$8CqiBwG;zaVa~_59e?(B(rI=Bd^ca9mn3 z>bIb4D1B9F?Mk{vEG@eIOyf{hn?|@n^nM0&#ne1lI)I$v#VcSv4(vV0RncLc*Re5_ zHJ&oHSHBMYPCcO?*?u9^& zD&dHC3xk|PqxOJG-pJ|s_qv7>=c%zLN!`NlKX~_C!TEeLBcDu<#HrFVHe^J}GH@XD z>j4;AN{fktb7!ii(tt34+;|p6=72QGK?}$JJ21(@=H%+W)O!Dw&s^dgiq9F}X2|{T zF5|m^cQy@r)B@*gPH=#o=eWTz+~8R-ws~tFo@fE^+W2}Wjx*9K zH*|PTbPHZ0W<1|=!k{hP9bgrBsQnx_F$`NM=ceP;+^|uLh4&A|^`G;!t9)M)9n!G4 z+%qLTU-#PIopQFL54jB)M_*L8H=zMGHy|6WoRf0QXa)M87DOGuE(KroKBtk_%S;qNDej+mJZO*;f^e5pEM)FKHO$jmTMRXfqx@g-WXkzK9m&V37>!EQtrOqK$Rz72Xuz)xlT+d{gm{m^YOrWgEmGSmzQE^Q^TfG?J&A5{FpZTkY@P?sy zLRt~1QzdLNVB2A9Xa~3(Nn9F0tE#sbH>+l7sl@ab@f~R2(0ymw!!+Y+s?gF+xNX0& z)dL1M|5>}lfX(@{w$-r$>MJc8&3?8`evzUwbjU&Xvd1!CCUa%SMe`56&ayPijaPlQ z!7Ci*J%HPyti6N?H;)~0B6y^Bs*C>eqp6qg@XWi@{gV-aSsMCtA!bGQt0brki_A*q zvPG$W6EM7UfDw;00-lMuF5WE>_>PuF& z{ZN~#w?%fxLdC$&2y6!*Qx2;nEU;sp(>kD|SfCDKXa{ZIn95rU-+yR)#gZaMCL{6% zd6IhF$-L0&oNbYbu$$S4PPn(#EXX^8NX(8v-7ioj0pgLiW`}U@gzwMVi_h$PUVSN? zDs`)Y6vUBScT&IDW!kUSWa)aVh!GE%J&w0f;x0eAKOQQK>_z)-VK&Qf`q(7M*5^2V zQ5HO=#V3|qQAAHaavGT~qJ7Nwgj&!(E7{<-Kbfm%`4b}hbA{f1 zHYk>VyjfIvRZ}J9d0yHHpal_}pQeKwllVV^5P2C`>Ya2t6$t%!-k#Ph&g5^ z0FmHY9^gYj+Tg>nxL6OuO@OR34g(xIbcRMAAh3ys$G$RHF)@D5}mHQ zb=!f_^yb}ha*m=;dp-*wo-`n405}4OiU6$~rj>On$C;{kA8nlQ(rdjJ-frc0>14}pB{ zp1{U45h32iCmLRNA2sM1g?N}3a4U92bmMDsYH}a*1rO?-sfP&T|6U2UOu9CN&u+7$ zvf{Tjxzk(3TC)}8k$z|PKNyN-or|bGbKa?X(dHw_fwk4+ddh`uSX+gX5sTesQGRR- z35#ObQA+3_eDcP@L36+L0-x)fhm+n+5{gVg@7bC6dm%Ih>?=iMk!b*SPT=#8q+~CD zj7o5r`u0qYnQDN4koQ5O%apae+36Is$#J(Dvfdj#>#&LoPmFp*7(H&tk+TiyV1W)k zhW+~bDs987HjPGx+88cl6&A;&^#)4ec-ngqb!nN3{k3NHbZ)(j&fsx=J2s2uk~`g4)D z0hRtC>iW0+vSHR%{`!kr z`%V%N##aJNRgi{9Dg~cM+&^8($^8*AojhAo>sz@dYOI#^l=)`Y^~C!;Iu%C?K=Ke% zbSzeS!Rp<~^QBsDVe<}7T4Nyt- z!%fQmpa_J#4NfxQQB|@IZF}=nm!J6*pZ@TG&2exeiBQKgy3s;XZtBi<4^c5B6R|z_ z1mOO)PdX+%6iK~V@$7=jp*LGB*$wYkDOd&SR8pU7^FMBADQT|MjN(RsHa0d{>V7RZvR6IlhJwA0LIf_(qEP;IKeILY{RKCt`1Y5 zTT27*(!bx=@lyLkt@DF~NKKTaVPt=d^26X!E_ua1`qQ5Dg3s<}UQ2JjB9WYlfq=*a zd@0ABkKxw^UkVf6c|{}P;)Q#Nh#=J?x)ZmR?Y*H6qQ_513VT1u#f+pe1}7s(hVw|$ z;LdUI(H_I)KS%q;UC@c0lSl7+c{jzAo4DdTMBfrN$&YMGI);Q+6{M6zBg2nIf$wAN zy6Yi^4Nhg(S^!yFm=A|xk=LSXlPU_08GMWe&t3e{b8TNHZ&$XiTX6}m$qU`KSWFfw zB;%Gpfft#CKNk;#4iYia0N&^3d!U(QcbDGIPp3%7@n-f3)6vc^S6vlV93~79DRn!B z>ct7;?>C>A@55yk|7D>p@WuNo$b8@sB$*;|CiM#EDTz&FHlEhpiDtR4{FHjlO1FD} z-RmbeNmj+f;Z`UAS{X_3%)67^XIPlK!ndncpq+oRCZ+g?98J&-CO-d*l`2E_=?UQv z7Vzs}T5|^tHxR=@#Dv~4MD=gP{fBG!UuO4OL#CcUPF)$qw-)~+HokBOzgjwcsJ*<@Y{=t zJ>MtV#Mr-wDmFIi{697T@bRS47`GJYrkx%(YMDb>zmUy<;~>L#?3ML}WVac(bRRqV z5mUhET-6WOj+-6$60`=uj4}{-xk6d?!|9a)1rmA^WWld3{Afhi?6uzZrAGD6#e2pJ zve^g;wioMYK==PoM=I0Ds&XJUIuChoY>M`!rNgC>WRCeuXp&?LH~^C^DaH_Dalxzs zR!-n!J??-F<7^gCZjc3K=_==^KdWx=#hCGXSmb2IMU32RH^~l>2QgA zxt$N+#Y9j3Y!TuPAg5{ohsdA*i@54ccoFF;_w5%2?Dz1ua?>p(rzRC#y;QH{3npK5 zH;Svw2vRq{bfHLc{eQv4#tA~0K0!37_7mN;;eKRNL>)|DP%$f--V%3p$<)xAEYpqD zu}FbFkLX6gJ_e}6jPwUL|1c7KK3$InHywR&@8fy3qX<14zO(R>gg|^LrSh zVr0`}a0vZ-;o&eA#nSPtB$eyn2U5a0)*4Iq(a%et2rY7bxyeWS!HKe@p5SE=_ zZWZ0W1bO0)HLux2OnfB}u7l31Zt{uNRDJj|*v`8}vNSaqmd8(a)U4w|{cL)uhQ=83 zn2WFZ*lSJ@ITkEn%k5RJBx97ow!--K&w{L_+;kx?iwO_j>yFCtZ zS#3PE$8rEt|B?-WDW zwKxt_wgwSS!X%;p3hgc?e2I%X6{l=Yyt&o8OHXZF{{<)iQQHvJ z_Yrz)Z|Ppr8-97fowsLE;>=j_p`Xu;eHT$!`|%+IGyGDz{y;9Q`Tuz-Tf;ci za9ct2T4oGcKtLC{eX1YVRq{t8EtcI-Z74%ZIrGr#k#~U#t@v$-0q+cZhW^IQUj^A_^_vqxR zN0la0*K0<`ULN^w2T#-Bd@eGutM&1Y1Gg_ptn%jAmkzG6k!4C`WG(1AIsj*5c>_SbtgyH zk-tYz(}je&vPpW>n2WZ{;z{2_r|E#PLE?>i2w!rn6aB-x0+2p$fVo294+lF5=IqhS z4Jwz?W!eHdsxjBjvP{TuWVA8+-MRchr5o05I{!aOR;!oDRvVM>sq5kpdwlwc#@Yj# z0g3Mhea8JfV-Zijha@)z@w?akEep{`8yNpt*y=#k-$%F+1@tU;Lo6zgj%i!C9^Z?` z#kcJn13_J8Bb=8j83$iY8+beWflG9$*;L7d}MIM$P2hUsCTfrnaB#W3v2& zx*GrNOSaj>M5pT_(-WCHq|atM0wj0DbHUG>e{Ga^3f419jCauOx3m=Q&0A-8X~)H^ zI#k^0a-3dKX$Eoje7I>Ek*#T0>Pz-V#r;QX6o3VbMp~2n*hf~~;MY;Uvs3q! zc|K}ujk}yV^1fRo8kSCt52H3CP=5mla~t4~@&~(;G1Y|qtPQk@2M|vCrmOm1<7>sM z2Z)TR@FhJlpjAoozHNZsPsouBVmV^qpD@PnM;@TztscSV=O4Yuxo#2SZVIcg9F{lA zCdge=o(*DrQEw?|CLLI-N+I+LSUY}9iJ=fE+&kL;Hv?8i5~?anyYK1;Ck$J^h;wk_ zUJaUK_==}||3HnzGdNu1%;Osc5<-UZ&#)Y;lF-S z?ahP#k|)AqnS|3~)u_Fdm!*4)0Wq`R3;x9ntM9?}dH*MGIo(`mOcQLf5u9q%A->Ov z4*K_a`%hLxosp%R?FbHxR%!ZwMhgZ~8C?J*(xfyBdGfQXbGm9dgS&6AtSCc zIj`KYjO4dglL$=vNPzyA#2<%cn#DVat+TqBJ}Wl4)KN95s8zc&4ZFEsU>7|3Ul2S#TzKdkg-g@{JZmpQsh}Xr0vGdef7WtC)^^Tb{EBU+bWiz- zxBR!&3)0izu|t~wRdr7zjL#rulV+rH6+JU1y;T5>_^Ndx47XrpR;F_eEI~ zK1a_2@(&Ye!k=&chnbGQnWA&4@2tY>uR|9bioRAt=jF(9&+s&57d4T4o=dP>wABf` zh5HLs){YBW{{Sd_r-=eGOe81HcNLuLHei40c+K$8yR2DSs3nL`-u8vL$3XsYE%ziK z0Ny&#z6h1Vu4YCYXxl~e)_}F1^ndob#HptZ^}OJ^c1o+d&ydOA!#{Jp1R zm?NhK)P)}fJO8p{VWKQf)K<9-4#~;Yk`BZySk<@2wY@6spD`QC@mU-;f!_vlRUPcE zcH+}O!N1AEf~N6yuy$VF(@?DZkc?A653JrQTQ>wdNPQJl7{Irv9mIsNJ`eiqbp^f3n|7d!GldIPUR}RlgtpEQm|JIZEO?XWN8Ib|CrsdIa&a#8znms z)>x%o(qaB6vZ)SEy^AxyF@GX?Va1gZQNl0W7=I?EJ>TPNEx>}~i}}ZDDyb~MqnULY z@5c%;UKpmv(uYp>vGz|G7}0YX8{gH-`k&nMqi_6vTN$hOC5b4b>I+njMx8&rYcxB>KuN2`#sIrdwx7+l_GbT0eoFaaTlm<4YW|S}E*)>Hi<263POrOt3jX`MQ93Kv%7(rTm=Q z(B(+4hu0W~Js8h_Dc#LKLoRe8_)|pZu{y@^7Zd!YqTt_yU7|>Z@Mn3s4wLmPqluH% z><#{eJ-Ml|j3>q{Y~?x`H`7^d$AWs(;mj3S?63UCKXrVr*>??%{F0&J&Orl{{6d^+ zgrsR_Bwq`D$2LXaoDQ3;OlHW+46In?{LAA5uS8|G4dvZc+PYjG+D#ejJg!g87TG#4 zy2m2AqFpI*_JwO;_{bs45IFW~{iDgP!X7cw?g0OJ%6$9wII?MJ;>Nrk!;WppVl(|l zMZiAwA#W`^Z%D!e*J&GVQ1@Ts0;{lvJRfDsF+F)R6ERG7MQbm2vt6k8-ge zIUGl_b?MNy&#d&>2RpxVFr6`dp!pkY`Pgiy?QgLhEX364fU-M9_Z!@b)zyfs?&sED zIi=lC+kGj@X1fV8oRbzzKgA;PfM(_A3bfSaeGaY+LSvC{fj2E++C{nLk@JTQB(`|V zyuKh>J~Z5s$ECrS)LSn9a6a`Fv3!X*;D>$h4oAWH z>!3KnB;o;wvG`TK4^N!Rh}iEnr^dNA=P~Cuo1{?_4N4?&;DC9uPI^k^~gJ z5B)Zf_+I8Dne5^QZEIY)2wOp0hyHCt3KWAKo8r_L`Rt#_V=#fIz&wHoj`RUH)EO!{(h~Jnc%$XY_`|UHUU5=G?V|zd^SxDB$nyV`2!Jb@5 z{yB&wa5M{5D=)1nie7#J1z5E@)7v*Yz4A?u!R~3NXL#F^s@zEEW+1r_JA6_X!Txcd zC*CO(yh-?=GOL`2>zke)8W;6mK~QxwYC0CFy?FXKmA%a4_v`QN#LhP~?&1j8W1j*% zw&^&2;C&jvD%@RG#I1UxbK0--`fjPFxuR#p=9uap^L9aVD4Vd@K)pECb&pUcQ&_Qq zWxdaFH!F&Rb=X~f6-Wg#cBixJeXY0}Lu*=eFZ^(Z-;iYaO=#JuJFw&MUhj7&KeJu! zLqOKx_NDQdHJIvg5bhUReb;$swh={hotDsXQ=zp}4Ng_tk&IPva4M+l#tkXi_vB17 zL*N?cvu9YAMKC3KY<&D}u+tUqKuh1w&Lt6dSS_DN|IO}Ra@pSf5!JJy&r}Eiqq~e>T z%QvoDgS?rKh911kwfcHjm&?gTAy(NjO+g;7lRpb` zY$=!RPR8^%UJ2#XNh7dqh*ZJB0O&ui`TRo&b79+u@5DEcNNYrG{$wR%EL|@cLFSO|6bXvj29nru-*!9TN=)(dgtjKN^B{N zrIsLm_#l}xH~q@(cVI1u1nFp2x>>DiPdlmHh?3`v9Y+*Gt6wYy9 z6|B&^)2k)mdvWvZHAsAi`iS%y<{Q3y3MzVx!KuzU-$gs36yV&p)|?sibQo?Sp{3_I z*GY9h?cxFo9lk!t)^})-Jm_j$LVZ@G+sJ+icl->4Q<2WdV`BJW+zUva@#3 z$INYs?>ImWHXy6K-mh>mV4VdaCUtrb$FV-6N@%z!T~uN*x3*Or{0>ce71csa+-96a z@IMt_bvF6=88=zh$62GNCUHOx9%%@kFP|Wo?}G^`;KGD!v4YIwzFu5^DOeF`(H~2n z@Sd;RZlpZ)eNSaHeSHtF-qK3$x7&S~Jpfvqk7m~yV7UL4wulpk;jO-1xIRWK|)*EZ|or4Z%14^cLc{Y+GssoBiT|?oSPxM%a>q$=v(b&1*c5Wku3)x?r9 zb+@?5uJee>PQ#ZEUfNuY{=nW6(SJou@$g#W0`k0m8_IusYj_keiY!NB!05Uu1Qw8J z-(8;h$og9)zk5b8_~YIC;dwseV@9jKA1Br-d%svW9r8p9n5H^QZPt8;ycpr}8R*`2 zLd#P|TK_D(Zl4jkaV~HTBt2Y|LbdPhg~^U zK+DfVz7psswl6x$yBHk~u@%JG`AkHg>Av4{RH?7w?AWNfowQi1o5y#UfP;5Wj4V6(TF+ zohLY<@G`3%N_7n~TcVxXXj+ZO50tvM2lJd>{ZjHy;G@<&)h;-^v9t;XQE%y1!O2A4 z6Fx~1Mbtc7rybt6+TV;ka-}d+%PBuhW_l5|&otZGALMaY{zd63+!By(ZGrbY$O+qn z;Cxm2J@h-9Y5#{|bt5rsjk~m-VNpBDJO{bZl`xJ?`CF*4>b8OF28In4**1*=jXE-C z#whRjs7j8xNeA$vaT`_e$O~m)_QlrceCip z%xI02@5FnweU*R%^DSo`RBee8N{j$rkOLyLO%^7We8y{`(KFkT`)N>W;b%8`OO&U#{oz3#)f=4@!)yE+YK=ul_ui$n|ou8Dbmb4rwZnVOG$O&124Vc+PKTx z!JHgZDg0ol6I1$Zy*QNZane!!`Sr;kEmWVhdBbcA1oWXdYDSJbIGgh)@2jP@D$qYo zR{L&T$xj(^!}`uLaz(miEkvHv6oW`pEq-J zvQGB7pL7e3RP}x)7Sm;E=&StMve4jZ=8GnEQh&ESj>$EV=Da-)`AN|9sTVrv`2jfA z-neNKro-@W=nkf>)}NW`Lzg31rpU^(+qt3yfYrMfh9Wl*-I-nXhOLG#9gS_`LRyT3 zygoB|O-62I@*XUHy1)PU{H^|{Y^vYKBq$fHtVQc`eYozZR~6FS-sVXbAo00QThcDs z_#mx^Np5iXu#dynz4T1^`AB^R*W&E&ChFEi`4i&Bwgs+faz*6LibhboXjhF{hpRU8 zl;6`}lmp-xq}hSL{exUMc-jkaFMfb}H-E7z?AXcQgg&z!_Gz>;cAFQ6_`M`~^n-Eh zSp`8^U1s>3Jf0Hy@gv$c(34&Ww5$R2+j-s3UcYwfU#~9whD*7R&<>dm0K)8I3a-uM zIDP5TpU1TBYVJUTuHSzx;9i_t8!JXLRdSbi(lDM0ud$tZRXLLNP5Eac<62Lx;wllt z_4Qoevxi9zZNb>U44I6HqbU7ZqwT~JWIgM%7gq#(8}FGd_HUB;SwFU@cD$ubt8!CV zwfJ6&S6h*QlGj_SZpsLx>(wW&p0Mkso@hn<2WSAG<~xAAUw$coWgx+GK|`6`cDX_*&AY^&QgJf6!leRVr;D=0Uik z@Ta!lAN5R>Q|+}g-Z4d9#FN~yH&7*tcLH#4pAUN+@4g<-gj;wBrY}aG1dtDkiTlCJ zw^vWgDO`X8Zz#$Yp7Q+Uz+30TBktUaFG*(A((>%pSl@G(!k1mMuFZRXj=qZD=F=6o z?HnjOCxCY@E$6IUpyfqD^RMKaK_O02^mJ`>w>TZ2zOW=AWbs zeh|XXmo;lrd zT1N4wqRA}u0u)T6-P8_HY2doZ!FnOZ<{MIC{Sfemg0j+yFG*eKUV7R46=fo5>YS&Q zWvv`FN$H6tmCtN(eN{KqzXv^S{v=HmL^E@|??B+cSSz_Lr2lwOO6+xvoy7q=hoIk$ znqN9Re&Zy5oPEjnD3_WY1C&2oJm`M0yqgAZybaoS-$3sHIjMv7Vv5a)KeVIiDhpq< zzcfx$9e5qc@Rt+I8Z(?hwAGns&)$^a zyh~1r*yza$bgGMO6S~bpCq+g$$D=&l8M7qL8zr|)+PbG1=_bBKeC>4b?fH{&E*CI! z0}>CW=t~zDw=>3B_QK;l_e)%X zTBLCk<>!~ly16FaM@Mq$nEN+Yi#)%a+k0^{tF-}{VwNV)>Qe`aE~1tb5eYGC_c2@vzy>;9douU&l*{QeH_WzP zOoPzd@Vz&?*EhxlX&dnH$>~4*EjFJbui4XA~&VzGObd4TLD!&bmP5CAL9)t zFnR#V;XJh{K!Z0PyqN+ijP(VhbplL)#Uq^UB_Fw{WGVysQXRDtR&?Iwq4;#Dr473klyL5N_D*npG z64dXUa^V#7DyC9Q!UQc@6V%5P%)wEa@TplD!LZhwfpSuaX4%X8c38S0PHwwbh z3ek16;Y9uhW=GSmR(O|h(`o*0lV3c^XU!y0rEeND8{t0?9;S^ZqwdvKhEV0XKw1~g zafZHK(NE9U0kAf+@SC~@t(LKn=aZ{JhsRTQ|wrv7+Moj0A4;k>7^;2X|B z)+w*kjYOBeDo=&?QTyfm!M7GLb+At;jsouAK-vfaX~PbleUTb3j`GTnqP$TM~ zAmwTxEWRmSU=zo=m4q0SZ=AxL&fV*>CK8j$ctr+8E4{Fytzz`e;GzQ)D<%vyc4Nm8?r?~ccQ`E z{mo;X-P=qnB*J&d9md(?JTI&ra(&PtqxB%(d3wfdf|5gHqH#xX{E{(c$i8q~^m9+~ zr`=Gtmp-bIWSoTH980Ww(5uA3BlAv(2Wj&l|7USq?AMFZWVSA+PJ=O7O^DdBWh;bdI;GNJBWY@YL&bPqWV+nvI7>yJYfF z7ixQFFc3AKlpwclCGPTAy(LOdF}`nLxfK8z1%#bz0yH}5_7%Ka;U{GqX_U=wC(V

WnuW-a|gW*evUot6IZ=HCs#VwrXbV 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 U`cr@E&-wZMe+y?<_GWk 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 diff --git a/testing/make-archives b/testing/make-archives index 1b825fe05..04b42dd9d 100755 --- a/testing/make-archives +++ b/testing/make-archives @@ -17,7 +17,7 @@ from typing import Sequence REPOS = ( ('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'), - ('ruby-build', 'https://github.com/rbenv/ruby-build', 'a5ca3e4'), + ('ruby-build', 'https://github.com/rbenv/ruby-build', '2004fd7'), ( 'ruby-download', 'https://github.com/garnieretienne/rvm-download', From 18f1cdf47078ee949c830468a189995e3ba534ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 19:50:30 +0000 Subject: [PATCH 004/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/add-trailing-comma: v2.2.2 → v2.2.3](https://github.com/asottile/add-trailing-comma/compare/v2.2.2...v2.2.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 887772b86..4d8dd1e2a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py37-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.2 + rev: v2.2.3 hooks: - id: add-trailing-comma args: [--py36-plus] From 777ffdd692b81db2432feccf8de16a6407a1d12a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 24 Apr 2022 18:04:19 -0400 Subject: [PATCH 005/342] deprecate pre-commit-validate-{config,manifest} --- .pre-commit-hooks.yaml | 4 ++-- pre_commit/clientlib.py | 30 +++++++++++------------- pre_commit/commands/validate_config.py | 16 +++++++++++++ pre_commit/commands/validate_manifest.py | 16 +++++++++++++ pre_commit/main.py | 26 ++++++++++++++++++-- tests/clientlib_test.py | 16 +++++++++++-- tests/main_test.py | 1 + 7 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 pre_commit/commands/validate_config.py create mode 100644 pre_commit/commands/validate_manifest.py diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 3d1ffbcbb..e1aaf5830 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,6 +1,6 @@ - id: validate_manifest name: validate pre-commit manifest description: This validator validates a pre-commit hooks manifest file - entry: pre-commit-validate-manifest + entry: pre-commit validate-manifest language: python - files: ^(\.pre-commit-hooks\.yaml|hooks\.yaml)$ + files: ^\.pre-commit-hooks\.yaml$ diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index bf4e2e455..9b53e8107 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -14,6 +14,8 @@ import pre_commit.constants as C from pre_commit.color import add_color_option +from pre_commit.commands.validate_config import validate_config +from pre_commit.commands.validate_manifest import validate_manifest from pre_commit.errors import FatalError from pre_commit.languages.all import all_languages from pre_commit.logging_handler import logging_handler @@ -100,14 +102,12 @@ def validate_manifest_main(argv: Sequence[str] | None = None) -> int: args = parser.parse_args(argv) with logging_handler(args.color): - ret = 0 - for filename in args.filenames: - try: - load_manifest(filename) - except InvalidManifestError as e: - print(e) - ret = 1 - return ret + logger.warning( + 'pre-commit-validate-manifest is deprecated -- ' + 'use `pre-commit validate-manifest` instead.', + ) + + return validate_manifest(args.filenames) LOCAL = 'local' @@ -409,11 +409,9 @@ def validate_config_main(argv: Sequence[str] | None = None) -> int: args = parser.parse_args(argv) with logging_handler(args.color): - ret = 0 - for filename in args.filenames: - try: - load_config(filename) - except InvalidConfigError as e: - print(e) - ret = 1 - return ret + logger.warning( + 'pre-commit-validate-config is deprecated -- ' + 'use `pre-commit validate-config` instead.', + ) + + return validate_config(args.filenames) diff --git a/pre_commit/commands/validate_config.py b/pre_commit/commands/validate_config.py new file mode 100644 index 000000000..91bb017a3 --- /dev/null +++ b/pre_commit/commands/validate_config.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pre_commit import clientlib + + +def validate_config(filenames: list[str]) -> int: + ret = 0 + + for filename in filenames: + try: + clientlib.load_config(filename) + except clientlib.InvalidConfigError as e: + print(e) + ret = 1 + + return ret diff --git a/pre_commit/commands/validate_manifest.py b/pre_commit/commands/validate_manifest.py new file mode 100644 index 000000000..372a6380f --- /dev/null +++ b/pre_commit/commands/validate_manifest.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pre_commit import clientlib + + +def validate_manifest(filenames: list[str]) -> int: + ret = 0 + + for filename in filenames: + try: + clientlib.load_manifest(filename) + except clientlib.InvalidManifestError as e: + print(e) + ret = 1 + + return ret diff --git a/pre_commit/main.py b/pre_commit/main.py index 645e97f74..6d2814b37 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -21,6 +21,8 @@ from pre_commit.commands.run import run from pre_commit.commands.sample_config import sample_config from pre_commit.commands.try_repo import try_repo +from pre_commit.commands.validate_config import validate_config +from pre_commit.commands.validate_manifest import validate_manifest from pre_commit.error_handler import error_handler from pre_commit.logging_handler import logging_handler from pre_commit.store import Store @@ -34,8 +36,10 @@ # pyvenv os.environ.pop('__PYVENV_LAUNCHER__', None) - -COMMANDS_NO_GIT = {'clean', 'gc', 'init-templatedir', 'sample-config'} +COMMANDS_NO_GIT = { + 'clean', 'gc', 'init-templatedir', 'sample-config', + 'validate-config', 'validate-manifest', +} def _add_config_option(parser: argparse.ArgumentParser) -> None: @@ -304,6 +308,20 @@ def main(argv: Sequence[str] | None = None) -> int: _add_config_option(uninstall_parser) _add_hook_type_option(uninstall_parser) + validate_config_parser = subparsers.add_parser( + 'validate-config', help='Validate .pre-commit-config.yaml files', + ) + add_color_option(validate_config_parser) + _add_config_option(validate_config_parser) + validate_config_parser.add_argument('filenames', nargs='*') + + validate_manifest_parser = subparsers.add_parser( + 'validate-manifest', help='Validate .pre-commit-hooks.yaml files', + ) + add_color_option(validate_manifest_parser) + _add_config_option(validate_manifest_parser) + validate_manifest_parser.add_argument('filenames', nargs='*') + help = subparsers.add_parser( 'help', help='Show help for a specific command.', ) @@ -378,6 +396,10 @@ def main(argv: Sequence[str] | None = None) -> int: config_file=args.config, hook_types=args.hook_types, ) + elif args.command == 'validate-config': + return validate_config(args.filenames) + elif args.command == 'validate-manifest': + return validate_manifest(args.filenames) else: raise NotImplementedError( f'Command {args.command} not implemented.', diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 3fb3af523..fb36bb55a 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -122,8 +122,8 @@ def test_validate_config_old_list_format_ok(tmpdir, cap_out): f = tmpdir.join('cfg.yaml') f.write('- {repo: meta, hooks: [{id: identity}]}') assert not validate_config_main((f.strpath,)) - start = '[WARNING] normalizing pre-commit configuration to a top-level map' - assert cap_out.get().startswith(start) + msg = '[WARNING] normalizing pre-commit configuration to a top-level map' + assert msg in cap_out.get() def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): @@ -139,6 +139,12 @@ def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): ret_val = validate_config_main((f.strpath,)) assert not ret_val assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + 'pre-commit-validate-config is deprecated -- ' + 'use `pre-commit validate-config` instead.', + ), ( 'pre_commit', logging.WARNING, @@ -162,6 +168,12 @@ def test_validate_warn_on_unknown_keys_at_top_level(tmpdir, caplog): ret_val = validate_config_main((f.strpath,)) assert not ret_val assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + 'pre-commit-validate-config is deprecated -- ' + 'use `pre-commit validate-config` instead.', + ), ( 'pre_commit', logging.WARNING, diff --git a/tests/main_test.py b/tests/main_test.py index a645300ab..a7afd6da4 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -79,6 +79,7 @@ def test_adjust_args_try_repo_repo_relative(in_git_dir): FNS = ( 'autoupdate', 'clean', 'gc', 'hook_impl', 'install', 'install_hooks', 'migrate_config', 'run', 'sample_config', 'uninstall', + 'validate_config', 'validate_manifest', ) CMDS = tuple(fn.replace('_', '-') for fn in FNS) From 3929fe4a6323e68ee5e6f9a185a18bbacfd311b9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 24 Apr 2022 19:09:05 -0400 Subject: [PATCH 006/342] upgrade CI to ubuntu-latest / windows-latest --- azure-pipelines.yml | 2 +- testing/get-swift.sh | 6 +++--- tests/repository_test.py | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index afb298289..454f6f137 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,7 +10,7 @@ resources: type: github endpoint: github name: asottile/azure-pipeline-templates - ref: refs/tags/v2.1.0 + ref: refs/tags/v2.4.1 jobs: - template: job--python-tox.yml@asottile diff --git a/testing/get-swift.sh b/testing/get-swift.sh index a05b7b9e6..b77e18c0e 100755 --- a/testing/get-swift.sh +++ b/testing/get-swift.sh @@ -3,9 +3,9 @@ set -euo pipefail . /etc/lsb-release -if [ "$DISTRIB_CODENAME" = "bionic" ]; then - SWIFT_URL='https://swift.org/builds/swift-5.1.3-release/ubuntu1804/swift-5.1.3-RELEASE/swift-5.1.3-RELEASE-ubuntu18.04.tar.gz' - SWIFT_HASH='ac82ccd773fe3d586fc340814e31e120da1ff695c6a712f6634e9cc720769610' +if [ "$DISTRIB_CODENAME" = "focal" ]; then + SWIFT_URL='https://download.swift.org/swift-5.6.1-release/ubuntu2004/swift-5.6.1-RELEASE/swift-5.6.1-RELEASE-ubuntu20.04.tar.gz' + SWIFT_HASH='2b4f22d4a8b59fe8e050f0b7f020f8d8f12553cbda56709b2340a4a3bb90cfea' else echo "unknown dist: ${DISTRIB_CODENAME}" 1>&2 exit 1 diff --git a/tests/repository_test.py b/tests/repository_test.py index cfa69c9fd..3729ab1d7 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -173,6 +173,7 @@ def test_python_venv(tempdir_factory, store): ) +@xfailif_windows # pragma: win32 no cover # no python 2 in GHA def test_switch_language_versions_doesnt_clobber(tempdir_factory, store): # We're using the python3 repo because it prints the python version path = make_repo(tempdir_factory, 'python3_hooks_repo') @@ -892,6 +893,7 @@ def test_local_python_repo(store, local_python_config): assert _norm_out(out) == b"3\n['filename']\nHello World\n" +@xfailif_windows # pragma: win32 no cover # no python2 in GHA def test_local_python_repo_python2(store, local_python_config): local_python_config['hooks'][0]['language_version'] = 'python2' hook = _get_hook(local_python_config, store, 'python3-hook') From 81129cefa550ad01d3b485f6a3015201948d33c8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 20:18:58 +0000 Subject: [PATCH 007/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.0.1 → v3.1.0](https://github.com/asottile/reorder_python_imports/compare/v3.0.1...v3.1.0) - [github.com/pre-commit/mirrors-mypy: v0.942 → v0.950](https://github.com/pre-commit/mirrors-mypy/compare/v0.942...v0.950) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d8dd1e2a..7791f7656 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.0.1 + rev: v3.1.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.942 + rev: v0.950 hooks: - id: mypy additional_dependencies: [types-all] From af467017c21d4b4e36da87a7b6e933fbc8e48cb6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 4 May 2022 11:49:47 -0700 Subject: [PATCH 008/342] add search term required input to issue template --- .github/ISSUE_TEMPLATE/bug.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 6cce5fef7..9ee61d185 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -9,6 +9,13 @@ body: [pre-commit.ci]: https://pre-commit.ci [pre-commit-ci/issues]: https://github.com/pre-commit-ci/issues + - type: input + id: version + attributes: + label: search tried in the issue tracker + placeholder: ... + validations: + required: true - type: textarea id: freeform attributes: From 96bf685380e3a89f3a03b5d542249f31a4bdfe53 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 4 May 2022 21:22:24 -0400 Subject: [PATCH 009/342] fix non-unique id --- .github/ISSUE_TEMPLATE/bug.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 9ee61d185..bfced0f29 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -10,7 +10,7 @@ body: [pre-commit.ci]: https://pre-commit.ci [pre-commit-ci/issues]: https://github.com/pre-commit-ci/issues - type: input - id: version + id: search attributes: label: search tried in the issue tracker placeholder: ... From cc9d950601cd3eba27e8395a7edcd455262705d9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 5 May 2022 06:54:43 -0700 Subject: [PATCH 010/342] v2.19.0 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd31c4b17..1b6d8b654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +2.19.0 - 2022-05-05 +=================== + +### Features +- Allow multiple outputs from `language: dotnet` hooks. + - #2332 PR by @WallucePinkham. +- Add more information to `healthy()` failure. + - #2348 PR by @asottile. +- Upgrade ruby-build. + - #2342 PR by @jalessio. +- Add `pre-commit validate-config` / `pre-commit validate-manifest` and + deprecate `pre-commit-validate-config` and `pre-commit-validate-manifest`. + - #2362 PR by @asottile. + +### Fixes +- Fix `pre-push` when pushed ref contains spaces. + - #2345 PR by @wwade. + - #2344 issue by @wwade. + +### Updating +- Change `pre-commit-validate-config` / `pre-commit-validate-manifest` to + `pre-commit validate-config` / `pre-commit validate-manifest`. + - #2362 PR by @asottile. + 2.18.1 - 2022-04-02 =================== diff --git a/setup.cfg b/setup.cfg index ca92af3e7..93a485c5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 2.18.1 +version = 2.19.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From a54391e96f27b8a75acfa14bac5dc39ea96994da Mon Sep 17 00:00:00 2001 From: Paul Gey Date: Sat, 7 May 2022 20:44:02 +0200 Subject: [PATCH 011/342] Force gem installation into `GEM_HOME` When `--user-install` is set in the gemrc config file, `gem` ignores `GEM_HOME`. `--no-user-install` prevents this behaviour. --- pre_commit/languages/ruby.py | 1 + tests/repository_test.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 6c5cff280..8955dd011 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -138,6 +138,7 @@ def install_environment( ( 'gem', 'install', '--no-document', '--no-format-executable', + '--no-user-install', *prefix.star('.gem'), *additional_dependencies, ), ) diff --git a/tests/repository_test.py b/tests/repository_test.py index 3729ab1d7..11d452ca4 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -332,6 +332,13 @@ def test_run_a_ruby_hook(tempdir_factory, store): ) +def test_run_a_ruby_hook_with_user_install_set(tempdir_factory, store, tmpdir): + gemrc = tmpdir.join('gemrc') + gemrc.write('gem: --user-install\n') + with envcontext((('GEMRC', str(gemrc)),)): + test_run_a_ruby_hook(tempdir_factory, store) + + @xfailif_windows # pragma: win32 no cover def test_run_versioned_ruby_hook(tempdir_factory, store): _test_hook_repo( From 323fd0d18819322359c7479c31f8921f96fac995 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 20:28:33 +0000 Subject: [PATCH 012/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7791f7656..c4cf5b46e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.0 + rev: v2.32.1 hooks: - id: pyupgrade args: [--py37-plus] From a84136d070d7f010ad187f808bac2d57bf822506 Mon Sep 17 00:00:00 2001 From: "Gaige B. Paulsen" Date: Sat, 14 May 2022 09:15:03 +0000 Subject: [PATCH 013/342] Switch pty use to fix solaris Use the child instead of parent fd when manipulating pty for color. --- pre_commit/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pre_commit/util.py b/pre_commit/util.py index 40c53e515..8c296f4d8 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -168,10 +168,10 @@ def __enter__(self) -> Pty: self.r, self.w = openpty() # tty flags normally change \n to \r\n - attrs = termios.tcgetattr(self.r) + attrs = termios.tcgetattr(self.w) assert isinstance(attrs[1], int) attrs[1] &= ~(termios.ONLCR | termios.OPOST) - termios.tcsetattr(self.r, termios.TCSANOW, attrs) + termios.tcsetattr(self.w, termios.TCSANOW, attrs) return self From 34e97023f4b0383abec38d484631daef80d96a6c Mon Sep 17 00:00:00 2001 From: "Gaige B. Paulsen" Date: Sat, 14 May 2022 08:28:08 -0400 Subject: [PATCH 014/342] force default branch name for tests --- testing/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/fixtures.py b/testing/fixtures.py index ef5a04185..5182a083e 100644 --- a/testing/fixtures.py +++ b/testing/fixtures.py @@ -38,7 +38,7 @@ def copy_tree_to_path(src_dir, dest_dir): def git_dir(tempdir_factory): path = tempdir_factory.get() - cmd_output('git', 'init', path) + cmd_output('git', '-c', 'init.defaultBranch=master', 'init', path) return path From fb0ccf3546a9cb34ec3692e403270feb6d6033a2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 26 May 2022 09:43:30 -0400 Subject: [PATCH 015/342] correct one slight inaccuracy in language docs --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa1678ca1..310c17ee8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ to implement. The current implemented languages are at varying levels: - 3rd class - pre-commit requires the user to install both the tool and the language globally (current examples: script, system) -"third class" is usually the easiest to implement first and is perfectly +"second class" is usually the easiest to implement first and is perfectly acceptable. Ideally the language works on the supported platforms for pre-commit (linux, From 50589386af364c9d3f2c46f5f6e328ee9a500c24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 May 2022 20:32:51 +0000 Subject: [PATCH 016/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.950 → v0.960](https://github.com/pre-commit/mirrors-mypy/compare/v0.950...v0.960) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4cf5b46e..e50b1b956 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.950 + rev: v0.960 hooks: - id: mypy additional_dependencies: [types-all] From 702ebf402cac8f1914b689ea198bcf3d68422d03 Mon Sep 17 00:00:00 2001 From: Matt Whitaker Date: Fri, 27 May 2022 17:03:21 +0100 Subject: [PATCH 017/342] Expose prepare-commit-msg arguments as environment vars --- pre_commit/commands/hook_impl.py | 18 +++++++++++++++- pre_commit/commands/run.py | 10 +++++++++ pre_commit/main.py | 14 +++++++++++++ testing/util.py | 4 ++++ tests/commands/hook_impl_test.py | 36 ++++++++++++++++++++++++++++++++ tests/commands/run_test.py | 7 ++++++- 6 files changed, 87 insertions(+), 2 deletions(-) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index f315c04de..f5995e9ad 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -76,6 +76,8 @@ def _ns( remote_name: str | None = None, remote_url: str | None = None, commit_msg_filename: str | None = None, + prepare_commit_message_source: str | None = None, + commit_object_name: str | None = None, checkout_type: str | None = None, is_squash_merge: str | None = None, rewrite_command: str | None = None, @@ -90,6 +92,8 @@ def _ns( remote_name=remote_name, remote_url=remote_url, commit_msg_filename=commit_msg_filename, + prepare_commit_message_source=prepare_commit_message_source, + commit_object_name=commit_object_name, all_files=all_files, checkout_type=checkout_type, is_squash_merge=is_squash_merge, @@ -202,8 +206,20 @@ def _run_ns( _check_args_length(hook_type, args) if hook_type == 'pre-push': return _pre_push_ns(color, args, stdin) - elif hook_type in {'commit-msg', 'prepare-commit-msg'}: + elif hook_type in 'commit-msg': return _ns(hook_type, color, commit_msg_filename=args[0]) + elif hook_type == 'prepare-commit-msg' and len(args) == 1: + return _ns(hook_type, color, commit_msg_filename=args[0]) + elif hook_type == 'prepare-commit-msg' and len(args) == 2: + return _ns( + hook_type, color, commit_msg_filename=args[0], + prepare_commit_message_source=args[1], + ) + elif hook_type == 'prepare-commit-msg' and len(args) == 3: + return _ns( + hook_type, color, commit_msg_filename=args[0], + prepare_commit_message_source=args[1], commit_object_name=args[2], + ) elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}: return _ns(hook_type, color) elif hook_type == 'post-checkout': diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 37f989b57..ad3d766ef 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -361,6 +361,16 @@ def run( ): return 0 + # Expose prepare_commit_message_source / commit_object_name + # as environment variables for the hooks + if args.prepare_commit_message_source: + environ['PRE_COMMIT_COMMIT_MSG_SOURCE'] = ( + args.prepare_commit_message_source + ) + + if args.commit_object_name: + environ['PRE_COMMIT_COMMIT_OBJECT_NAME'] = args.commit_object_name + # Expose from-ref / to-ref as environment variables for hooks to consume if args.from_ref and args.to_ref: # legacy names diff --git a/pre_commit/main.py b/pre_commit/main.py index 6d2814b37..41278ca98 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -107,6 +107,20 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: '--commit-msg-filename', help='Filename to check when running during `commit-msg`', ) + parser.add_argument( + '--prepare-commit-message-source', + help=( + 'Source of the commit message ' + '(typically the second argument to .git/hooks/prepare-commit-msg)' + ), + ) + parser.add_argument( + '--commit-object-name', + help=( + 'Commit object name ' + '(typically the third argument to .git/hooks/prepare-commit-msg)' + ), + ) parser.add_argument( '--remote-name', help='Remote name used by `git push`.', ) diff --git a/testing/util.py b/testing/util.py index 0dd178406..e807f0482 100644 --- a/testing/util.py +++ b/testing/util.py @@ -76,6 +76,8 @@ def run_opts( hook_stage='commit', show_diff_on_failure=False, commit_msg_filename='', + prepare_commit_message_source='', + commit_object_name='', checkout_type='', is_squash_merge='', rewrite_command='', @@ -97,6 +99,8 @@ def run_opts( hook_stage=hook_stage, show_diff_on_failure=show_diff_on_failure, commit_msg_filename=commit_msg_filename, + prepare_commit_message_source=prepare_commit_message_source, + commit_object_name=commit_object_name, checkout_type=checkout_type, is_squash_merge=is_squash_merge, rewrite_command=rewrite_command, diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py index 3e20874e3..aa321dabc 100644 --- a/tests/commands/hook_impl_test.py +++ b/tests/commands/hook_impl_test.py @@ -154,6 +154,42 @@ def test_run_ns_commit_msg(): assert ns.commit_msg_filename == '.git/COMMIT_MSG' +def test_run_ns_prepare_commit_msg_one_arg(): + ns = hook_impl._run_ns( + 'prepare-commit-msg', False, + ('.git/COMMIT_MSG',), b'', + ) + assert ns is not None + assert ns.hook_stage == 'prepare-commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + + +def test_run_ns_prepare_commit_msg_two_arg(): + ns = hook_impl._run_ns( + 'prepare-commit-msg', False, + ('.git/COMMIT_MSG', 'message'), b'', + ) + assert ns is not None + assert ns.hook_stage == 'prepare-commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + assert ns.prepare_commit_message_source == 'message' + + +def test_run_ns_prepare_commit_msg_three_arg(): + ns = hook_impl._run_ns( + 'prepare-commit-msg', False, + ('.git/COMMIT_MSG', 'message', 'HEAD'), b'', + ) + assert ns is not None + assert ns.hook_stage == 'prepare-commit-msg' + assert ns.color is False + assert ns.commit_msg_filename == '.git/COMMIT_MSG' + assert ns.prepare_commit_message_source == 'message' + assert ns.commit_object_name == 'HEAD' + + def test_run_ns_post_commit(): ns = hook_impl._run_ns('post-commit', True, (), b'') assert ns is not None diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 085b063f3..2634c0c5e 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -810,7 +810,12 @@ def test_prepare_commit_msg_hook(cap_out, store, prepare_commit_msg_repo): cap_out, store, prepare_commit_msg_repo, - {'hook_stage': 'prepare-commit-msg', 'commit_msg_filename': filename}, + { + 'hook_stage': 'prepare-commit-msg', + 'commit_msg_filename': filename, + 'prepare_commit_message_source': 'commit', + 'commit_object_name': 'HEAD', + }, expected_outputs=[b'Add "Signed off by:"', b'Passed'], expected_ret=0, stage=False, From efc1d059fa3f0b650810ccebd6aa306c7df5a7f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 21:57:06 +0000 Subject: [PATCH 018/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/asottile/pyupgrade: v2.32.1 → v2.34.0](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.34.0) - [github.com/pre-commit/mirrors-mypy: v0.960 → v0.961](https://github.com/pre-commit/mirrors-mypy/compare/v0.960...v0.961) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e50b1b956..1bdabc1ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v2.34.0 hooks: - id: pyupgrade args: [--py37-plus] @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.960 + rev: v0.961 hooks: - id: mypy additional_dependencies: [types-all] From 53643def070f8b106d08727b35fd9b7dc4a7b1a7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Jun 2022 15:56:50 -0700 Subject: [PATCH 019/342] remove unused --config options from commands which don't use it --- pre_commit/main.py | 83 ++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/pre_commit/main.py b/pre_commit/main.py index 41278ca98..b4fa96617 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -181,11 +181,15 @@ def main(argv: Sequence[str] | None = None) -> int: subparsers = parser.add_subparsers(dest='command') - autoupdate_parser = subparsers.add_parser( + def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: + parser = subparsers.add_parser(name, help=help) + add_color_option(parser) + return parser + + autoupdate_parser = _add_cmd( 'autoupdate', help="Auto-update pre-commit config to the latest repos' versions.", ) - add_color_option(autoupdate_parser) _add_config_option(autoupdate_parser) autoupdate_parser.add_argument( '--bleeding-edge', action='store_true', @@ -203,34 +207,17 @@ def main(argv: Sequence[str] | None = None) -> int: help='Only update this repository -- may be specified multiple times.', ) - clean_parser = subparsers.add_parser( - 'clean', help='Clean out pre-commit files.', - ) - add_color_option(clean_parser) - _add_config_option(clean_parser) - - hook_impl_parser = subparsers.add_parser('hook-impl') - add_color_option(hook_impl_parser) - _add_config_option(hook_impl_parser) - hook_impl_parser.add_argument('--hook-type') - hook_impl_parser.add_argument('--hook-dir') - hook_impl_parser.add_argument( - '--skip-on-missing-config', action='store_true', - ) - hook_impl_parser.add_argument(dest='rest', nargs=argparse.REMAINDER) + _add_cmd('clean', help='Clean out pre-commit files.') - gc_parser = subparsers.add_parser('gc', help='Clean unused cached repos.') - add_color_option(gc_parser) - _add_config_option(gc_parser) + _add_cmd('gc', help='Clean unused cached repos.') - init_templatedir_parser = subparsers.add_parser( + init_templatedir_parser = _add_cmd( 'init-templatedir', help=( 'Install hook script in a directory intended for use with ' '`git config init.templateDir`.' ), ) - add_color_option(init_templatedir_parser) _add_config_option(init_templatedir_parser) init_templatedir_parser.add_argument( 'directory', help='The directory in which to write the hook script.', @@ -243,10 +230,7 @@ def main(argv: Sequence[str] | None = None) -> int: ) _add_hook_type_option(init_templatedir_parser) - install_parser = subparsers.add_parser( - 'install', help='Install the pre-commit script.', - ) - add_color_option(install_parser) + install_parser = _add_cmd('install', help='Install the pre-commit script.') _add_config_option(install_parser) install_parser.add_argument( '-f', '--overwrite', action='store_true', @@ -268,7 +252,7 @@ def main(argv: Sequence[str] | None = None) -> int: ), ) - install_hooks_parser = subparsers.add_parser( + install_hooks_parser = _add_cmd( 'install-hooks', help=( 'Install hook environments for all environments in the config ' @@ -276,32 +260,24 @@ def main(argv: Sequence[str] | None = None) -> int: 'useful.' ), ) - add_color_option(install_hooks_parser) _add_config_option(install_hooks_parser) - migrate_config_parser = subparsers.add_parser( + migrate_config_parser = _add_cmd( 'migrate-config', help='Migrate list configuration to new map configuration.', ) - add_color_option(migrate_config_parser) _add_config_option(migrate_config_parser) - run_parser = subparsers.add_parser('run', help='Run hooks.') - add_color_option(run_parser) + run_parser = _add_cmd('run', help='Run hooks.') _add_config_option(run_parser) _add_run_options(run_parser) - sample_config_parser = subparsers.add_parser( - 'sample-config', help=f'Produce a sample {C.CONFIG_FILE} file', - ) - add_color_option(sample_config_parser) - _add_config_option(sample_config_parser) + _add_cmd('sample-config', help=f'Produce a sample {C.CONFIG_FILE} file') - try_repo_parser = subparsers.add_parser( + try_repo_parser = _add_cmd( 'try-repo', help='Try the hooks in a repository, useful for developing new hooks.', ) - add_color_option(try_repo_parser) _add_config_option(try_repo_parser) try_repo_parser.add_argument( 'repo', help='Repository to source hooks from.', @@ -315,32 +291,39 @@ def main(argv: Sequence[str] | None = None) -> int: ) _add_run_options(try_repo_parser) - uninstall_parser = subparsers.add_parser( + uninstall_parser = _add_cmd( 'uninstall', help='Uninstall the pre-commit script.', ) - add_color_option(uninstall_parser) _add_config_option(uninstall_parser) _add_hook_type_option(uninstall_parser) - validate_config_parser = subparsers.add_parser( + validate_config_parser = _add_cmd( 'validate-config', help='Validate .pre-commit-config.yaml files', ) - add_color_option(validate_config_parser) - _add_config_option(validate_config_parser) validate_config_parser.add_argument('filenames', nargs='*') - validate_manifest_parser = subparsers.add_parser( + validate_manifest_parser = _add_cmd( 'validate-manifest', help='Validate .pre-commit-hooks.yaml files', ) - add_color_option(validate_manifest_parser) - _add_config_option(validate_manifest_parser) validate_manifest_parser.add_argument('filenames', nargs='*') + # does not use `_add_cmd` because it doesn't use `--color` help = subparsers.add_parser( 'help', help='Show help for a specific command.', ) help.add_argument('help_cmd', nargs='?', help='Command to show help for.') + # not intended for users to call this directly + hook_impl_parser = subparsers.add_parser('hook-impl') + add_color_option(hook_impl_parser) + _add_config_option(hook_impl_parser) + hook_impl_parser.add_argument('--hook-type') + hook_impl_parser.add_argument('--hook-dir') + hook_impl_parser.add_argument( + '--skip-on-missing-config', action='store_true', + ) + hook_impl_parser.add_argument(dest='rest', nargs=argparse.REMAINDER) + # argparse doesn't really provide a way to use a `default` subparser if len(argv) == 0: argv = ['run'] @@ -354,11 +337,11 @@ def main(argv: Sequence[str] | None = None) -> int: with error_handler(), logging_handler(args.color): git.check_for_cygwin_mismatch() + store = Store() + if args.command not in COMMANDS_NO_GIT: _adjust_args_and_chdir(args) - - store = Store() - store.mark_config_used(args.config) + store.mark_config_used(args.config) if args.command == 'autoupdate': return autoupdate( From d8b59300ce44918bcfa070eff06d2f861222c3fa Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 4 Jul 2022 17:57:38 -0400 Subject: [PATCH 020/342] remove imports from TYPE_CHECKING (py37+) Committed via https://github.com/asottile/all-repos --- pre_commit/languages/helpers.py | 5 +---- pre_commit/parse_shebang.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 05a71651e..0be08b54b 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -5,9 +5,9 @@ import random import re from typing import Any +from typing import NoReturn from typing import overload from typing import Sequence -from typing import TYPE_CHECKING import pre_commit.constants as C from pre_commit import parse_shebang @@ -16,9 +16,6 @@ from pre_commit.util import cmd_output_b from pre_commit.xargs import xargs -if TYPE_CHECKING: - from typing import NoReturn - FIXED_RANDOM_SEED = 1542676187 SHIMS_RE = re.compile(r'[/\\]shims[/\\]') diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py index 3fd3129f1..3ac933c09 100644 --- a/pre_commit/parse_shebang.py +++ b/pre_commit/parse_shebang.py @@ -2,13 +2,10 @@ import os.path from typing import Mapping -from typing import TYPE_CHECKING +from typing import NoReturn from identify.identify import parse_shebang_from_file -if TYPE_CHECKING: - from typing import NoReturn - class ExecutableNotFoundError(OSError): def to_output(self) -> tuple[int, bytes, None]: From 3ebd101eb5e450c9e9d2345ffe72c78838fb2316 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Jul 2022 21:58:49 +0000 Subject: [PATCH 021/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.1.0 → v3.3.0](https://github.com/asottile/reorder_python_imports/compare/v3.1.0...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bdabc1ce..94a35a762 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.1.0 + rev: v3.3.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) From 901e831313e897e3b9313f5efbb5ef589b3e279c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Sun, 10 Jul 2022 02:03:56 +0200 Subject: [PATCH 022/342] Tests: Adjust traceback regexes to allow Python 3.11+ ^^^^^^^ Fixes https://github.com/pre-commit/pre-commit/issues/2451 --- tests/error_handler_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py index 31c71d287..47e2afaa4 100644 --- a/tests/error_handler_test.py +++ b/tests/error_handler_test.py @@ -45,9 +45,11 @@ def test_error_handler_fatal_error(mocked_log_and_exit): r'Traceback \(most recent call last\):\n' r' File ".+pre_commit.error_handler.py", line \d+, in error_handler\n' r' yield\n' + r'( \^\^\^\^\^\n)?' r' File ".+tests.error_handler_test.py", line \d+, ' r'in test_error_handler_fatal_error\n' r' raise exc\n' + r'( \^\^\^\^\^\^\^\^\^\n)?' r'(pre_commit\.errors\.)?FatalError: just a test\n', ) pattern.assert_matches(mocked_log_and_exit.call_args[0][3]) @@ -69,9 +71,11 @@ def test_error_handler_uncaught_error(mocked_log_and_exit): r'Traceback \(most recent call last\):\n' r' File ".+pre_commit.error_handler.py", line \d+, in error_handler\n' r' yield\n' + r'( \^\^\^\^\^\n)?' r' File ".+tests.error_handler_test.py", line \d+, ' r'in test_error_handler_uncaught_error\n' r' raise exc\n' + r'( \^\^\^\^\^\^\^\^\^\n)?' r'ValueError: another test\n', ) pattern.assert_matches(mocked_log_and_exit.call_args[0][3]) @@ -93,9 +97,11 @@ def test_error_handler_keyboardinterrupt(mocked_log_and_exit): r'Traceback \(most recent call last\):\n' r' File ".+pre_commit.error_handler.py", line \d+, in error_handler\n' r' yield\n' + r'( \^\^\^\^\^\n)?' r' File ".+tests.error_handler_test.py", line \d+, ' r'in test_error_handler_keyboardinterrupt\n' r' raise exc\n' + r'( \^\^\^\^\^\^\^\^\^\n)?' r'KeyboardInterrupt\n', ) pattern.assert_matches(mocked_log_and_exit.call_args[0][3]) From ebce88c13d09000f6d1c04a6232ad14fe9c5e33d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 10 Jul 2022 14:20:14 -0400 Subject: [PATCH 023/342] remove warnings checks this wasn't all that useful -- and most of it was for checking python 2 things --- tests/conftest.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b68a1d00c..40c0c0500 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,24 +21,6 @@ from testing.util import git_commit -@pytest.fixture(autouse=True) -def no_warnings(recwarn): - yield - warnings = [] - for warning in recwarn: # pragma: no cover - message = str(warning.message) - # ImportWarning: Not importing directory '...' missing __init__(.py) - if not ( - isinstance(warning.message, ImportWarning) and - message.startswith('Not importing directory ') and - ' missing __init__' in message - ): - warnings.append( - f'{warning.filename}:{warning.lineno} {message}', - ) - assert not warnings - - @pytest.fixture def tempdir_factory(tmpdir): class TmpdirFactory: From 78a2d867feac2c1602a608c1fa4eeecb2f8bb415 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 10 Jul 2022 20:55:02 -0400 Subject: [PATCH 024/342] v2.20.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b6d8b654..03a7c8006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +2.20.0 - 2022-07-10 +=================== + +### Features +- Expose `source` and `object-name` (positional args) of `prepare-commit-msg` + hook as `PRE_COMMIT_COMIT_MSG_SOURCE` and `PRE_COMMIT_COMMIT_OBJECT_NAME`. + - #2407 PR by @M-Whitaker. + - #2406 issue by @M-Whitaker. + +### Fixes +- Fix `language: ruby` installs when `--user-install` is set in gemrc. + - #2394 PR by @narpfel. + - #2393 issue by @narpfel. +- Adjust pty setup for solaris. + - #2390 PR by @gaige. + - #2389 issue by @gaige. +- Remove unused `--config` option from `gc`, `sample-config`, + `validate-config`, `validate-manifest` sub-commands. + - #2429 PR by @asottile. + 2.19.0 - 2022-05-05 =================== diff --git a/setup.cfg b/setup.cfg index 93a485c5a..ae214f657 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 2.19.0 +version = 2.20.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 0cef48edbfac863771aad34e43637e05fd57e56a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 21:25:15 +0000 Subject: [PATCH 025/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.3.0 → v3.8.1](https://github.com/asottile/reorder_python_imports/compare/v3.3.0...v3.8.1) - [github.com/asottile/pyupgrade: v2.34.0 → v2.37.1](https://github.com/asottile/pyupgrade/compare/v2.34.0...v2.37.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 94a35a762..511ffd525 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.3.0 + rev: v3.8.1 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v2.37.1 hooks: - id: pyupgrade args: [--py37-plus] From db51d3009f5cbeee6aafdc3e7c0cbbd2627a1a78 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 12 Jul 2022 14:08:57 -0400 Subject: [PATCH 026/342] adjust relative --commit-msg-filename if in subdir --- pre_commit/main.py | 8 +++++++ tests/main_test.py | 55 +++++++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/pre_commit/main.py b/pre_commit/main.py index b4fa96617..3915993ff 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -155,6 +155,10 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None: args.config = os.path.abspath(args.config) if args.command in {'run', 'try-repo'}: args.files = [os.path.abspath(filename) for filename in args.files] + if args.commit_msg_filename is not None: + args.commit_msg_filename = os.path.abspath( + args.commit_msg_filename, + ) if args.command == 'try-repo' and os.path.exists(args.repo): args.repo = os.path.abspath(args.repo) @@ -164,6 +168,10 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None: args.config = os.path.relpath(args.config) if args.command in {'run', 'try-repo'}: args.files = [os.path.relpath(filename) for filename in args.files] + if args.commit_msg_filename is not None: + args.commit_msg_filename = os.path.relpath( + args.commit_msg_filename, + ) if args.command == 'try-repo' and os.path.exists(args.repo): args.repo = os.path.relpath(args.repo) diff --git a/tests/main_test.py b/tests/main_test.py index a7afd6da4..511592622 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -17,6 +17,8 @@ def _args(**kwargs): kwargs.setdefault('command', 'help') kwargs.setdefault('config', C.CONFIG_FILE) + if kwargs['command'] in {'run', 'try-repo'}: + kwargs.setdefault('commit_msg_filename', None) return argparse.Namespace(**kwargs) @@ -35,13 +37,24 @@ def test_adjust_args_and_chdir_noop(in_git_dir): def test_adjust_args_and_chdir_relative_things(in_git_dir): in_git_dir.join('foo/cfg.yaml').ensure() - in_git_dir.join('foo').chdir() - - args = _args(command='run', files=['f1', 'f2'], config='cfg.yaml') - main._adjust_args_and_chdir(args) - assert os.getcwd() == in_git_dir - assert args.config == os.path.join('foo', 'cfg.yaml') - assert args.files == [os.path.join('foo', 'f1'), os.path.join('foo', 'f2')] + with in_git_dir.join('foo').as_cwd(): + args = _args(command='run', files=['f1', 'f2'], config='cfg.yaml') + main._adjust_args_and_chdir(args) + assert os.getcwd() == in_git_dir + assert args.config == os.path.join('foo', 'cfg.yaml') + assert args.files == [ + os.path.join('foo', 'f1'), + os.path.join('foo', 'f2'), + ] + + +def test_adjust_args_and_chdir_relative_commit_msg(in_git_dir): + in_git_dir.join('foo/cfg.yaml').ensure() + with in_git_dir.join('foo').as_cwd(): + args = _args(command='run', files=[], commit_msg_filename='t.txt') + main._adjust_args_and_chdir(args) + assert os.getcwd() == in_git_dir + assert args.commit_msg_filename == os.path.join('foo', 't.txt') @pytest.mark.skipif(os.name != 'nt', reason='windows feature') @@ -56,24 +69,22 @@ def test_install_on_subst(in_git_dir, store): # pragma: posix no cover def test_adjust_args_and_chdir_non_relative_config(in_git_dir): - in_git_dir.join('foo').ensure_dir().chdir() - - args = _args() - main._adjust_args_and_chdir(args) - assert os.getcwd() == in_git_dir - assert args.config == C.CONFIG_FILE + with in_git_dir.join('foo').ensure_dir().as_cwd(): + args = _args() + main._adjust_args_and_chdir(args) + assert os.getcwd() == in_git_dir + assert args.config == C.CONFIG_FILE def test_adjust_args_try_repo_repo_relative(in_git_dir): - in_git_dir.join('foo').ensure_dir().chdir() - - args = _args(command='try-repo', repo='../foo', files=[]) - assert args.repo is not None - assert os.path.exists(args.repo) - main._adjust_args_and_chdir(args) - assert os.getcwd() == in_git_dir - assert os.path.exists(args.repo) - assert args.repo == 'foo' + with in_git_dir.join('foo').ensure_dir().as_cwd(): + args = _args(command='try-repo', repo='../foo', files=[]) + assert args.repo is not None + assert os.path.exists(args.repo) + main._adjust_args_and_chdir(args) + assert os.getcwd() == in_git_dir + assert os.path.exists(args.repo) + assert args.repo == 'foo' FNS = ( From 7c14405f8bc2e11a80ff7397e86177081fa1ea65 Mon Sep 17 00:00:00 2001 From: Lorenz Walthert Date: Tue, 12 Jul 2022 22:44:31 +0100 Subject: [PATCH 027/342] just bump failing CI From a568f3c818eea994ac22ebe3fb3f4aec7886a26f Mon Sep 17 00:00:00 2001 From: Lorenz Walthert Date: Tue, 12 Jul 2022 22:47:19 +0100 Subject: [PATCH 028/342] enforce binary installs also for dependencies of R packages Similar problem seems to be found in https://github.com/r-lib/devtools/issues/1724 --- pre_commit/languages/r.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 40a001dbf..22b5f2530 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -158,7 +158,7 @@ def _inline_r_setup(code: str) -> str: only be configured via R options once R has started. These are set here. """ with_option = f"""\ - options(install.packages.compile.from.source = "never") + options(install.packages.compile.from.source = "never", pkgType = "binary") {code} """ return with_option From a8bfaab0913901cbb9c7b7be3210a76c14478538 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 21:46:05 +0000 Subject: [PATCH 029/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v1.20.1 → v1.20.2](https://github.com/asottile/setup-cfg-fmt/compare/v1.20.1...v1.20.2) - [github.com/asottile/reorder_python_imports: v3.8.1 → v3.8.2](https://github.com/asottile/reorder_python_imports/compare/v3.8.1...v3.8.2) - [github.com/asottile/pyupgrade: v2.37.1 → v2.37.2](https://github.com/asottile/pyupgrade/compare/v2.37.1...v2.37.2) - [github.com/pre-commit/mirrors-mypy: v0.961 → v0.971](https://github.com/pre-commit/mirrors-mypy/compare/v0.961...v0.971) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 511ffd525..01c8c8449 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,11 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.1 + rev: v1.20.2 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.1 + rev: v3.8.2 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.1 + rev: v2.37.2 hooks: - id: pyupgrade args: [--py37-plus] @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 + rev: v0.971 hooks: - id: mypy additional_dependencies: [types-all] From f4e658fc6e84fd4578833de58e2701fcb1b543ee Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 25 Jul 2022 19:29:09 -0400 Subject: [PATCH 030/342] require a version of virtualenv which is less broken in 3.10+ --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ae214f657..f86b31439 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,7 @@ install_requires = nodeenv>=0.11.1 pyyaml>=5.1 toml - virtualenv>=20.0.8 + virtualenv>=20.10.0 importlib-metadata;python_version<"3.8" python_requires = >=3.7 From 3e920b5ba763cd22caf4b58a7a37be23ae476076 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 22:40:58 +0000 Subject: [PATCH 031/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v1.20.2 → v2.0.0](https://github.com/asottile/setup-cfg-fmt/compare/v1.20.2...v2.0.0) - [github.com/asottile/pyupgrade: v2.37.2 → v2.37.3](https://github.com/asottile/pyupgrade/compare/v2.37.2...v2.37.3) - [github.com/PyCQA/flake8: 4.0.1 → 5.0.2](https://github.com/PyCQA/flake8/compare/4.0.1...5.0.2) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01c8c8449..cd411e5de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.2 + rev: v2.0.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.2 + rev: v2.37.3 hooks: - id: pyupgrade args: [--py37-plus] @@ -34,7 +34,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 5.0.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From d4b73c9e889806a9ec50b401876602d8a3517113 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 22:41:21 +0000 Subject: [PATCH 032/342] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.cfg | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index f86b31439..afe56848d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,10 +13,6 @@ classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy From 317c9e037a185c2c30e1e1220744f0f9bb3fc025 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Aug 2022 22:24:53 +0000 Subject: [PATCH 033/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 5.0.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/5.0.2...5.0.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cd411e5de..7fca524c6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 5.0.2 + rev: 5.0.4 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From 587c6b97e77fea48a8bb88dce6aa9a0a6687a4fa Mon Sep 17 00:00:00 2001 From: Mark Korondi Date: Wed, 10 Aug 2022 17:04:05 +0200 Subject: [PATCH 034/342] respect aliases in SKIP when installing environments --- pre_commit/commands/run.py | 6 +++++- tests/commands/run_test.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index ad3d766ef..8d11882c0 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -420,7 +420,11 @@ def run( return 1 skips = _get_skips(environ) - to_install = [hook for hook in hooks if hook.id not in skips] + to_install = [ + hook + for hook in hooks + if hook.id not in skips and hook.alias not in skips + ] install_hook_envs(to_install, store) return _run_hooks(config, hooks, skips, args) diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 2634c0c5e..3ae3b537c 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -635,6 +635,32 @@ def test_skip_bypasses_installation(cap_out, store, repo_with_passing_hook): assert ret == 0 +def test_skip_alias_bypasses_installation( + cap_out, store, repo_with_passing_hook, +): + config = { + 'repo': 'local', + 'hooks': [ + { + 'id': 'skipme', + 'name': 'skipme-1', + 'alias': 'skipme-1', + 'entry': 'skipme', + 'language': 'python', + 'additional_dependencies': ['/pre-commit-does-not-exist'], + }, + ], + } + add_config_to_repo(repo_with_passing_hook, config) + + ret, printed = _do_run( + cap_out, store, repo_with_passing_hook, + run_opts(all_files=True), + {'SKIP': 'skipme-1'}, + ) + assert ret == 0 + + def test_hook_id_not_in_non_verbose_output( cap_out, store, repo_with_passing_hook, ): From 2405caa352924aa6148b0e4dc52f97291b3ff2b7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 15 Aug 2022 13:46:17 -0400 Subject: [PATCH 035/342] allow `pre-commit run --files ...` against unmerged files --- pre_commit/commands/run.py | 2 +- tests/commands/run_test.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 8d11882c0..37f78f746 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -333,7 +333,7 @@ def run( stash = not args.all_files and not args.files # Check if we have unresolved merge conflict files and fail fast. - if _has_unmerged_paths(): + if stash and _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.from_ref) != bool(args.to_ref): diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 3ae3b537c..ef865330f 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -536,6 +536,13 @@ def test_merge_conflict(cap_out, store, in_merge_conflict): assert b'Unmerged files. Resolve before committing.' in printed +def test_files_during_merge_conflict(cap_out, store, in_merge_conflict): + opts = run_opts(files=['placeholder']) + ret, printed = _do_run(cap_out, store, in_merge_conflict, opts) + assert ret == 0 + assert b'Bash hook' in printed + + def test_merge_conflict_modified(cap_out, store, in_merge_conflict): # Touch another file so we have unstaged non-conflicting things assert os.path.exists('placeholder') From 7a62bf7be2564ccc4f98456192bfa4e608741411 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 21:15:11 +0000 Subject: [PATCH 036/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-autopep8: v1.6.0 → v1.7.0](https://github.com/pre-commit/mirrors-autopep8/compare/v1.6.0...v1.7.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fca524c6..e6c63ae8a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v1.6.0 + rev: v1.7.0 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From fb608ee1b45607e35a386aa104f434879b78c962 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 22:48:22 +0000 Subject: [PATCH 037/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.37.3 → v2.38.0](https://github.com/asottile/pyupgrade/compare/v2.37.3...v2.38.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6c63ae8a..af68fee90 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v2.38.0 hooks: - id: pyupgrade args: [--py37-plus] From a95f488e71f6226696926c43140659fbbcad964b Mon Sep 17 00:00:00 2001 From: chrisRedwine Date: Thu, 22 Sep 2022 21:55:26 -0500 Subject: [PATCH 038/342] extend warning if globs are used instead of regex to local hooks --- pre_commit/clientlib.py | 10 +++++++++- tests/clientlib_test.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 9b53e8107..da6ca2be2 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -298,6 +298,14 @@ def check(self, dct: dict[str, Any]) -> None: OptionalSensibleRegexAtHook('files', cfgv.check_string), OptionalSensibleRegexAtHook('exclude', cfgv.check_string), ) +LOCAL_HOOK_DICT = cfgv.Map( + 'Hook', 'id', + + *MANIFEST_HOOK_DICT.items, + + OptionalSensibleRegexAtHook('files', cfgv.check_string), + OptionalSensibleRegexAtHook('exclude', cfgv.check_string), +) CONFIG_REPO_DICT = cfgv.Map( 'Repository', 'repo', @@ -308,7 +316,7 @@ def check(self, dct: dict[str, Any]) -> None: 'repo', cfgv.NotIn(LOCAL, META), ), cfgv.ConditionalRecurse( - 'hooks', cfgv.Array(MANIFEST_HOOK_DICT), + 'hooks', cfgv.Array(LOCAL_HOOK_DICT), 'repo', LOCAL, ), cfgv.ConditionalRecurse( diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index fb36bb55a..9fea7e168 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -15,6 +15,8 @@ from pre_commit.clientlib import MANIFEST_SCHEMA from pre_commit.clientlib import META_HOOK_DICT from pre_commit.clientlib import MigrateShaToRev +from pre_commit.clientlib import OptionalSensibleRegexAtHook +from pre_commit.clientlib import OptionalSensibleRegexAtTop from pre_commit.clientlib import validate_config_main from pre_commit.clientlib import validate_manifest_main from testing.fixtures import sample_local_config @@ -261,6 +263,27 @@ def test_warn_mutable_rev_conditional(): cfgv.validate(config_obj, CONFIG_REPO_DICT) +@pytest.mark.parametrize( + 'validator_cls', + ( + OptionalSensibleRegexAtHook, + OptionalSensibleRegexAtTop, + ), +) +def test_sensible_regex_validators_dont_pass_none(validator_cls): + key = 'files' + with pytest.raises(cfgv.ValidationError) as excinfo: + validator = validator_cls(key, cfgv.check_string) + validator.check({key: None}) + + assert str(excinfo.value) == ( + '\n' + f'==> At key: {key}' + '\n' + '=====> Expected string got NoneType' + ) + + @pytest.mark.parametrize( ('regex', 'warning'), ( @@ -296,6 +319,22 @@ def test_validate_optional_sensible_regex_at_hook(caplog, regex, warning): assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)] +def test_validate_optional_sensible_regex_at_local_hook(caplog): + config_obj = sample_local_config() + config_obj['hooks'][0]['files'] = r'dir/*.py' + + cfgv.validate(config_obj, CONFIG_REPO_DICT) + + assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + "The 'files' field in hook 'do_not_commit' is a regex, not a glob " + "-- matching '/*' probably isn't what you want here", + ), + ] + + @pytest.mark.parametrize( ('regex', 'warning'), ( From 6d5de9feaf9086f96021d5d63606de54262ca85e Mon Sep 17 00:00:00 2001 From: chrisRedwine Date: Mon, 26 Sep 2022 17:53:14 -0500 Subject: [PATCH 039/342] remove extraneous raw string literal in test --- tests/clientlib_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 9fea7e168..b4c3c4e06 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -321,7 +321,7 @@ def test_validate_optional_sensible_regex_at_hook(caplog, regex, warning): def test_validate_optional_sensible_regex_at_local_hook(caplog): config_obj = sample_local_config() - config_obj['hooks'][0]['files'] = r'dir/*.py' + config_obj['hooks'][0]['files'] = 'dir/*.py' cfgv.validate(config_obj, CONFIG_REPO_DICT) From 404f2dccd57aec10a1fefddb3dba41156c6bc971 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 22:58:40 +0000 Subject: [PATCH 040/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.2 → v3.8.3](https://github.com/asottile/reorder_python_imports/compare/v3.8.2...v3.8.3) - [github.com/asottile/add-trailing-comma: v2.2.3 → v2.3.0](https://github.com/asottile/add-trailing-comma/compare/v2.2.3...v2.3.0) - [github.com/asottile/pyupgrade: v2.38.0 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.38.0...v2.38.2) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af68fee90..6ec15b710 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,18 +14,18 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.2 + rev: v3.8.3 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py37-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.3 + rev: v2.3.0 hooks: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.0 + rev: v2.38.2 hooks: - id: pyupgrade args: [--py37-plus] From 495b5991cfa7aeeb35b0dcc44814bffd8d2d04e1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 29 Sep 2022 16:51:19 -0400 Subject: [PATCH 041/342] "yes" is not a valid search --- .github/ISSUE_TEMPLATE/bug.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index bfced0f29..96cd6c75c 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -12,7 +12,7 @@ body: - type: input id: search attributes: - label: search tried in the issue tracker + label: search you tried in the issue tracker placeholder: ... validations: required: true From 68be295b759f64e9cc820577c26955f661b81145 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 22:51:41 +0000 Subject: [PATCH 042/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.971 → v0.981](https://github.com/pre-commit/mirrors-mypy/compare/v0.971...v0.981) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ec15b710..972128026 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.971 + rev: v0.981 hooks: - id: mypy additional_dependencies: [types-all] From 3d4f6db2a01e687659fef8bf4de66d43b39f2d48 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 23:49:52 +0000 Subject: [PATCH 043/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder_python_imports: v3.8.3 → v3.8.4](https://github.com/asottile/reorder_python_imports/compare/v3.8.3...v3.8.4) - [github.com/asottile/pyupgrade: v2.38.2 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.1.0) - [github.com/pre-commit/mirrors-mypy: v0.981 → v0.982](https://github.com/pre-commit/mirrors-mypy/compare/v0.981...v0.982) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 972128026..02d662ccd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.3 + rev: v3.8.4 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 + rev: v3.1.0 hooks: - id: pyupgrade args: [--py37-plus] @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.981 + rev: v0.982 hooks: - id: mypy additional_dependencies: [types-all] From eb469c756de4282e37da52cc346e70ba9d116e06 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 30 Sep 2022 11:44:18 +0200 Subject: [PATCH 044/342] Rust as 1st class language --- CONTRIBUTING.md | 4 +- azure-pipelines.yml | 2 + pre_commit/languages/rust.py | 118 +++++++++++++++++++++++++++++------ tests/languages/rust_test.py | 70 +++++++++++++++++++++ tests/repository_test.py | 4 +- 5 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 tests/languages/rust_test.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 310c17ee8..0817681a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,9 +65,9 @@ to implement. The current implemented languages are at varying levels: - 0th class - pre-commit does not require any dependencies for these languages as they're not actually languages (current examples: fail, pygrep) - 1st class - pre-commit will bootstrap a full interpreter requiring nothing to - be installed globally (current examples: node, ruby) + be installed globally (current examples: node, ruby, rust) - 2nd class - pre-commit requires the user to install the language globally but - will install tools in an isolated fashion (current examples: python, go, rust, + will install tools in an isolated fashion (current examples: python, go, swift, docker). - 3rd class - pre-commit requires the user to install both the tool and the language globally (current examples: script, system) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 454f6f137..34c94f54a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,6 +17,8 @@ jobs: parameters: toxenvs: [py37] os: windows + additional_variables: + TEMP: C:\Temp pre_test: - task: UseRubyVersion@0 - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 01c373061..5e4ecafa6 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -1,13 +1,20 @@ from __future__ import annotations import contextlib +import functools import os.path +import platform +import shutil +import sys +import tempfile +import urllib.request from typing import Generator from typing import Sequence import toml import pre_commit.constants as C +from pre_commit import parse_shebang from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var @@ -16,24 +23,61 @@ from pre_commit.prefix import Prefix from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b +from pre_commit.util import make_executable +from pre_commit.util import win_exe ENVIRONMENT_DIR = 'rustenv' -get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check -def get_env_patch(target_dir: str) -> PatchesT: +@functools.lru_cache(maxsize=1) +def get_default_version() -> str: + # If rust is already installed, we can save a bunch of setup time by + # using the installed version. + # + # Just detecting the executable does not suffice, because if rustup is + # installed but no toolchain is available, then `cargo` exists but + # cannot be used without installing a toolchain first. + if cmd_output_b('cargo', '--version', retcode=None)[0] == 0: + return 'system' + else: + return C.DEFAULT + + +def _rust_toolchain(language_version: str) -> str: + """Transform the language version into a rust toolchain version.""" + if language_version == C.DEFAULT: + return 'stable' + else: + return language_version + + +def _envdir(prefix: Prefix, version: str) -> str: + directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + return prefix.path(directory) + + +def get_env_patch(target_dir: str, version: str) -> PatchesT: return ( + ('CARGO_HOME', target_dir), ('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))), + # Only set RUSTUP_TOOLCHAIN if we don't want use the system's default + # toolchain + *( + (('RUSTUP_TOOLCHAIN', _rust_toolchain(version)),) + if version != 'system' else () + ), ) @contextlib.contextmanager -def in_env(prefix: Prefix) -> Generator[None, None, None]: - target_dir = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) - with envcontext(get_env_patch(target_dir)): +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + with envcontext( + get_env_patch(_envdir(prefix, language_version), language_version), + ): yield @@ -52,15 +96,45 @@ def _add_dependencies( f.truncate() +def install_rust_with_toolchain(toolchain: str) -> None: + with tempfile.TemporaryDirectory() as rustup_dir: + with envcontext((('RUSTUP_HOME', rustup_dir),)): + # acquire `rustup` if not present + if parse_shebang.find_executable('rustup') is None: + # We did not detect rustup and need to download it first. + if sys.platform == 'win32': # pragma: win32 cover + if platform.machine() == 'x86_64': + url = 'https://win.rustup.rs/x86_64' + else: + url = 'https://win.rustup.rs/i686' + else: # pragma: win32 no cover + url = 'https://sh.rustup.rs' + + resp = urllib.request.urlopen(url) + + rustup_init = os.path.join(rustup_dir, win_exe('rustup-init')) + with open(rustup_init, 'wb') as f: + shutil.copyfileobj(resp, f) + make_executable(rustup_init) + + # install rustup into `$CARGO_HOME/bin` + cmd_output_b( + rustup_init, '-y', '--quiet', '--no-modify-path', + '--default-toolchain', 'none', + ) + + cmd_output_b( + 'rustup', 'toolchain', 'install', '--no-self-update', + toolchain, + ) + + def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('rust', version) - directory = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) + directory = _envdir(prefix, version) # There are two cases where we might want to specify more dependencies: # as dependencies for the library being built, and as binary packages @@ -84,17 +158,21 @@ def install_environment( packages_to_install: set[tuple[str, ...]] = {('--path', '.')} for cli_dep in cli_deps: cli_dep = cli_dep[len('cli:'):] - package, _, version = cli_dep.partition(':') - if version != '': - packages_to_install.add((package, '--version', version)) + package, _, crate_version = cli_dep.partition(':') + if crate_version != '': + packages_to_install.add((package, '--version', crate_version)) else: packages_to_install.add((package,)) - for args in packages_to_install: - cmd_output_b( - 'cargo', 'install', '--bins', '--root', directory, *args, - cwd=prefix.prefix_dir, - ) + with in_env(prefix, version): + if version != 'system': + install_rust_with_toolchain(_rust_toolchain(version)) + + for args in packages_to_install: + cmd_output_b( + 'cargo', 'install', '--bins', '--root', directory, *args, + cwd=prefix.prefix_dir, + ) def run_hook( @@ -102,5 +180,5 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix): + with in_env(hook.prefix, hook.language_version): return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/tests/languages/rust_test.py b/tests/languages/rust_test.py new file mode 100644 index 000000000..9bf97830a --- /dev/null +++ b/tests/languages/rust_test.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from unittest import mock + +import pytest + +import pre_commit.constants as C +from pre_commit import parse_shebang +from pre_commit.languages import rust +from pre_commit.prefix import Prefix +from pre_commit.util import cmd_output + +ACTUAL_GET_DEFAULT_VERSION = rust.get_default_version.__wrapped__ + + +@pytest.fixture +def cmd_output_b_mck(): + with mock.patch.object(rust, 'cmd_output_b') as mck: + yield mck + + +def test_sets_system_when_rust_is_available(cmd_output_b_mck): + cmd_output_b_mck.return_value = (0, b'', b'') + assert ACTUAL_GET_DEFAULT_VERSION() == 'system' + + +def test_uses_default_when_rust_is_not_available(cmd_output_b_mck): + cmd_output_b_mck.return_value = (127, b'', b'error: not found') + assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT + + +@pytest.mark.parametrize('language_version', (C.DEFAULT, '1.56.0')) +def test_installs_with_bootstrapped_rustup(tmpdir, language_version): + tmpdir.join('src', 'main.rs').ensure().write( + 'fn main() {\n' + ' println!("Hello, world!");\n' + '}\n', + ) + tmpdir.join('Cargo.toml').ensure().write( + '[package]\n' + 'name = "hello_world"\n' + 'version = "0.1.0"\n' + 'edition = "2021"\n', + ) + prefix = Prefix(str(tmpdir)) + + find_executable_exes = [] + + original_find_executable = parse_shebang.find_executable + + def mocked_find_executable(exe: str) -> str | None: + """ + Return `None` the first time `find_executable` is called to ensure + that the bootstrapping code is executed, then just let the function + work as normal. + + Also log the arguments to ensure that everything works as expected. + """ + find_executable_exes.append(exe) + if len(find_executable_exes) == 1: + return None + return original_find_executable(exe) + + with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck: + find_exe_mck.side_effect = mocked_find_executable + rust.install_environment(prefix, language_version, ()) + assert find_executable_exes == ['rustup', 'rustup', 'cargo'] + + with rust.in_env(prefix, language_version): + assert cmd_output('hello_world')[1] == 'Hello, world!\n' diff --git a/tests/repository_test.py b/tests/repository_test.py index 11d452ca4..0d4cb651b 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -471,7 +471,7 @@ def test_additional_rust_cli_dependencies_installed( hook = _get_hook(config, store, 'rust-hook') binaries = os.listdir( hook.prefix.path( - helpers.environment_dir(rust.ENVIRONMENT_DIR, C.DEFAULT), 'bin', + helpers.environment_dir(rust.ENVIRONMENT_DIR, 'system'), 'bin', ), ) # normalize for windows @@ -490,7 +490,7 @@ def test_additional_rust_lib_dependencies_installed( hook = _get_hook(config, store, 'rust-hook') binaries = os.listdir( hook.prefix.path( - helpers.environment_dir(rust.ENVIRONMENT_DIR, C.DEFAULT), 'bin', + helpers.environment_dir(rust.ENVIRONMENT_DIR, 'system'), 'bin', ), ) # normalize for windows From f9532fb59ab302d8782640c4c1652dc27ab09ec5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:10:12 +0000 Subject: [PATCH 045/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.0.0 → v2.1.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.0.0...v2.1.0) - [github.com/asottile/reorder_python_imports: v3.8.4 → v3.8.5](https://github.com/asottile/reorder_python_imports/compare/v3.8.4...v3.8.5) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02d662ccd..08ed35b05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,11 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.0.0 + rev: v2.1.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.4 + rev: v3.8.5 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) From bc96b0bcf688f8c5e6494e8bcf67ef72780f4c20 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 22 Oct 2022 09:34:43 -0700 Subject: [PATCH 046/342] fix tests for submodules for CVE-2022-39253 --- pre_commit/git.py | 7 +++---- tox.ini | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pre_commit/git.py b/pre_commit/git.py index 35392b341..40b12f01c 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -3,7 +3,7 @@ import logging import os.path import sys -from typing import MutableMapping +from typing import Mapping from pre_commit.errors import FatalError from pre_commit.util import CalledProcessError @@ -24,9 +24,7 @@ def zsplit(s: str) -> list[str]: return [] -def no_git_env( - _env: MutableMapping[str, str] | None = None, -) -> dict[str, str]: +def no_git_env(_env: Mapping[str, str] | None = None) -> dict[str, str]: # Too many bugs dealing with environment variables and GIT: # https://github.com/pre-commit/pre-commit/issues/300 # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running @@ -44,6 +42,7 @@ def no_git_env( 'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO', 'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT', 'GIT_HTTP_PROXY_AUTHMETHOD', + 'GIT_ALLOW_PROTOCOL', } } diff --git a/tox.ini b/tox.ini index 7f43e41e4..463b72f35 100644 --- a/tox.ini +++ b/tox.ini @@ -23,5 +23,6 @@ env = GIT_COMMITTER_NAME=test GIT_AUTHOR_EMAIL=test@example.com GIT_COMMITTER_EMAIL=test@example.com + GIT_ALLOW_PROTOCOL=file VIRTUALENV_NO_DOWNLOAD=1 PRE_COMMIT_NO_CONCURRENCY=1 From 8ebb7ae2f574cfda9721865d4399fd273a370dec Mon Sep 17 00:00:00 2001 From: Matt Phillips Date: Thu, 27 Oct 2022 15:32:38 -0400 Subject: [PATCH 047/342] add GIT_ASKPASS as a passthrough env var documented via man gitcredentials, it is used to provide a script/input for git to fetch creds in a no-tty usecase. used among other things by jenkins to pass credentials down to git for authentication. https://github.com/jenkinsci/git-plugin/blob/1e3488a730a169778ba0863dd4edbb1dc29154a1/README.adoc#git-bindings https://github.com/jenkinsci/git-plugin/blob/9429e7d05df3dbb4060ac6ab4da6538bb0eb50ba/src/main/java/jenkins/plugins/git/GitUsernamePasswordBinding.java#L130 --- pre_commit/git.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pre_commit/git.py b/pre_commit/git.py index 40b12f01c..439da7ada 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -43,6 +43,7 @@ def no_git_env(_env: Mapping[str, str] | None = None) -> dict[str, str]: 'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT', 'GIT_HTTP_PROXY_AUTHMETHOD', 'GIT_ALLOW_PROTOCOL', + 'GIT_ASKPASS', } } From e703982de45ac64492897b25fa4edbdb8da10e62 Mon Sep 17 00:00:00 2001 From: marsha Date: Fri, 28 Oct 2022 20:23:00 -0500 Subject: [PATCH 048/342] Change Rust to install environment with `cargo add` over `toml` --- pre_commit/languages/rust.py | 26 +++++++++++--------------- setup.cfg | 1 - tests/repository_test.py | 2 +- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 5e4ecafa6..0c347b492 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -11,8 +11,6 @@ from typing import Generator from typing import Sequence -import toml - import pre_commit.constants as C from pre_commit import parse_shebang from pre_commit.envcontext import envcontext @@ -82,18 +80,16 @@ def in_env( def _add_dependencies( - cargo_toml_path: str, + prefix: Prefix, additional_dependencies: set[str], ) -> None: - with open(cargo_toml_path, 'r+') as f: - cargo_toml = toml.load(f) - cargo_toml.setdefault('dependencies', {}) - for dep in additional_dependencies: - name, _, spec = dep.partition(':') - cargo_toml['dependencies'][name] = spec or '*' - f.seek(0) - toml.dump(cargo_toml, f) - f.truncate() + crates = [] + for dep in additional_dependencies: + name, _, spec = dep.partition(':') + crate = f'{name}@{spec or "*"}' + crates.append(crate) + + helpers.run_setup_cmd(prefix, ('cargo', 'add', *crates)) def install_rust_with_toolchain(toolchain: str) -> None: @@ -151,9 +147,6 @@ def install_environment( } lib_deps = set(additional_dependencies) - cli_deps - if len(lib_deps) > 0: - _add_dependencies(prefix.path('Cargo.toml'), lib_deps) - with clean_path_on_failure(directory): packages_to_install: set[tuple[str, ...]] = {('--path', '.')} for cli_dep in cli_deps: @@ -168,6 +161,9 @@ def install_environment( if version != 'system': install_rust_with_toolchain(_rust_toolchain(version)) + if len(lib_deps) > 0: + _add_dependencies(prefix, lib_deps) + for args in packages_to_install: cmd_output_b( 'cargo', 'install', '--bins', '--root', directory, *args, diff --git a/setup.cfg b/setup.cfg index afe56848d..ab95cc042 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,6 @@ install_requires = identify>=1.0.0 nodeenv>=0.11.1 pyyaml>=5.1 - toml virtualenv>=20.10.0 importlib-metadata;python_version<"3.8" python_requires = >=3.7 diff --git a/tests/repository_test.py b/tests/repository_test.py index 0d4cb651b..252c126c0 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -485,7 +485,7 @@ def test_additional_rust_lib_dependencies_installed( path = make_repo(tempdir_factory, 'rust_hooks_repo') config = make_config_from_repo(path) # A small rust package with no dependencies. - deps = ['shellharden:3.1.0'] + deps = ['shellharden:3.1.0', 'git-version'] config['hooks'][0]['additional_dependencies'] = deps hook = _get_hook(config, store, 'rust-hook') binaries = os.listdir( From 84b38f7b89fa22bea8bc70b03e664cd6cda9db84 Mon Sep 17 00:00:00 2001 From: marsha Date: Sun, 30 Oct 2022 14:47:42 -0500 Subject: [PATCH 049/342] Change `cmd_output_b`s `retcode` arg to a boolean `check` --- pre_commit/commands/run.py | 4 ++-- pre_commit/error_handler.py | 2 +- pre_commit/git.py | 4 ++-- pre_commit/languages/node.py | 2 +- pre_commit/languages/rust.py | 2 +- pre_commit/staged_files_only.py | 2 +- pre_commit/util.py | 11 ++++++----- pre_commit/xargs.py | 2 +- tests/commands/init_templatedir_test.py | 2 +- tests/commands/install_uninstall_test.py | 6 +++--- tests/commands/run_test.py | 4 ++-- tests/conftest.py | 2 +- tests/error_handler_test.py | 3 ++- tests/git_test.py | 2 +- tests/util_test.py | 6 +++--- 15 files changed, 28 insertions(+), 26 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 37f78f746..429e04c60 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -263,7 +263,7 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]: def _get_diff() -> bytes: _, out, _ = cmd_output_b( - 'git', 'diff', '--no-ext-diff', '--ignore-submodules', retcode=None, + 'git', 'diff', '--no-ext-diff', '--ignore-submodules', check=False, ) return out @@ -318,7 +318,7 @@ def _has_unmerged_paths() -> bool: def _has_unstaged_config(config_file: str) -> bool: retcode, _, _ = cmd_output_b( 'git', 'diff', '--no-ext-diff', '--exit-code', config_file, - retcode=None, + check=False, ) # be explicit, other git errors don't mean it has an unstaged config. return retcode == 1 diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py index 992f5cdc0..d740ee3e4 100644 --- a/pre_commit/error_handler.py +++ b/pre_commit/error_handler.py @@ -25,7 +25,7 @@ def _log_and_exit( error_msg = f'{msg}: {type(exc).__name__}: '.encode() + force_bytes(exc) output.write_line_b(error_msg) - _, git_version_b, _ = cmd_output_b('git', '--version', retcode=None) + _, git_version_b, _ = cmd_output_b('git', '--version', check=False) git_version = git_version_b.decode(errors='backslashreplace').rstrip() storedir = Store().directory diff --git a/pre_commit/git.py b/pre_commit/git.py index 439da7ada..37ed3a718 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -187,11 +187,11 @@ def head_rev(remote: str) -> str: def has_diff(*args: str, repo: str = '.') -> bool: cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args) - return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1 + return cmd_output_b(*cmd, cwd=repo, check=False)[0] == 1 def has_core_hookpaths_set() -> bool: - _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', retcode=None) + _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', check=False) return bool(out.strip()) diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 39f300065..37a5b63f1 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -75,7 +75,7 @@ def in_env( def health_check(prefix: Prefix, language_version: str) -> str | None: with in_env(prefix, language_version): - retcode, _, _ = cmd_output_b('node', '--version', retcode=None) + retcode, _, _ = cmd_output_b('node', '--version', check=False) if retcode != 0: # pragma: win32 no cover return f'`node --version` returned {retcode}' else: diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 0c347b492..ef603bc00 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -36,7 +36,7 @@ def get_default_version() -> str: # Just detecting the executable does not suffice, because if rustup is # installed but no toolchain is available, then `cargo` exists but # cannot be used without installing a toolchain first. - if cmd_output_b('cargo', '--version', retcode=None)[0] == 0: + if cmd_output_b('cargo', '--version', check=False)[0] == 0: return 'system' else: return C.DEFAULT diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index 83d8a03e1..172fb20b1 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -52,7 +52,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: retcode, diff_stdout_binary, _ = cmd_output_b( 'git', 'diff-index', '--ignore-submodules', '--binary', '--exit-code', '--no-color', '--no-ext-diff', tree, '--', - retcode=None, + check=False, ) if retcode and diff_stdout_binary.strip(): patch_filename = f'patch{int(time.time())}-{os.getpid()}' diff --git a/pre_commit/util.py b/pre_commit/util.py index 8c296f4d8..a935c2d85 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -124,7 +124,7 @@ def _oserror_to_output(e: OSError) -> tuple[int, bytes, None]: def cmd_output_b( *cmd: str, - retcode: int | None = 0, + check: bool = True, **kwargs: Any, ) -> tuple[int, bytes, bytes | None]: _setdefault_kwargs(kwargs) @@ -142,8 +142,9 @@ def cmd_output_b( stdout_b, stderr_b = proc.communicate() returncode = proc.returncode - if retcode is not None and retcode != returncode: - raise CalledProcessError(returncode, cmd, retcode, stdout_b, stderr_b) + SUCCESS = 0 + if check and returncode != SUCCESS: + raise CalledProcessError(returncode, cmd, SUCCESS, stdout_b, stderr_b) return returncode, stdout_b, stderr_b @@ -196,10 +197,10 @@ def __exit__( def cmd_output_p( *cmd: str, - retcode: int | None = 0, + check: bool = True, **kwargs: Any, ) -> tuple[int, bytes, bytes | None]: - assert retcode is None + assert check is False assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr'] _setdefault_kwargs(kwargs) diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index f2b3421a0..e3af90efd 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -154,7 +154,7 @@ def run_cmd_partition( run_cmd: tuple[str, ...], ) -> tuple[int, bytes, bytes | None]: return cmd_fn( - *run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs, + *run_cmd, check=False, stderr=subprocess.STDOUT, **kwargs, ) threads = min(len(partitions), target_concurrency) diff --git a/tests/commands/init_templatedir_test.py b/tests/commands/init_templatedir_test.py index 64bfc8b4e..28f29b77c 100644 --- a/tests/commands/init_templatedir_test.py +++ b/tests/commands/init_templatedir_test.py @@ -135,7 +135,7 @@ def test_init_templatedir_skip_on_missing_config( retcode, output = git_commit( fn=cmd_output_mocked_pre_commit_home, tempdir_factory=tempdir_factory, - retcode=None, + check=False, ) assert retcode == commit_retcode diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index ae668ac9f..379c03a4f 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -126,7 +126,7 @@ def _get_commit_output(tempdir_factory, touch_file='foo', **kwargs): cmd_output('git', 'add', touch_file) return git_commit( fn=cmd_output_mocked_pre_commit_home, - retcode=None, + check=False, tempdir_factory=tempdir_factory, **kwargs, ) @@ -286,7 +286,7 @@ def test_environment_not_sourced(tempdir_factory, store): 'GIT_AUTHOR_EMAIL': os.environ['GIT_AUTHOR_EMAIL'], 'GIT_COMMITTER_EMAIL': os.environ['GIT_COMMITTER_EMAIL'], }, - retcode=None, + check=False, ) assert ret == 1 assert out == ( @@ -551,7 +551,7 @@ def _get_push_output(tempdir_factory, remote='origin', opts=()): return cmd_output_mocked_pre_commit_home( 'git', 'push', remote, 'HEAD:new_branch', *opts, tempdir_factory=tempdir_factory, - retcode=None, + check=False, )[:2] diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index ef865330f..03d741e06 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -718,7 +718,7 @@ def test_non_ascii_hook_id(repo_with_passing_hook, tempdir_factory): with cwd(repo_with_passing_hook): _, stdout, _ = cmd_output_mocked_pre_commit_home( sys.executable, '-m', 'pre_commit.main', 'run', '☃', - retcode=None, tempdir_factory=tempdir_factory, + check=False, tempdir_factory=tempdir_factory, ) assert 'UnicodeDecodeError' not in stdout # Doesn't actually happen, but a reasonable assertion @@ -737,7 +737,7 @@ def test_stdout_write_bug_py26(repo_with_failing_hook, store, tempdir_factory): _, out = git_commit( fn=cmd_output_mocked_pre_commit_home, tempdir_factory=tempdir_factory, - retcode=None, + check=False, ) assert 'UnicodeEncodeError' not in out # Doesn't actually happen, but a reasonable assertion diff --git a/tests/conftest.py b/tests/conftest.py index 40c0c0500..30761715b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,7 +68,7 @@ def _make_conflict(): bar_only_file.write('bar') cmd_output('git', 'add', 'bar_only_file') git_commit(msg=_make_conflict.__name__) - cmd_output('git', 'merge', 'foo', retcode=None) + cmd_output('git', 'merge', 'foo', check=False) @pytest.fixture diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py index 47e2afaa4..068149e3f 100644 --- a/tests/error_handler_test.py +++ b/tests/error_handler_test.py @@ -183,10 +183,11 @@ def test_error_handler_no_tty(tempdir_factory): 'from pre_commit.error_handler import error_handler\n' 'with error_handler():\n' ' raise ValueError("\\u2603")\n', - retcode=3, + check=False, tempdir_factory=tempdir_factory, pre_commit_home=pre_commit_home, ) + assert ret == 3 log_file = os.path.join(pre_commit_home, 'pre-commit.log') out_lines = out.splitlines() assert out_lines[-2] == 'An unexpected error has occurred: ValueError: ☃' diff --git a/tests/git_test.py b/tests/git_test.py index b9f524a14..93f5a1c6e 100644 --- a/tests/git_test.py +++ b/tests/git_test.py @@ -104,7 +104,7 @@ def test_is_in_merge_conflict_submodule(in_conflicting_submodule): def test_cherry_pick_conflict(in_merge_conflict): cmd_output('git', 'merge', '--abort') foo_ref = cmd_output('git', 'rev-parse', 'foo')[1].strip() - cmd_output('git', 'cherry-pick', foo_ref, retcode=None) + cmd_output('git', 'cherry-pick', foo_ref, check=False) assert git.is_in_merge_conflict() is False diff --git a/tests/util_test.py b/tests/util_test.py index 6b00f9fc6..bc8f585f8 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -83,14 +83,14 @@ def test_tmpdir(): def test_cmd_output_exe_not_found(): - ret, out, _ = cmd_output('dne', retcode=None) + ret, out, _ = cmd_output('dne', check=False) assert ret == 1 assert out == 'Executable `dne` not found' @pytest.mark.parametrize('fn', (cmd_output_b, cmd_output_p)) def test_cmd_output_exe_not_found_bytes(fn): - ret, out, _ = fn('dne', retcode=None, stderr=subprocess.STDOUT) + ret, out, _ = fn('dne', check=False, stderr=subprocess.STDOUT) assert ret == 1 assert out == b'Executable `dne` not found' @@ -101,7 +101,7 @@ def test_cmd_output_no_shebang(tmpdir, fn): make_executable(f) # previously this raised `OSError` -- the output is platform specific - ret, out, _ = fn(str(f), retcode=None, stderr=subprocess.STDOUT) + ret, out, _ = fn(str(f), check=False, stderr=subprocess.STDOUT) assert ret == 1 assert isinstance(out, bytes) assert out.endswith(b'\n') From 42102a1bfd96f70ae817f90ac2e7e1b07eae933d Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:18:13 -0500 Subject: [PATCH 050/342] Remove `expected_returncode` from `CalledProcessError` --- pre_commit/util.py | 10 +++------- tests/error_handler_test.py | 2 +- tests/languages/docker_test.py | 2 +- tests/store_test.py | 2 +- tests/util_test.py | 6 ++---- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pre_commit/util.py b/pre_commit/util.py index a935c2d85..b85076883 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -83,14 +83,12 @@ def __init__( self, returncode: int, cmd: tuple[str, ...], - expected_returncode: int, stdout: bytes, stderr: bytes | None, ) -> None: - super().__init__(returncode, cmd, expected_returncode, stdout, stderr) + super().__init__(returncode, cmd, stdout, stderr) self.returncode = returncode self.cmd = cmd - self.expected_returncode = expected_returncode self.stdout = stdout self.stderr = stderr @@ -104,7 +102,6 @@ def _indent_or_none(part: bytes | None) -> bytes: return b''.join(( f'command: {self.cmd!r}\n'.encode(), f'return code: {self.returncode}\n'.encode(), - f'expected return code: {self.expected_returncode}\n'.encode(), b'stdout:', _indent_or_none(self.stdout), b'\n', b'stderr:', _indent_or_none(self.stderr), )) @@ -142,9 +139,8 @@ def cmd_output_b( stdout_b, stderr_b = proc.communicate() returncode = proc.returncode - SUCCESS = 0 - if check and returncode != SUCCESS: - raise CalledProcessError(returncode, cmd, SUCCESS, stdout_b, stderr_b) + if check and returncode: + raise CalledProcessError(returncode, cmd, stdout_b, stderr_b) return returncode, stdout_b, stderr_b diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py index 068149e3f..a79d9c1a9 100644 --- a/tests/error_handler_test.py +++ b/tests/error_handler_test.py @@ -162,7 +162,7 @@ def test_error_handler_non_ascii_exception(mock_store_dir): def test_error_handler_non_utf8_exception(mock_store_dir): with pytest.raises(SystemExit): with error_handler.error_handler(): - raise CalledProcessError(1, ('exe',), 0, b'error: \xa0\xe1', b'') + raise CalledProcessError(1, ('exe',), b'error: \xa0\xe1', b'') def test_error_handler_non_stringable_exception(mock_store_dir): diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py index 58387611f..5f7c85e71 100644 --- a/tests/languages/docker_test.py +++ b/tests/languages/docker_test.py @@ -178,6 +178,6 @@ def test_get_docker_path_in_docker_windows(in_docker): def test_get_docker_path_in_docker_docker_in_docker(in_docker): # won't be able to discover "self" container in true docker-in-docker - err = CalledProcessError(1, (), 0, b'', b'') + err = CalledProcessError(1, (), b'', b'') with mock.patch.object(docker, 'cmd_output_b', side_effect=err): assert docker._get_docker_path('/project') == '/project' diff --git a/tests/store_test.py b/tests/store_test.py index ff671a83d..818776623 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -127,7 +127,7 @@ def test_clone_shallow_failure_fallback_to_complete( # Force shallow clone failure def fake_shallow_clone(self, *args, **kwargs): - raise CalledProcessError(1, (), 0, b'', None) + raise CalledProcessError(1, (), b'', None) store._shallow_clone = fake_shallow_clone ret = store.clone(path, rev) diff --git a/tests/util_test.py b/tests/util_test.py index bc8f585f8..b3f18b4cf 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -18,11 +18,10 @@ def test_CalledProcessError_str(): - error = CalledProcessError(1, ('exe',), 0, b'output', b'errors') + error = CalledProcessError(1, ('exe',), b'output', b'errors') assert str(error) == ( "command: ('exe',)\n" 'return code: 1\n' - 'expected return code: 0\n' 'stdout:\n' ' output\n' 'stderr:\n' @@ -31,11 +30,10 @@ def test_CalledProcessError_str(): def test_CalledProcessError_str_nooutput(): - error = CalledProcessError(1, ('exe',), 0, b'', b'') + error = CalledProcessError(1, ('exe',), b'', b'') assert str(error) == ( "command: ('exe',)\n" 'return code: 1\n' - 'expected return code: 0\n' 'stdout: (none)\n' 'stderr: (none)' ) From 3b3cf8c1f1536e69a9cc536f0f2f41d3847b6237 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 23:27:34 +0000 Subject: [PATCH 051/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.1.0 → v2.2.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.1.0...v2.2.0) - [github.com/asottile/reorder_python_imports: v3.8.5 → v3.9.0](https://github.com/asottile/reorder_python_imports/compare/v3.8.5...v3.9.0) - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) - [github.com/pre-commit/mirrors-autopep8: v1.7.0 → v2.0.0](https://github.com/pre-commit/mirrors-autopep8/compare/v1.7.0...v2.0.0) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 08ed35b05..1a4b56776 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,11 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.1.0 + rev: v2.2.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.5 + rev: v3.9.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -25,12 +25,12 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v1.7.0 + rev: v2.0.0 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From 4bca29ee2cb6631a4d9420e443674c6905a81705 Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:00:32 -0500 Subject: [PATCH 052/342] Change `intent_to_add_files` from using `git status` to `git diff` --- pre_commit/git.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pre_commit/git.py b/pre_commit/git.py index 37ed3a718..f84eb06bd 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -150,18 +150,10 @@ def get_staged_files(cwd: str | None = None) -> list[str]: def intent_to_add_files() -> list[str]: _, stdout, _ = cmd_output( - 'git', 'status', '--ignore-submodules', '--porcelain', '-z', + 'git', 'diff', '--ignore-submodules', '--diff-filter=A', + '--name-only', '-z', ) - parts = list(reversed(zsplit(stdout))) - intent_to_add = [] - while parts: - line = parts.pop() - status, filename = line[:3], line[3:] - if status[0] in {'C', 'R'}: # renames / moves have an additional arg - parts.pop() - if status[1] == 'A': - intent_to_add.append(filename) - return intent_to_add + return zsplit(stdout) def get_all_files() -> list[str]: From 97ad4f89ecc9a7462baf78b29d1ee47f121304a1 Mon Sep 17 00:00:00 2001 From: mishaschwartz Date: Tue, 1 Nov 2022 13:09:29 -0400 Subject: [PATCH 053/342] ruby: update ruby-build to 20220710 to ensure that the correct openssl version is used --- pre_commit/resources/ruby-build.tar.gz | Bin 72569 -> 74032 bytes testing/make-archives | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz index 8edb3caa3c27f1ccff9f0e0dc36e9863845c4efa..35419f63aebe33b6710851aef70937cd9ef163ba 100644 GIT binary patch literal 74032 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

Q#vRC5;yit2Sd`xrHX$e_C?P2bQUVfE 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

@UO6IsN-Ld{vdH%#= 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

FB_o9 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%0Da>5o|PaFgyRW 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 diff --git a/testing/make-archives b/testing/make-archives index 04b42dd9d..704101f51 100755 --- a/testing/make-archives +++ b/testing/make-archives @@ -17,7 +17,7 @@ from typing import Sequence REPOS = ( ('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'), - ('ruby-build', 'https://github.com/rbenv/ruby-build', '2004fd7'), + ('ruby-build', 'https://github.com/rbenv/ruby-build', '98c0337'), ( 'ruby-download', 'https://github.com/garnieretienne/rvm-download', From 4399c2dbc6b7571489a524eedecfba9cd53d93a8 Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Thu, 10 Nov 2022 20:09:56 -0600 Subject: [PATCH 054/342] Add `--no-ext-diff` to `git diff` call --- pre_commit/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pre_commit/git.py b/pre_commit/git.py index f84eb06bd..a76118f0b 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -150,8 +150,8 @@ def get_staged_files(cwd: str | None = None) -> list[str]: def intent_to_add_files() -> list[str]: _, stdout, _ = cmd_output( - 'git', 'diff', '--ignore-submodules', '--diff-filter=A', - '--name-only', '-z', + 'git', 'diff', '--no-ext-diff', '--ignore-submodules', + '--diff-filter=A', '--name-only', '-z', ) return zsplit(stdout) From 371b4fc1fd33267c0ca0fe6962eee4fa76c4305d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 23:36:47 +0000 Subject: [PATCH 055/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2) - [github.com/pre-commit/mirrors-mypy: v0.982 → v0.990](https://github.com/pre-commit/mirrors-mypy/compare/v0.982...v0.990) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a4b56776..ad5fecb41 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade args: [--py37-plus] @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.982 + rev: v0.990 hooks: - id: mypy additional_dependencies: [types-all] From 318296d8c5427430510620bc443393a290f6db92 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 16 Nov 2022 19:19:49 -0500 Subject: [PATCH 056/342] remove no_implicit_optional this is the default in mypy 0.990 Committed via https://github.com/asottile/all-repos --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ab95cc042..dd0f9c9a4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,7 +56,6 @@ check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true -no_implicit_optional = true warn_redundant_casts = true warn_unused_ignores = true From 391d05e2f30916677919bcea2128414e807d0ff0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:11:57 +0000 Subject: [PATCH 057/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.990 → v0.991](https://github.com/pre-commit/mirrors-mypy/compare/v0.990...v0.991) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad5fecb41..44172896c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.990 + rev: v0.991 hooks: - id: mypy additional_dependencies: [types-all] From 50c217964b0f00e38d67cac858b597501a86e22b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 27 Nov 2022 16:30:58 -0500 Subject: [PATCH 058/342] remove obsolete comment --- pre_commit/commands/sample_config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pre_commit/commands/sample_config.py b/pre_commit/commands/sample_config.py index 82a1617f8..ce22f65e4 100644 --- a/pre_commit/commands/sample_config.py +++ b/pre_commit/commands/sample_config.py @@ -1,7 +1,3 @@ -# TODO: maybe `git ls-remote git://github.com/pre-commit/pre-commit-hooks` to -# determine the latest revision? This adds ~200ms from my tests (and is -# significantly faster than https:// or http://). For now, periodically -# manually updating the revision is fine. from __future__ import annotations SAMPLE_CONFIG = '''\ # See https://pre-commit.com for more information From df7bcf78c3b1c056150b5476a5a58b6bd0d8c8d5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Nov 2022 01:09:52 +0000 Subject: [PATCH 059/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 44172896c..ee1492c6f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -34,7 +34,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From 5becd50974ea39caea357be2d62c940120ead91a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 5 Dec 2022 23:55:06 -0500 Subject: [PATCH 060/342] update swift for jammy --- testing/get-swift.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/get-swift.sh b/testing/get-swift.sh index b77e18c0e..3e7808241 100755 --- a/testing/get-swift.sh +++ b/testing/get-swift.sh @@ -3,9 +3,9 @@ set -euo pipefail . /etc/lsb-release -if [ "$DISTRIB_CODENAME" = "focal" ]; then - SWIFT_URL='https://download.swift.org/swift-5.6.1-release/ubuntu2004/swift-5.6.1-RELEASE/swift-5.6.1-RELEASE-ubuntu20.04.tar.gz' - SWIFT_HASH='2b4f22d4a8b59fe8e050f0b7f020f8d8f12553cbda56709b2340a4a3bb90cfea' +if [ "$DISTRIB_CODENAME" = "jammy" ]; then + SWIFT_URL='https://download.swift.org/swift-5.7.1-release/ubuntu2204/swift-5.7.1-RELEASE/swift-5.7.1-RELEASE-ubuntu22.04.tar.gz' + SWIFT_HASH='7f60291f5088d3e77b0c2364beaabd29616ee7b37260b7b06bdbeb891a7fe161' else echo "unknown dist: ${DISTRIB_CODENAME}" 1>&2 exit 1 From 6c524f7a554f03a247eb76ac48da78b8eef27389 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 23 Nov 2022 14:45:08 -0500 Subject: [PATCH 061/342] fix rust platform detection on windows --- pre_commit/languages/rust.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index ef603bc00..204f2aa79 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -3,7 +3,6 @@ import contextlib import functools import os.path -import platform import shutil import sys import tempfile @@ -99,10 +98,7 @@ def install_rust_with_toolchain(toolchain: str) -> None: if parse_shebang.find_executable('rustup') is None: # We did not detect rustup and need to download it first. if sys.platform == 'win32': # pragma: win32 cover - if platform.machine() == 'x86_64': - url = 'https://win.rustup.rs/x86_64' - else: - url = 'https://win.rustup.rs/i686' + url = 'https://win.rustup.rs/x86_64' else: # pragma: win32 no cover url = 'https://sh.rustup.rs' From 46c64efd9d8da13e20292f26429d228e4f76958f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 6 Dec 2022 15:00:06 -0500 Subject: [PATCH 062/342] update .net framework target --- testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj | 2 +- testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj | 2 +- .../dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj | 2 +- .../dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj index 4f714d339..861ced6d9 100644 --- a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj +++ b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6 true proj1 diff --git a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj index da451f7cc..dfce2cad1 100644 --- a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj +++ b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6 true proj2 diff --git a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj b/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj index d2e556ac0..fa9879b0d 100644 --- a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj +++ b/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1 + net6 true testeroni ./nupkg diff --git a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj b/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj index e37296480..a4e2d0058 100644 --- a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj +++ b/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp3.1 + net6 true testeroni ./nupkg From 0b45ecc8a40d7d980b43a7f37d4f18b8e390aa8d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 6 Dec 2022 15:35:10 -0500 Subject: [PATCH 063/342] remove python 2.x cross version tests --- CONTRIBUTING.md | 1 - .../python3_hooks_repo/.pre-commit-hooks.yaml | 1 + tests/repository_test.py | 34 ++----------------- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0817681a8..a9bcb79ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,6 @@ - The complete test suite depends on having at least the following installed (possibly not a complete list) - git (Version 2.24.0 or above is required to run pre-merge-commit tests) - - python2 (Required by a test which checks different python versions) - python3 (Required by a test which checks different python versions) - tox (or virtualenv) - ruby + gem diff --git a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml index 964cf8363..2c2370092 100644 --- a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml +++ b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml @@ -2,4 +2,5 @@ name: Python 3 Hook entry: python3-hook language: python + language_version: python3 files: \.py$ diff --git a/tests/repository_test.py b/tests/repository_test.py index 252c126c0..64d8a1701 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -173,23 +173,6 @@ def test_python_venv(tempdir_factory, store): ) -@xfailif_windows # pragma: win32 no cover # no python 2 in GHA -def test_switch_language_versions_doesnt_clobber(tempdir_factory, store): - # We're using the python3 repo because it prints the python version - path = make_repo(tempdir_factory, 'python3_hooks_repo') - - def run_on_version(version, expected_output): - config = make_config_from_repo(path) - config['hooks'][0]['language_version'] = version - hook = _get_hook(config, store, 'python3-hook') - ret, out = _hook_run(hook, [], color=False) - assert ret == 0 - assert _norm_out(out) == expected_output - - run_on_version('python2', b'2\n[]\nHello World\n') - run_on_version('python3', b'3\n[]\nHello World\n') - - def test_versioned_python_hook(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'python3_hooks_repo', @@ -883,7 +866,7 @@ def test_tags_on_repositories(in_tmpdir, tempdir_factory, store): @pytest.fixture def local_python_config(): # Make a "local" hooks repo that just installs our other hooks repo - repo_path = get_resource_path('python3_hooks_repo') + repo_path = get_resource_path('python_hooks_repo') manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE)) hooks = [ dict(hook, additional_dependencies=[repo_path]) for hook in manifest @@ -892,23 +875,12 @@ def local_python_config(): def test_local_python_repo(store, local_python_config): - hook = _get_hook(local_python_config, store, 'python3-hook') - # language_version should have been adjusted to the interpreter version - assert hook.language_version != C.DEFAULT - ret, out = _hook_run(hook, ('filename',), color=False) - assert ret == 0 - assert _norm_out(out) == b"3\n['filename']\nHello World\n" - - -@xfailif_windows # pragma: win32 no cover # no python2 in GHA -def test_local_python_repo_python2(store, local_python_config): - local_python_config['hooks'][0]['language_version'] = 'python2' - hook = _get_hook(local_python_config, store, 'python3-hook') + hook = _get_hook(local_python_config, store, 'foo') # language_version should have been adjusted to the interpreter version assert hook.language_version != C.DEFAULT ret, out = _hook_run(hook, ('filename',), color=False) assert ret == 0 - assert _norm_out(out) == b"2\n['filename']\nHello World\n" + assert _norm_out(out) == b"['filename']\nHello World\n" def test_default_language_version(store, local_python_config): From 92c70766fd2db34fa025c2d8c7dad25f6cb0b17e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 6 Dec 2022 16:47:56 -0500 Subject: [PATCH 064/342] fix rust coverage on windows it's a complete mystery why this isn't needed on other platforms, the branch is legitimately uncovered there --- tests/languages/rust_test.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/languages/rust_test.py b/tests/languages/rust_test.py index 9bf97830a..f011e7199 100644 --- a/tests/languages/rust_test.py +++ b/tests/languages/rust_test.py @@ -68,3 +68,23 @@ def mocked_find_executable(exe: str) -> str | None: with rust.in_env(prefix, language_version): assert cmd_output('hello_world')[1] == 'Hello, world!\n' + + +def test_installs_with_existing_rustup(tmpdir): + tmpdir.join('src', 'main.rs').ensure().write( + 'fn main() {\n' + ' println!("Hello, world!");\n' + '}\n', + ) + tmpdir.join('Cargo.toml').ensure().write( + '[package]\n' + 'name = "hello_world"\n' + 'version = "0.1.0"\n' + 'edition = "2021"\n', + ) + prefix = Prefix(str(tmpdir)) + + assert parse_shebang.find_executable('rustup') is not None + rust.install_environment(prefix, '1.56.0', ()) + with rust.in_env(prefix, '1.56.0'): + assert cmd_output('hello_world')[1] == 'Hello, world!\n' From b92fe017559d93061c8c259db389ecd8e4f39b00 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 10 Dec 2022 23:33:20 -0500 Subject: [PATCH 065/342] force the `-p` branch to run for language: python under test --- tests/repository_test.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/repository_test.py b/tests/repository_test.py index 64d8a1701..6d50cb84c 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -173,13 +173,20 @@ def test_python_venv(tempdir_factory, store): ) -def test_versioned_python_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'python3_hooks_repo', - 'python3-hook', - [os.devnull], - f'3\n[{os.devnull!r}]\nHello World\n'.encode(), - ) +def test_language_versioned_python_hook(tempdir_factory, store): + # we patch this force virtualenv executing with `-p` since we can't + # reliably have multiple pythons available in CI + with mock.patch.object( + python, + '_sys_executable_matches', + return_value=False, + ): + _test_hook_repo( + tempdir_factory, store, 'python3_hooks_repo', + 'python3-hook', + [os.devnull], + f'3\n[{os.devnull!r}]\nHello World\n'.encode(), + ) @skipif_cant_run_coursier # pragma: win32 no cover From 8cc3a6d8aa45b9984972bb0f73b9b6dcb3227273 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 10 Dec 2022 23:45:04 -0500 Subject: [PATCH 066/342] passenv is stupid anyway --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 463b72f35..e06be115b 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py37,py38,pypy3,pre-commit [testenv] deps = -rrequirements-dev.txt -passenv = APPDATA HOME LOCALAPPDATA PROGRAMFILES RUSTUP_HOME +passenv = * commands = coverage erase coverage run -m pytest {posargs:tests} From b00c31cf9e917b5ac62e093e9cca6a59d3677992 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 12 Dec 2022 12:22:39 -0500 Subject: [PATCH 067/342] use a newer version of ruby which builds cleanly --- .../ruby_versioned_hooks_repo/.pre-commit-hooks.yaml | 2 +- tests/languages/ruby_test.py | 4 ++-- tests/repository_test.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml index 63e1dd4c6..364d47d8f 100644 --- a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml +++ b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml @@ -2,5 +2,5 @@ name: Ruby Hook entry: ruby_hook language: ruby - language_version: 2.5.1 + language_version: 3.1.0 files: \.rb$ diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index dc55456e8..29f3c802e 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -71,10 +71,10 @@ def test_install_ruby_default(fake_gem_prefix): @xfailif_windows # pragma: win32 no cover def test_install_ruby_with_version(fake_gem_prefix): - ruby.install_environment(fake_gem_prefix, '2.7.2', ()) + ruby.install_environment(fake_gem_prefix, '3.1.0', ()) # Should be able to activate and use rbenv install - with ruby.in_env(fake_gem_prefix, '2.7.2'): + with ruby.in_env(fake_gem_prefix, '3.1.0'): cmd_output('rbenv', 'install', '--help') diff --git a/tests/repository_test.py b/tests/repository_test.py index 6d50cb84c..8705d8860 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -335,7 +335,7 @@ def test_run_versioned_ruby_hook(tempdir_factory, store): tempdir_factory, store, 'ruby_versioned_hooks_repo', 'ruby_hook', [os.devnull], - b'2.5.1\nHello world from a ruby hook\n', + b'3.1.0\nHello world from a ruby hook\n', ) @@ -357,7 +357,7 @@ def test_run_ruby_hook_with_disable_shared_gems( tempdir_factory, store, 'ruby_versioned_hooks_repo', 'ruby_hook', [os.devnull], - b'2.5.1\nHello world from a ruby hook\n', + b'3.1.0\nHello world from a ruby hook\n', ) From 6ab7fc25d56872824252ff7357c2d7a754bfcb1e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 23:51:53 +0000 Subject: [PATCH 068/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee1492c6f..3aecdc13a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: [--py37-plus] From 52948f610c458cad95f60fbc7b0337780a98c9f0 Mon Sep 17 00:00:00 2001 From: Lorenz Walthert Date: Tue, 6 Dec 2022 12:23:47 +0100 Subject: [PATCH 069/342] When R executable is an explicit path, we need to appene `.exe` on Windows --- pre_commit/languages/r.py | 3 ++- tests/languages/r_test.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 22b5f2530..d281102b2 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -15,6 +15,7 @@ from pre_commit.prefix import Prefix from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b +from pre_commit.util import win_exe ENVIRONMENT_DIR = 'renv' RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') @@ -63,7 +64,7 @@ def _rscript_exec() -> str: if r_home is None: return 'Rscript' else: - return os.path.join(r_home, 'bin', 'Rscript') + return os.path.join(r_home, 'bin', win_exe('Rscript')) def _entry_validate(entry: Sequence[str]) -> None: diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index 5bc63b27c..c52d5acd3 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -6,6 +6,7 @@ from pre_commit import envcontext from pre_commit.languages import r +from pre_commit.util import win_exe from testing.fixtures import make_config_from_repo from testing.fixtures import make_repo from tests.repository_test import _get_hook_no_install @@ -133,7 +134,7 @@ def test_r_parsing_file_local(tempdir_factory, store): def test_rscript_exec_relative_to_r_home(): - expected = os.path.join('r_home_dir', 'bin', 'Rscript') + expected = os.path.join('r_home_dir', 'bin', win_exe('Rscript')) with envcontext.envcontext((('R_HOME', 'r_home_dir'),)): assert r._rscript_exec() == expected From a179808bfeb63094b2127402da8cb4eeccb5be2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 00:40:31 +0000 Subject: [PATCH 070/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/add-trailing-comma: v2.3.0 → v2.4.0](https://github.com/asottile/add-trailing-comma/compare/v2.3.0...v2.4.0) - [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3aecdc13a..c3b261bd1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,12 +20,12 @@ repos: exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py37-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.3.0 + rev: v2.4.0 hooks: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] From 94b617890624fc760a91289019ef4608e1a386fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 00:30:07 +0000 Subject: [PATCH 071/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-autopep8: v2.0.0 → v2.0.1](https://github.com/pre-commit/mirrors-autopep8/compare/v2.0.0...v2.0.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3b261bd1..7e58bdd81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.0 + rev: v2.0.1 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From e904628830490042426e411512bfb4d519de891b Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod Date: Tue, 6 Dec 2022 12:04:19 +0000 Subject: [PATCH 072/342] fix dotnet hooks with prefixes --- pre_commit/languages/dotnet.py | 32 ++++++++++++++++--- .../.gitignore | 3 ++ .../.pre-commit-hooks.yaml | 5 +++ .../Program.cs | 12 +++++++ .../dotnet_hooks_csproj_prefix_repo.csproj | 9 ++++++ tests/repository_test.py | 1 + 6 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore create mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml create mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs create mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 3983c6f0c..9ebda2f73 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -2,6 +2,9 @@ import contextlib import os.path +import re +import xml.etree.ElementTree +import zipfile from typing import Generator from typing import Sequence @@ -57,10 +60,29 @@ def install_environment( ), ) - # Determine tool from the packaged file ..nupkg - build_outputs = os.listdir(os.path.join(prefix.prefix_dir, build_dir)) - for output in build_outputs: - tool_name = output.split('.')[0] + nupkg_dir = prefix.path(build_dir) + nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')] + + if not nupkgs: + raise AssertionError('could not find any build outputs to install') + + for nupkg in nupkgs: + with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f: + nuspec, = (x for x in f.namelist() if x.endswith('.nuspec')) + with f.open(nuspec) as spec: + tree = xml.etree.ElementTree.parse(spec) + + namespace = re.match(r'{.*}', tree.getroot().tag) + if not namespace: + raise AssertionError('could not parse namespace from nuspec') + + tool_id_element = tree.find(f'.//{namespace[0]}id') + if tool_id_element is None: + raise AssertionError('expected to find an "id" element') + + tool_id = tool_id_element.text + if not tool_id: + raise AssertionError('"id" element missing tool name') # Install to bin dir helpers.run_setup_cmd( @@ -69,7 +91,7 @@ def install_environment( 'dotnet', 'tool', 'install', '--tool-path', os.path.join(envdir, BIN_DIR), '--add-source', build_dir, - tool_name, + tool_id, ), ) diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore b/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore new file mode 100644 index 000000000..edcd28f4a --- /dev/null +++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +nupkg/ diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml new file mode 100644 index 000000000..6626627d7 --- /dev/null +++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml @@ -0,0 +1,5 @@ +- id: dotnet-example-hook + name: dotnet example hook + entry: testeroni.tool + language: dotnet + files: '' diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs b/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs new file mode 100644 index 000000000..1456e8ef2 --- /dev/null +++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace dotnet_hooks_repo +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello from dotnet!"); + } + } +} diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj b/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj new file mode 100644 index 000000000..754b76006 --- /dev/null +++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj @@ -0,0 +1,9 @@ + + + Exe + net7.0 + true + testeroni.tool + ./nupkg + + diff --git a/tests/repository_test.py b/tests/repository_test.py index 8705d8860..c3936bf2f 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -1031,6 +1031,7 @@ def test_local_perl_additional_dependencies(store): 'dotnet_hooks_csproj_repo', 'dotnet_hooks_sln_repo', 'dotnet_hooks_combo_repo', + 'dotnet_hooks_csproj_prefix_repo', ), ) def test_dotnet_hook(tempdir_factory, store, repo): From c38e0c7ba8e8a98338a3ed492f83b896337244e6 Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod Date: Tue, 6 Dec 2022 12:45:44 +0000 Subject: [PATCH 073/342] dotnet: ignore nuget source during tool install --- pre_commit/languages/dotnet.py | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 9ebda2f73..e26b45c3a 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -3,6 +3,7 @@ import contextlib import os.path import re +import tempfile import xml.etree.ElementTree import zipfile from typing import Generator @@ -38,6 +39,22 @@ def in_env(prefix: Prefix) -> Generator[None, None, None]: yield +@contextlib.contextmanager +def _nuget_config_no_sources() -> Generator[str, None, None]: + with tempfile.TemporaryDirectory() as tmpdir: + nuget_config = os.path.join(tmpdir, 'nuget.config') + with open(nuget_config, 'w') as f: + f.write( + '' + '' + ' ' + ' ' + ' ' + '', + ) + yield nuget_config + + def install_environment( prefix: Prefix, version: str, @@ -85,15 +102,17 @@ def install_environment( raise AssertionError('"id" element missing tool name') # Install to bin dir - helpers.run_setup_cmd( - prefix, - ( - 'dotnet', 'tool', 'install', - '--tool-path', os.path.join(envdir, BIN_DIR), - '--add-source', build_dir, - tool_id, - ), - ) + with _nuget_config_no_sources() as nuget_config: + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'tool', 'install', + '--configfile', nuget_config, + '--tool-path', os.path.join(envdir, BIN_DIR), + '--add-source', build_dir, + tool_id, + ), + ) # Clean the git dir, ignoring the environment dir clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') From 40c5bdad65da4015af0e5ffe88227053109aecf3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 25 Dec 2022 17:52:05 -0500 Subject: [PATCH 074/342] v2.21.0 --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a7c8006..cd0de5f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +2.21.0 - 2022-12-25 +=================== + +### Features +- Require new-enough virtualenv to prevent 3.10 breakage + - #2467 PR by @asottile. +- Respect aliases with `SKIP` for environment install. + - #2480 PR by @kmARC. + - #2478 issue by @kmARC. +- Allow `pre-commit run --files` against unmerged paths. + - #2484 PR by @asottile. +- Also apply regex warnings to `repo: local` hooks. + - #2524 PR by @chrisRedwine. + - #2521 issue by @asottile. +- `rust` is now a "first class" language -- supporting `language_version` and + installation when not present. + - #2534 PR by @Holzhaus. +- `r` now uses more-reliable binary installation. + - #2460 PR by @lorenzwalthert. +- `GIT_ALLOW_PROTOCOL` is now passed through for git operations. + - #2555 PR by @asottile. +- `GIT_ASKPASS` is now passed through for git operations. + - #2564 PR by @mattp-. +- Remove `toml` dependency by using `cargo add` directly. + - #2568 PR by @m-rsha. +- Support `dotnet` hooks which have dotted prefixes. + - #2641 PR by @rkm. + - #2629 issue by @rkm. + +### Fixes +- Properly adjust `--commit-msg-filename` if run from a sub directory. + - #2459 PR by @asottile. +- Simplify `--intent-to-add` detection by using `git diff`. + - #2580 PR by @m-rsha. +- Fix `R.exe` selection on windows. + - #2605 PR by @lorenzwalthert. + - #2599 issue by @SInginc. +- Skip default `nuget` source when installing `dotnet` packages. + - #2642 PR by @rkm. + 2.20.0 - 2022-07-10 =================== diff --git a/setup.cfg b/setup.cfg index dd0f9c9a4..a89889217 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 2.20.0 +version = 2.21.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 524a23607217e115c2f1f51b3bbb869f186040ad Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 23 Dec 2022 18:37:33 -0500 Subject: [PATCH 075/342] drop python<3.8 --- .pre-commit-config.yaml | 4 ++-- azure-pipelines.yml | 6 +++--- pre_commit/constants.py | 9 ++------- setup.cfg | 3 +-- tox.ini | 2 +- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7e58bdd81..b7d7f1f0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) - args: [--py37-plus, --add-import, 'from __future__ import annotations'] + args: [--py38-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma rev: v2.4.0 hooks: @@ -28,7 +28,7 @@ repos: rev: v3.3.1 hooks: - id: pyupgrade - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v2.0.1 hooks: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 34c94f54a..911ef32d5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,7 +15,7 @@ resources: jobs: - template: job--python-tox.yml@asottile parameters: - toxenvs: [py37] + toxenvs: [py38] os: windows additional_variables: TEMP: C:\Temp @@ -34,7 +34,7 @@ jobs: displayName: install R - template: job--python-tox.yml@asottile parameters: - toxenvs: [py37] + toxenvs: [py38] os: linux name_postfix: _latest_git pre_test: @@ -52,7 +52,7 @@ jobs: displayName: install R - template: job--python-tox.yml@asottile parameters: - toxenvs: [py37, py38, py39] + toxenvs: [py38, py39, py310] os: linux pre_test: - task: UseRubyVersion@0 diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 5bc4ae98b..8fc5e55db 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -1,11 +1,6 @@ from __future__ import annotations -import sys - -if sys.version_info >= (3, 8): # pragma: >=3.8 cover - import importlib.metadata as importlib_metadata -else: # pragma: <3.8 cover - import importlib_metadata +import importlib.metadata CONFIG_FILE = '.pre-commit-config.yaml' MANIFEST_FILE = '.pre-commit-hooks.yaml' @@ -15,7 +10,7 @@ # Bump when modifying `empty_template` LOCAL_REPO_VERSION = '1' -VERSION = importlib_metadata.version('pre_commit') +VERSION = importlib.metadata.version('pre_commit') # `manual` is not invoked by any installed git hook. See #719 STAGES = ( diff --git a/setup.cfg b/setup.cfg index a89889217..1d28a41c8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,8 +24,7 @@ install_requires = nodeenv>=0.11.1 pyyaml>=5.1 virtualenv>=20.10.0 - importlib-metadata;python_version<"3.8" -python_requires = >=3.7 +python_requires = >=3.8 [options.packages.find] exclude = diff --git a/tox.ini b/tox.ini index e06be115b..a44f93d48 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38,pypy3,pre-commit +envlist = py,pypy3,pre-commit [testenv] deps = -rrequirements-dev.txt From 0024484f5b6b0b8a811c0bed4773c1fd28a98503 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 27 Dec 2022 11:15:45 -0500 Subject: [PATCH 076/342] remove support for top-level list format --- pre_commit/clientlib.py | 15 +-- tests/clientlib_test.py | 8 -- tests/commands/install_uninstall_test.py | 122 ++++++++++++----------- 3 files changed, 66 insertions(+), 79 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index da6ca2be2..df8d2e2d0 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -391,23 +391,10 @@ class InvalidConfigError(FatalError): pass -def ordered_load_normalize_legacy_config(contents: str) -> dict[str, Any]: - data = yaml_load(contents) - if isinstance(data, list): - logger.warning( - 'normalizing pre-commit configuration to a top-level map. ' - 'support for top level list will be removed in a future version. ' - 'run: `pre-commit migrate-config` to automatically fix this.', - ) - return {'repos': data} - else: - return data - - load_config = functools.partial( cfgv.load_from_filename, schema=CONFIG_SCHEMA, - load_strategy=ordered_load_normalize_legacy_config, + load_strategy=yaml_load, exc_tp=InvalidConfigError, ) diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index b4c3c4e06..23d9352f0 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -120,14 +120,6 @@ def test_validate_config_main_ok(): assert not validate_config_main(('.pre-commit-config.yaml',)) -def test_validate_config_old_list_format_ok(tmpdir, cap_out): - f = tmpdir.join('cfg.yaml') - f.write('- {repo: meta, hooks: [{id: identity}]}') - assert not validate_config_main((f.strpath,)) - msg = '[WARNING] normalizing pre-commit configuration to a top-level map' - assert msg in cap_out.get() - - def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): f = tmpdir.join('cfg.yaml') f.write( diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index 379c03a4f..e3943773f 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -739,20 +739,22 @@ def test_commit_msg_legacy(commit_msg_repo, tempdir_factory, store): def test_post_commit_integration(tempdir_factory, store): path = git_dir(tempdir_factory) - config = [ - { - 'repo': 'local', - 'hooks': [{ - 'id': 'post-commit', - 'name': 'Post commit', - 'entry': 'touch post-commit.tmp', - 'language': 'system', - 'always_run': True, - 'verbose': True, - 'stages': ['post-commit'], - }], - }, - ] + config = { + 'repos': [ + { + 'repo': 'local', + 'hooks': [{ + 'id': 'post-commit', + 'name': 'Post commit', + 'entry': 'touch post-commit.tmp', + 'language': 'system', + 'always_run': True, + 'verbose': True, + 'stages': ['post-commit'], + }], + }, + ], + } write_config(path, config) with cwd(path): _get_commit_output(tempdir_factory) @@ -765,20 +767,22 @@ def test_post_commit_integration(tempdir_factory, store): def test_post_merge_integration(tempdir_factory, store): path = git_dir(tempdir_factory) - config = [ - { - 'repo': 'local', - 'hooks': [{ - 'id': 'post-merge', - 'name': 'Post merge', - 'entry': 'touch post-merge.tmp', - 'language': 'system', - 'always_run': True, - 'verbose': True, - 'stages': ['post-merge'], - }], - }, - ] + config = { + 'repos': [ + { + 'repo': 'local', + 'hooks': [{ + 'id': 'post-merge', + 'name': 'Post merge', + 'entry': 'touch post-merge.tmp', + 'language': 'system', + 'always_run': True, + 'verbose': True, + 'stages': ['post-merge'], + }], + }, + ], + } write_config(path, config) with cwd(path): # create a simple diamond of commits for a non-trivial merge @@ -807,20 +811,22 @@ def test_post_merge_integration(tempdir_factory, store): def test_post_rewrite_integration(tempdir_factory, store): path = git_dir(tempdir_factory) - config = [ - { - 'repo': 'local', - 'hooks': [{ - 'id': 'post-rewrite', - 'name': 'Post rewrite', - 'entry': 'touch post-rewrite.tmp', - 'language': 'system', - 'always_run': True, - 'verbose': True, - 'stages': ['post-rewrite'], - }], - }, - ] + config = { + 'repos': [ + { + 'repo': 'local', + 'hooks': [{ + 'id': 'post-rewrite', + 'name': 'Post rewrite', + 'entry': 'touch post-rewrite.tmp', + 'language': 'system', + 'always_run': True, + 'verbose': True, + 'stages': ['post-rewrite'], + }], + }, + ], + } write_config(path, config) with cwd(path): open('init', 'a').close() @@ -836,21 +842,23 @@ def test_post_rewrite_integration(tempdir_factory, store): def test_post_checkout_integration(tempdir_factory, store): path = git_dir(tempdir_factory) - config = [ - { - 'repo': 'local', - 'hooks': [{ - 'id': 'post-checkout', - 'name': 'Post checkout', - 'entry': 'bash -c "echo ${PRE_COMMIT_TO_REF}"', - 'language': 'system', - 'always_run': True, - 'verbose': True, - 'stages': ['post-checkout'], - }], - }, - {'repo': 'meta', 'hooks': [{'id': 'identity'}]}, - ] + config = { + 'repos': [ + { + 'repo': 'local', + 'hooks': [{ + 'id': 'post-checkout', + 'name': 'Post checkout', + 'entry': 'bash -c "echo ${PRE_COMMIT_TO_REF}"', + 'language': 'system', + 'always_run': True, + 'verbose': True, + 'stages': ['post-checkout'], + }], + }, + {'repo': 'meta', 'hooks': [{'id': 'identity'}]}, + ], + } write_config(path, config) with cwd(path): cmd_output('git', 'add', '.') From ff3150d58a2ca9441ed6f0a9f3dcc2623301124a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 27 Dec 2022 11:29:00 -0500 Subject: [PATCH 077/342] remove support for sha to specify rev --- pre_commit/clientlib.py | 44 +++++------------------------------------ tests/clientlib_test.py | 43 ---------------------------------------- 2 files changed, 5 insertions(+), 82 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index da6ca2be2..bbb4915fc 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -114,8 +114,7 @@ def validate_manifest_main(argv: Sequence[str] | None = None) -> int: META = 'meta' -# should inherit from cfgv.Conditional if sha support is dropped -class WarnMutableRev(cfgv.ConditionalOptional): +class WarnMutableRev(cfgv.Conditional): def check(self, dct: dict[str, Any]) -> None: super().check(dct) @@ -171,36 +170,6 @@ def check(self, dct: dict[str, Any]) -> None: ) -class MigrateShaToRev: - key = 'rev' - - @staticmethod - def _cond(key: str) -> cfgv.Conditional: - return cfgv.Conditional( - key, cfgv.check_string, - condition_key='repo', - condition_value=cfgv.NotIn(LOCAL, META), - ensure_absent=True, - ) - - def check(self, dct: dict[str, Any]) -> None: - if dct.get('repo') in {LOCAL, META}: - self._cond('rev').check(dct) - self._cond('sha').check(dct) - elif 'sha' in dct and 'rev' in dct: - raise cfgv.ValidationError('Cannot specify both sha and rev') - elif 'sha' in dct: - self._cond('sha').check(dct) - else: - self._cond('rev').check(dct) - - def apply_default(self, dct: dict[str, Any]) -> None: - if 'sha' in dct: - dct['rev'] = dct.pop('sha') - - remove_default = cfgv.Required.remove_default - - def _entry(modname: str) -> str: """the hook `entry` is passed through `shlex.split()` by the command runner, so to prevent issues with spaces and backslashes (on Windows) @@ -324,14 +293,11 @@ def check(self, dct: dict[str, Any]) -> None: 'repo', META, ), - MigrateShaToRev(), WarnMutableRev( - 'rev', - cfgv.check_string, - '', - 'repo', - cfgv.NotIn(LOCAL, META), - True, + 'rev', cfgv.check_string, + condition_key='repo', + condition_value=cfgv.NotIn(LOCAL, META), + ensure_absent=True, ), cfgv.WarnAdditionalKeys(('repo', 'rev', 'hooks'), warn_unknown_keys_repo), ) diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index b4c3c4e06..985860465 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -14,7 +14,6 @@ from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION from pre_commit.clientlib import MANIFEST_SCHEMA from pre_commit.clientlib import META_HOOK_DICT -from pre_commit.clientlib import MigrateShaToRev from pre_commit.clientlib import OptionalSensibleRegexAtHook from pre_commit.clientlib import OptionalSensibleRegexAtTop from pre_commit.clientlib import validate_config_main @@ -425,48 +424,6 @@ def test_valid_manifests(manifest_obj, expected): assert ret is expected -@pytest.mark.parametrize( - 'dct', - ( - {'repo': 'local'}, {'repo': 'meta'}, - {'repo': 'wat', 'sha': 'wat'}, {'repo': 'wat', 'rev': 'wat'}, - ), -) -def test_migrate_sha_to_rev_ok(dct): - MigrateShaToRev().check(dct) - - -def test_migrate_sha_to_rev_dont_specify_both(): - with pytest.raises(cfgv.ValidationError) as excinfo: - MigrateShaToRev().check({'repo': 'a', 'sha': 'b', 'rev': 'c'}) - msg, = excinfo.value.args - assert msg == 'Cannot specify both sha and rev' - - -@pytest.mark.parametrize( - 'dct', - ( - {'repo': 'a'}, - {'repo': 'meta', 'sha': 'a'}, {'repo': 'meta', 'rev': 'a'}, - ), -) -def test_migrate_sha_to_rev_conditional_check_failures(dct): - with pytest.raises(cfgv.ValidationError): - MigrateShaToRev().check(dct) - - -def test_migrate_to_sha_apply_default(): - dct = {'repo': 'a', 'sha': 'b'} - MigrateShaToRev().apply_default(dct) - assert dct == {'repo': 'a', 'rev': 'b'} - - -def test_migrate_to_sha_ok(): - dct = {'repo': 'a', 'rev': 'b'} - MigrateShaToRev().apply_default(dct) - assert dct == {'repo': 'a', 'rev': 'b'} - - @pytest.mark.parametrize( 'config_repo', ( From 40e69ce8e3fed20290d031c8b660c74da5d4ca3d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 29 Aug 2022 18:58:03 -0400 Subject: [PATCH 078/342] use modules as protocols --- pre_commit/languages/all.py | 84 ++++++++++++++++++++++--------------- testing/gen-languages-all | 30 ------------- tests/repository_test.py | 16 ++++--- 3 files changed, 60 insertions(+), 70 deletions(-) delete mode 100755 testing/gen-languages-all diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index cfd42ce20..7c7c58bde 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -1,7 +1,6 @@ from __future__ import annotations -from typing import Callable -from typing import NamedTuple +from typing import Protocol from typing import Sequence from pre_commit.hook import Hook @@ -27,44 +26,61 @@ from pre_commit.prefix import Prefix -class Language(NamedTuple): - name: str +class Language(Protocol): # Use `None` for no installation / environment - ENVIRONMENT_DIR: str | None + @property + def ENVIRONMENT_DIR(self) -> str | None: ... # return a value to replace `'default` for `language_version` - get_default_version: Callable[[], str] + def get_default_version(self) -> str: ... + # return whether the environment is healthy (or should be rebuilt) - health_check: Callable[[Prefix, str], str | None] + def health_check( + self, + prefix: Prefix, + language_version: str, + ) -> str | None: + ... + # install a repository for the given language and language_version - install_environment: Callable[[Prefix, str, Sequence[str]], None] + def install_environment( + self, + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], + ) -> None: + ... + # execute a hook and return the exit code and output - run_hook: Callable[[Hook, Sequence[str], bool], tuple[int, bytes]] + def run_hook( + self, + hook: Hook, + file_args: Sequence[str], + color: bool, + ) -> tuple[int, bytes]: + ... -# TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018 -languages = { - # BEGIN GENERATED (testing/gen-languages-all) - 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, health_check=conda.health_check, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 - 'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, health_check=coursier.health_check, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501 - 'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, health_check=dart.health_check, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501 - 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, health_check=docker.health_check, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 - 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, health_check=docker_image.health_check, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 - 'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, health_check=dotnet.health_check, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501 - 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, health_check=fail.health_check, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 - 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, health_check=golang.health_check, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 - 'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, health_check=lua.health_check, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501 - 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, health_check=node.health_check, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 - 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, health_check=perl.health_check, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 - 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, health_check=pygrep.health_check, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 - 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, health_check=python.health_check, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 - 'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, health_check=r.health_check, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501 - 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, health_check=ruby.health_check, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 - 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, health_check=rust.health_check, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 - 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, health_check=script.health_check, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 - 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, health_check=swift.health_check, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 - 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, health_check=system.health_check, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 - # END GENERATED +languages: dict[str, Language] = { + 'conda': conda, + 'coursier': coursier, + 'dart': dart, + 'docker': docker, + 'docker_image': docker_image, + 'dotnet': dotnet, + 'fail': fail, + 'golang': golang, + 'lua': lua, + 'node': node, + 'perl': perl, + 'pygrep': pygrep, + 'python': python, + 'r': r, + 'ruby': ruby, + 'rust': rust, + 'script': script, + 'swift': swift, + 'system': system, + # TODO: fully deprecate `python_venv` + 'python_venv': python, } -# TODO: fully deprecate `python_venv` -languages['python_venv'] = languages['python'] all_languages = sorted(languages) diff --git a/testing/gen-languages-all b/testing/gen-languages-all deleted file mode 100755 index 05f892956..000000000 --- a/testing/gen-languages-all +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations - -import sys - -LANGUAGES = ( - 'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail', - 'golang', 'lua', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust', - 'script', 'swift', 'system', -) -FIELDS = ( - 'ENVIRONMENT_DIR', 'get_default_version', 'health_check', - 'install_environment', 'run_hook', -) - - -def main() -> int: - print(f' # BEGIN GENERATED ({sys.argv[0]})') - for lang in LANGUAGES: - parts = [f' {lang!r}: Language(name={lang!r}'] - for k in FIELDS: - parts.append(f', {k}={lang}.{k}') - parts.append('), # noqa: E501') - print(''.join(parts)) - print(' # END GENERATED') - return 0 - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/tests/repository_test.py b/tests/repository_test.py index c3936bf2f..6aa0f0073 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -133,9 +133,11 @@ def test_python_hook(tempdir_factory, store): def test_python_hook_default_version(tempdir_factory, store): # make sure that this continues to work for platforms where default # language detection does not work - returns_default = mock.Mock(return_value=C.DEFAULT) - lang = languages['python']._replace(get_default_version=returns_default) - with mock.patch.dict(languages, python=lang): + with mock.patch.object( + python, + 'get_default_version', + return_value=C.DEFAULT, + ): test_python_hook(tempdir_factory, store) @@ -247,9 +249,11 @@ def test_run_a_node_hook(tempdir_factory, store): def test_run_a_node_hook_default_version(tempdir_factory, store): # make sure that this continues to work for platforms where node is not # installed at the system - returns_default = mock.Mock(return_value=C.DEFAULT) - lang = languages['node']._replace(get_default_version=returns_default) - with mock.patch.dict(languages, node=lang): + with mock.patch.object( + node, + 'get_default_version', + return_value=C.DEFAULT, + ): test_run_a_node_hook(tempdir_factory, store) From 4a50859936b71a2f38c407a1eb56e74a6978c0a5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 27 Dec 2022 11:47:15 -0500 Subject: [PATCH 079/342] remove pre-commit-validate-config and pre-commit-validate-manifest --- pre_commit/clientlib.py | 39 ------------ pre_commit/commands/validate_config.py | 4 +- pre_commit/commands/validate_manifest.py | 4 +- setup.cfg | 2 - tests/clientlib_test.py | 78 ------------------------ tests/commands/validate_config_test.py | 64 +++++++++++++++++++ tests/commands/validate_manifest_test.py | 18 ++++++ 7 files changed, 88 insertions(+), 121 deletions(-) create mode 100644 tests/commands/validate_config_test.py create mode 100644 tests/commands/validate_manifest_test.py diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index df8d2e2d0..deb160a0e 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -1,6 +1,5 @@ from __future__ import annotations -import argparse import functools import logging import re @@ -13,12 +12,8 @@ from identify.identify import ALL_TAGS import pre_commit.constants as C -from pre_commit.color import add_color_option -from pre_commit.commands.validate_config import validate_config -from pre_commit.commands.validate_manifest import validate_manifest from pre_commit.errors import FatalError from pre_commit.languages.all import all_languages -from pre_commit.logging_handler import logging_handler from pre_commit.util import parse_version from pre_commit.util import yaml_load @@ -44,14 +39,6 @@ def check_min_version(version: str) -> None: ) -def _make_argparser(filenames_help: str) -> argparse.ArgumentParser: - parser = argparse.ArgumentParser() - parser.add_argument('filenames', nargs='*', help=filenames_help) - parser.add_argument('-V', '--version', action='version', version=C.VERSION) - add_color_option(parser) - return parser - - MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -97,19 +84,6 @@ class InvalidManifestError(FatalError): ) -def validate_manifest_main(argv: Sequence[str] | None = None) -> int: - parser = _make_argparser('Manifest filenames.') - args = parser.parse_args(argv) - - with logging_handler(args.color): - logger.warning( - 'pre-commit-validate-manifest is deprecated -- ' - 'use `pre-commit validate-manifest` instead.', - ) - - return validate_manifest(args.filenames) - - LOCAL = 'local' META = 'meta' @@ -397,16 +371,3 @@ class InvalidConfigError(FatalError): load_strategy=yaml_load, exc_tp=InvalidConfigError, ) - - -def validate_config_main(argv: Sequence[str] | None = None) -> int: - parser = _make_argparser('Config filenames.') - args = parser.parse_args(argv) - - with logging_handler(args.color): - logger.warning( - 'pre-commit-validate-config is deprecated -- ' - 'use `pre-commit validate-config` instead.', - ) - - return validate_config(args.filenames) diff --git a/pre_commit/commands/validate_config.py b/pre_commit/commands/validate_config.py index 91bb017a3..24bd3135e 100644 --- a/pre_commit/commands/validate_config.py +++ b/pre_commit/commands/validate_config.py @@ -1,9 +1,11 @@ from __future__ import annotations +from typing import Sequence + from pre_commit import clientlib -def validate_config(filenames: list[str]) -> int: +def validate_config(filenames: Sequence[str]) -> int: ret = 0 for filename in filenames: diff --git a/pre_commit/commands/validate_manifest.py b/pre_commit/commands/validate_manifest.py index 372a6380f..419031a9b 100644 --- a/pre_commit/commands/validate_manifest.py +++ b/pre_commit/commands/validate_manifest.py @@ -1,9 +1,11 @@ from __future__ import annotations +from typing import Sequence + from pre_commit import clientlib -def validate_manifest(filenames: list[str]) -> int: +def validate_manifest(filenames: Sequence[str]) -> int: ret = 0 for filename in filenames: diff --git a/setup.cfg b/setup.cfg index 1d28a41c8..ca1f7d8bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,8 +34,6 @@ exclude = [options.entry_points] console_scripts = pre-commit = pre_commit.main:main - pre-commit-validate-config = pre_commit.clientlib:validate_config_main - pre-commit-validate-manifest = pre_commit.clientlib:validate_manifest_main [options.package_data] pre_commit.resources = diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 23d9352f0..2afeaeced 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -17,8 +17,6 @@ from pre_commit.clientlib import MigrateShaToRev from pre_commit.clientlib import OptionalSensibleRegexAtHook from pre_commit.clientlib import OptionalSensibleRegexAtTop -from pre_commit.clientlib import validate_config_main -from pre_commit.clientlib import validate_manifest_main from testing.fixtures import sample_local_config @@ -112,70 +110,6 @@ def test_config_schema_does_not_contain_defaults(): assert not isinstance(item, cfgv.Optional) -def test_validate_manifest_main_ok(): - assert not validate_manifest_main(('.pre-commit-hooks.yaml',)) - - -def test_validate_config_main_ok(): - assert not validate_config_main(('.pre-commit-config.yaml',)) - - -def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): - f = tmpdir.join('cfg.yaml') - f.write( - 'repos:\n' - '- repo: https://gitlab.com/pycqa/flake8\n' - ' rev: 3.7.7\n' - ' hooks:\n' - ' - id: flake8\n' - ' args: [--some-args]\n', - ) - ret_val = validate_config_main((f.strpath,)) - assert not ret_val - assert caplog.record_tuples == [ - ( - 'pre_commit', - logging.WARNING, - 'pre-commit-validate-config is deprecated -- ' - 'use `pre-commit validate-config` instead.', - ), - ( - 'pre_commit', - logging.WARNING, - 'Unexpected key(s) present on https://gitlab.com/pycqa/flake8: ' - 'args', - ), - ] - - -def test_validate_warn_on_unknown_keys_at_top_level(tmpdir, caplog): - f = tmpdir.join('cfg.yaml') - f.write( - 'repos:\n' - '- repo: https://gitlab.com/pycqa/flake8\n' - ' rev: 3.7.7\n' - ' hooks:\n' - ' - id: flake8\n' - 'foo:\n' - ' id: 1.0.0\n', - ) - ret_val = validate_config_main((f.strpath,)) - assert not ret_val - assert caplog.record_tuples == [ - ( - 'pre_commit', - logging.WARNING, - 'pre-commit-validate-config is deprecated -- ' - 'use `pre-commit validate-config` instead.', - ), - ( - 'pre_commit', - logging.WARNING, - 'Unexpected key(s) present at root: foo', - ), - ] - - def test_ci_map_key_allowed_at_top_level(caplog): cfg = { 'ci': {'skip': ['foo']}, @@ -362,18 +296,6 @@ def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning): assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)] -@pytest.mark.parametrize('fn', (validate_config_main, validate_manifest_main)) -def test_mains_not_ok(tmpdir, fn): - not_yaml = tmpdir.join('f.notyaml') - not_yaml.write('{') - not_schema = tmpdir.join('notconfig.yaml') - not_schema.write('{}') - - assert fn(('does-not-exist',)) - assert fn((not_yaml.strpath,)) - assert fn((not_schema.strpath,)) - - @pytest.mark.parametrize( ('manifest_obj', 'expected'), ( diff --git a/tests/commands/validate_config_test.py b/tests/commands/validate_config_test.py new file mode 100644 index 000000000..a475cd814 --- /dev/null +++ b/tests/commands/validate_config_test.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import logging + +from pre_commit.commands.validate_config import validate_config + + +def test_validate_config_ok(): + assert not validate_config(('.pre-commit-config.yaml',)) + + +def test_validate_warn_on_unknown_keys_at_repo_level(tmpdir, caplog): + f = tmpdir.join('cfg.yaml') + f.write( + 'repos:\n' + '- repo: https://gitlab.com/pycqa/flake8\n' + ' rev: 3.7.7\n' + ' hooks:\n' + ' - id: flake8\n' + ' args: [--some-args]\n', + ) + ret_val = validate_config((f.strpath,)) + assert not ret_val + assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + 'Unexpected key(s) present on https://gitlab.com/pycqa/flake8: ' + 'args', + ), + ] + + +def test_validate_warn_on_unknown_keys_at_top_level(tmpdir, caplog): + f = tmpdir.join('cfg.yaml') + f.write( + 'repos:\n' + '- repo: https://gitlab.com/pycqa/flake8\n' + ' rev: 3.7.7\n' + ' hooks:\n' + ' - id: flake8\n' + 'foo:\n' + ' id: 1.0.0\n', + ) + ret_val = validate_config((f.strpath,)) + assert not ret_val + assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + 'Unexpected key(s) present at root: foo', + ), + ] + + +def test_mains_not_ok(tmpdir): + not_yaml = tmpdir.join('f.notyaml') + not_yaml.write('{') + not_schema = tmpdir.join('notconfig.yaml') + not_schema.write('{}') + + assert validate_config(('does-not-exist',)) + assert validate_config((not_yaml.strpath,)) + assert validate_config((not_schema.strpath,)) diff --git a/tests/commands/validate_manifest_test.py b/tests/commands/validate_manifest_test.py new file mode 100644 index 000000000..a4bc8ac05 --- /dev/null +++ b/tests/commands/validate_manifest_test.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from pre_commit.commands.validate_manifest import validate_manifest + + +def test_validate_manifest_ok(): + assert not validate_manifest(('.pre-commit-hooks.yaml',)) + + +def test_not_ok(tmpdir): + not_yaml = tmpdir.join('f.notyaml') + not_yaml.write('{') + not_schema = tmpdir.join('notconfig.yaml') + not_schema.write('{}') + + assert validate_manifest(('does-not-exist',)) + assert validate_manifest((not_yaml.strpath,)) + assert validate_manifest((not_schema.strpath,)) From 887c5e1142ea9022407e85e7319bec3a403e1572 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 29 Dec 2022 20:54:03 -0500 Subject: [PATCH 080/342] azure pipelines -> github actions --- .github/actions/pre-test/action.yml | 41 +++++++++++++++++ .github/workflows/main.yml | 23 ++++++++++ README.md | 3 +- azure-pipelines.yml | 68 ----------------------------- testing/get-coursier.sh | 2 +- testing/get-dart.sh | 4 +- testing/get-lua.sh | 5 --- testing/get-swift.sh | 2 +- 8 files changed, 69 insertions(+), 79 deletions(-) create mode 100644 .github/actions/pre-test/action.yml create mode 100644 .github/workflows/main.yml delete mode 100644 azure-pipelines.yml delete mode 100755 testing/get-lua.sh diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml new file mode 100644 index 000000000..f230df7b0 --- /dev/null +++ b/.github/actions/pre-test/action.yml @@ -0,0 +1,41 @@ +inputs: + env: + default: ${{ matrix.env }} + +runs: + using: composite + steps: + - name: setup (windows) + shell: bash + if: runner.os == 'Windows' + run: | + set -x + + echo 'TEMP=C:\TEMP' >> "$GITHUB_ENV" + + echo "$CONDA\Scripts" >> "$GITHUB_PATH" + + echo 'C:\Strawberry\perl\bin' >> "$GITHUB_PATH" + echo 'C:\Strawberry\perl\site\bin' >> "$GITHUB_PATH" + echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" + + testing/get-dart.sh + pwsh testing/get-r.ps1 + - name: setup (linux) + shell: bash + if: runner.os == 'Linux' + run: | + set -x + + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + lua5.3 \ + liblua5.3-dev \ + luarocks \ + r-base + + testing/get-coursier.sh + testing/get-dart.sh + testing/get-swift.sh + - uses: asottile/workflows/.github/actions/latest-git@v1.2.0 + if: inputs.env == 'py38' && runner.os == 'Linux' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..c78d1051c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,23 @@ +name: main + +on: + push: + branches: [main, test-me-*] + tags: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + main-windows: + uses: asottile/workflows/.github/workflows/tox.yml@v1.2.0 + with: + env: '["py38"]' + os: windows-latest + main-linux: + uses: asottile/workflows/.github/workflows/tox.yml@v1.2.0 + with: + env: '["py38", "py39", "py310"]' + os: ubuntu-latest diff --git a/README.md b/README.md index db1259c25..0c81a7890 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/pre-commit.pre-commit?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main) -[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main) +[![build status](https://github.com/pre-commit/pre-commit/actions/workflows/main.yml/badge.svg)](https://github.com/pre-commit/pre-commit/actions/workflows/main.yml) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit/main.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/main) ## pre-commit diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 911ef32d5..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,68 +0,0 @@ -trigger: - branches: - include: [main, test-me-*] - tags: - include: ['*'] - -resources: - repositories: - - repository: asottile - type: github - endpoint: github - name: asottile/azure-pipeline-templates - ref: refs/tags/v2.4.1 - -jobs: -- template: job--python-tox.yml@asottile - parameters: - toxenvs: [py38] - os: windows - additional_variables: - TEMP: C:\Temp - pre_test: - - task: UseRubyVersion@0 - - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" - displayName: Add conda to PATH - - powershell: | - Write-Host "##vso[task.prependpath]C:\Strawberry\perl\bin" - Write-Host "##vso[task.prependpath]C:\Strawberry\perl\site\bin" - Write-Host "##vso[task.prependpath]C:\Strawberry\c\bin" - displayName: Add strawberry perl to PATH - - bash: testing/get-dart.sh - displayName: install dart - - powershell: testing/get-r.ps1 - displayName: install R -- template: job--python-tox.yml@asottile - parameters: - toxenvs: [py38] - os: linux - name_postfix: _latest_git - pre_test: - - task: UseRubyVersion@0 - - template: step--git-install.yml - - bash: testing/get-coursier.sh - displayName: install coursier - - bash: testing/get-dart.sh - displayName: install dart - - bash: testing/get-lua.sh - displayName: install lua - - bash: testing/get-swift.sh - displayName: install swift - - bash: testing/get-r.sh - displayName: install R -- template: job--python-tox.yml@asottile - parameters: - toxenvs: [py38, py39, py310] - os: linux - pre_test: - - task: UseRubyVersion@0 - - bash: testing/get-coursier.sh - displayName: install coursier - - bash: testing/get-dart.sh - displayName: install dart - - bash: testing/get-lua.sh - displayName: install lua - - bash: testing/get-swift.sh - displayName: install swift - - bash: testing/get-r.sh - displayName: install R diff --git a/testing/get-coursier.sh b/testing/get-coursier.sh index 4c5e955de..6033c3e35 100755 --- a/testing/get-coursier.sh +++ b/testing/get-coursier.sh @@ -12,4 +12,4 @@ curl --location --silent --output "$ARTIFACT" "$COURSIER_URL" echo "$COURSIER_HASH $ARTIFACT" | sha256sum --check chmod ugo+x /tmp/coursier/cs -echo '##vso[task.prependpath]/tmp/coursier' +echo '/tmp/coursier' >> "$GITHUB_PATH" diff --git a/testing/get-dart.sh b/testing/get-dart.sh index b655e1a8d..998b9d98f 100755 --- a/testing/get-dart.sh +++ b/testing/get-dart.sh @@ -5,10 +5,10 @@ VERSION=2.13.4 if [ "$OSTYPE" = msys ]; then URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip" - echo "##vso[task.prependpath]$(cygpath -w /tmp/dart-sdk/bin)" + cygpath -w /tmp/dart-sdk/bin >> "$GITHUB_PATH" else URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-linux-x64-release.zip" - echo '##vso[task.prependpath]/tmp/dart-sdk/bin' + echo '/tmp/dart-sdk/bin' >> "$GITHUB_PATH" fi curl --silent --location --output /tmp/dart.zip "$URL" diff --git a/testing/get-lua.sh b/testing/get-lua.sh deleted file mode 100755 index 580e24772..000000000 --- a/testing/get-lua.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Install the runtime and package manager. -sudo apt install lua5.3 liblua5.3-dev luarocks diff --git a/testing/get-swift.sh b/testing/get-swift.sh index 3e7808241..dfe093912 100755 --- a/testing/get-swift.sh +++ b/testing/get-swift.sh @@ -26,4 +26,4 @@ fi mkdir -p /tmp/swift tar -xf "$TGZ" --strip 1 --directory /tmp/swift -echo '##vso[task.prependpath]/tmp/swift/usr/bin' +echo '/tmp/swift/usr/bin' >> "$GITHUB_PATH" From cddaa0dddc7e1fab506795287007aff50e88b592 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 29 Dec 2022 22:56:58 -0500 Subject: [PATCH 081/342] r is installed by default on GHA --- .github/actions/pre-test/action.yml | 4 +--- testing/get-r.ps1 | 6 ------ testing/get-r.sh | 9 --------- 3 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 testing/get-r.ps1 delete mode 100755 testing/get-r.sh diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml index f230df7b0..a7bf0abed 100644 --- a/.github/actions/pre-test/action.yml +++ b/.github/actions/pre-test/action.yml @@ -20,7 +20,6 @@ runs: echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" testing/get-dart.sh - pwsh testing/get-r.ps1 - name: setup (linux) shell: bash if: runner.os == 'Linux' @@ -31,8 +30,7 @@ runs: sudo apt-get install -y --no-install-recommends \ lua5.3 \ liblua5.3-dev \ - luarocks \ - r-base + luarocks testing/get-coursier.sh testing/get-dart.sh diff --git a/testing/get-r.ps1 b/testing/get-r.ps1 deleted file mode 100644 index e7b7b6195..000000000 --- a/testing/get-r.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -$dir = $Env:Temp -$urlR = "https://cran.r-project.org/bin/windows/base/old/4.0.4/R-4.0.4-win.exe" -$outputR = "$dir\R-win.exe" -$wcR = New-Object System.Net.WebClient -$wcR.DownloadFile($urlR, $outputR) -Start-Process -FilePath $outputR -ArgumentList "/S /v/qn" diff --git a/testing/get-r.sh b/testing/get-r.sh deleted file mode 100755 index 5d09828e4..000000000 --- a/testing/get-r.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -sudo apt install r-base -# create empty folder for user library. -# necessary for non-root users who have -# never installed an R package before. -# Alternatively, we require the renv -# package to be installed already, then we can -# omit that. -Rscript -e 'dir.create(Sys.getenv("R_LIBS_USER"), recursive = TRUE)' From 0a0754e44a3b6bc3d2e56353f5143d5905d45f97 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 17:12:28 -0500 Subject: [PATCH 082/342] special rmtree is not needed for TemporaryDirectory in 3.8+ --- pre_commit/commands/autoupdate.py | 4 ++-- pre_commit/commands/try_repo.py | 4 ++-- pre_commit/util.py | 13 ------------- tests/util_test.py | 7 ------- 4 files changed, 4 insertions(+), 24 deletions(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index d5352e5e7..6da53112e 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -2,6 +2,7 @@ import os.path import re +import tempfile from typing import Any from typing import NamedTuple from typing import Sequence @@ -19,7 +20,6 @@ from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b -from pre_commit.util import tmpdir from pre_commit.util import yaml_dump from pre_commit.util import yaml_load @@ -47,7 +47,7 @@ def update(self, tags_only: bool, freeze: bool) -> RevInfo: 'FETCH_HEAD', '--tags', '--exact', ) - with tmpdir() as tmp: + with tempfile.TemporaryDirectory() as tmp: git.init_repo(tmp, self.repo) cmd_output_b( *git_cmd, 'fetch', 'origin', 'HEAD', '--tags', diff --git a/pre_commit/commands/try_repo.py b/pre_commit/commands/try_repo.py index ef099f5e3..5244aeff4 100644 --- a/pre_commit/commands/try_repo.py +++ b/pre_commit/commands/try_repo.py @@ -3,6 +3,7 @@ import argparse import logging import os.path +import tempfile import pre_commit.constants as C from pre_commit import git @@ -11,7 +12,6 @@ from pre_commit.commands.run import run from pre_commit.store import Store from pre_commit.util import cmd_output_b -from pre_commit.util import tmpdir from pre_commit.util import yaml_dump from pre_commit.xargs import xargs @@ -49,7 +49,7 @@ def _repo_ref(tmpdir: str, repo: str, ref: str | None) -> tuple[str, str]: def try_repo(args: argparse.Namespace) -> int: - with tmpdir() as tempdir: + with tempfile.TemporaryDirectory() as tempdir: repo, ref = _repo_ref(tempdir, args.repo, args.ref) store = Store(tempdir) diff --git a/pre_commit/util.py b/pre_commit/util.py index b85076883..bca89bb74 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -9,7 +9,6 @@ import stat import subprocess import sys -import tempfile from types import TracebackType from typing import Any from typing import Callable @@ -52,18 +51,6 @@ def clean_path_on_failure(path: str) -> Generator[None, None, None]: raise -@contextlib.contextmanager -def tmpdir() -> Generator[str, None, None]: - """Contextmanager to create a temporary directory. It will be cleaned up - afterwards. - """ - tempdir = tempfile.mkdtemp() - try: - yield tempdir - finally: - rmtree(tempdir) - - def resource_bytesio(filename: str) -> IO[bytes]: return importlib.resources.open_binary('pre_commit.resources', filename) diff --git a/tests/util_test.py b/tests/util_test.py index b3f18b4cf..415982d01 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -14,7 +14,6 @@ from pre_commit.util import make_executable from pre_commit.util import parse_version from pre_commit.util import rmtree -from pre_commit.util import tmpdir def test_CalledProcessError_str(): @@ -74,12 +73,6 @@ class MySystemExit(SystemExit): assert not os.path.exists('foo') -def test_tmpdir(): - with tmpdir() as tempdir: - assert os.path.exists(tempdir) - assert not os.path.exists(tempdir) - - def test_cmd_output_exe_not_found(): ret, out, _ = cmd_output('dne', check=False) assert ret == 1 From 5425c754a0cdfe9f35df6d5de49c41bc9fb3413c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 17:17:00 -0500 Subject: [PATCH 083/342] move parse_version to pre_commit.clientlib --- pre_commit/clientlib.py | 6 +++++- pre_commit/repository.py | 2 +- pre_commit/util.py | 5 ----- tests/clientlib_test.py | 7 +++++++ tests/util_test.py | 7 ------- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 5b0bdbbdf..e03d5d666 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -14,7 +14,6 @@ import pre_commit.constants as C from pre_commit.errors import FatalError from pre_commit.languages.all import all_languages -from pre_commit.util import parse_version from pre_commit.util import yaml_load logger = logging.getLogger('pre_commit') @@ -30,6 +29,11 @@ def check_type_tag(tag: str) -> None: ) +def parse_version(s: str) -> tuple[int, ...]: + """poor man's version comparison""" + return tuple(int(p) for p in s.split('.')) + + def check_min_version(version: str) -> None: if parse_version(version) > parse_version(C.VERSION): raise cfgv.ValidationError( diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 4092277a8..7670f9970 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -10,12 +10,12 @@ from pre_commit.clientlib import load_manifest from pre_commit.clientlib import LOCAL from pre_commit.clientlib import META +from pre_commit.clientlib import parse_version from pre_commit.hook import Hook from pre_commit.languages.all import languages from pre_commit.languages.helpers import environment_dir from pre_commit.prefix import Prefix from pre_commit.store import Store -from pre_commit.util import parse_version from pre_commit.util import rmtree diff --git a/pre_commit/util.py b/pre_commit/util.py index b85076883..0aa6cccba 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -254,10 +254,5 @@ def handle_remove_readonly( shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly) -def parse_version(s: str) -> tuple[int, ...]: - """poor man's version comparison""" - return tuple(int(p) for p in s.split('.')) - - def win_exe(s: str) -> str: return s if sys.platform != 'win32' else f'{s}.exe' diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 12694e4d8..efb2aa84a 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -16,6 +16,7 @@ from pre_commit.clientlib import META_HOOK_DICT from pre_commit.clientlib import OptionalSensibleRegexAtHook from pre_commit.clientlib import OptionalSensibleRegexAtTop +from pre_commit.clientlib import parse_version from testing.fixtures import sample_local_config @@ -384,6 +385,12 @@ def test_default_language_version_invalid(mapping): cfgv.validate(mapping, DEFAULT_LANGUAGE_VERSION) +def test_parse_version(): + assert parse_version('0.0') == parse_version('0.0') + assert parse_version('0.1') > parse_version('0.0') + assert parse_version('2.1') >= parse_version('2') + + def test_minimum_pre_commit_version_failing(): with pytest.raises(cfgv.ValidationError) as excinfo: cfg = {'repos': [], 'minimum_pre_commit_version': '999'} diff --git a/tests/util_test.py b/tests/util_test.py index b3f18b4cf..26dafc34d 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -12,7 +12,6 @@ from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_p from pre_commit.util import make_executable -from pre_commit.util import parse_version from pre_commit.util import rmtree from pre_commit.util import tmpdir @@ -105,12 +104,6 @@ def test_cmd_output_no_shebang(tmpdir, fn): assert out.endswith(b'\n') -def test_parse_version(): - assert parse_version('0.0') == parse_version('0.0') - assert parse_version('0.1') > parse_version('0.0') - assert parse_version('2.1') >= parse_version('2') - - def test_rmtree_read_only_directories(tmpdir): """Simulates the go module tree. See #1042""" tmpdir.join('x/y/z').ensure_dir().join('a').ensure() From 8e57e8075dc4adcacf9b8dd49abc8c0b6e50f9e0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 18:14:55 -0500 Subject: [PATCH 084/342] avoid using hook.src in R language this wasn't meant to be read -- hook.prefix works fine for local too --- pre_commit/languages/r.py | 18 ++++----------- tests/languages/r_test.py | 48 ++++++++++++++------------------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index d281102b2..c050d451b 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -44,19 +44,11 @@ def _get_env_dir(prefix: Prefix, version: str) -> str: return prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) -def _prefix_if_non_local_file_entry( - entry: Sequence[str], - prefix: Prefix, - src: str, -) -> Sequence[str]: +def _prefix_if_file_entry(entry: list[str], prefix: Prefix) -> Sequence[str]: if entry[1] == '-e': return entry[1:] else: - if src == 'local': - path = entry[1] - else: - path = prefix.path(entry[1]) - return (path,) + return (prefix.path(entry[1]),) def _rscript_exec() -> str: @@ -67,7 +59,7 @@ def _rscript_exec() -> str: return os.path.join(r_home, 'bin', win_exe('Rscript')) -def _entry_validate(entry: Sequence[str]) -> None: +def _entry_validate(entry: list[str]) -> None: """ Allowed entries: # Rscript -e expr @@ -91,8 +83,8 @@ def _cmd_from_hook(hook: Hook) -> tuple[str, ...]: _entry_validate(entry) return ( - *entry[:1], *RSCRIPT_OPTS, - *_prefix_if_non_local_file_entry(entry, hook.prefix, hook.src), + entry[0], *RSCRIPT_OPTS, + *_prefix_if_file_entry(entry, hook.prefix), *hook.args, ) diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index c52d5acd3..c653a3ccf 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -16,27 +16,18 @@ def _test_r_parsing( tempdir_factory, store, hook_id, - expected_hook_expr={}, - expected_args={}, - config={}, - expect_path_prefix=True, + expected_hook_expr=(), + expected_args=(), + config=None, ): - repo_path = 'r_hooks_repo' - path = make_repo(tempdir_factory, repo_path) - config = config or make_config_from_repo(path) + repo = make_repo(tempdir_factory, 'r_hooks_repo') + config = make_config_from_repo(repo) hook = _get_hook_no_install(config, store, hook_id) ret = r._cmd_from_hook(hook) - expected_cmd = 'Rscript' - expected_opts = ( - '--no-save', '--no-restore', '--no-site-file', '--no-environ', - ) - expected_path = os.path.join( - hook.prefix.prefix_dir if expect_path_prefix else '', - f'{hook_id}.R', - ) + expected_path = os.path.join(hook.prefix.prefix_dir, f'{hook_id}.R') expected = ( - expected_cmd, - *expected_opts, + 'Rscript', + '--no-save', '--no-restore', '--no-site-file', '--no-environ', *(expected_hook_expr or (expected_path,)), *expected_args, ) @@ -84,9 +75,7 @@ def test_r_parsing_expr_no_opts_no_args2(tempdir_factory, store): def test_r_parsing_expr_opts_no_args2(tempdir_factory, store): with pytest.raises(ValueError) as execinfo: r._entry_validate( - [ - 'Rscript', '--vanilla', '-e', '1+1', '-e', 'letters', - ], + ['Rscript', '--vanilla', '-e', '1+1', '-e', 'letters'], ) msg = execinfo.value.args assert msg == ( @@ -112,24 +101,21 @@ def test_r_parsing_expr_non_Rscirpt(tempdir_factory, store): def test_r_parsing_file_local(tempdir_factory, store): - path = 'path/to/script.R' - hook_id = 'local-r' config = { 'repo': 'local', 'hooks': [{ - 'id': hook_id, + 'id': 'local-r', 'name': 'local-r', - 'entry': f'Rscript {path}', + 'entry': 'Rscript path/to/script.R', 'language': 'r', }], } - _test_r_parsing( - tempdir_factory, - store, - hook_id=hook_id, - expected_hook_expr=(path,), - config=config, - expect_path_prefix=False, + hook = _get_hook_no_install(config, store, 'local-r') + ret = r._cmd_from_hook(hook) + assert ret == ( + 'Rscript', + '--no-save', '--no-restore', '--no-site-file', '--no-environ', + hook.prefix.path('path/to/script.R'), ) From f0baffb01fe8efe200f103fccd4a5842860095cd Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 19:20:40 -0500 Subject: [PATCH 085/342] remove None overload for environment_dir --- pre_commit/languages/helpers.py | 14 ++------------ pre_commit/repository.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 0be08b54b..d462e86ca 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -6,7 +6,6 @@ import re from typing import Any from typing import NoReturn -from typing import overload from typing import Sequence import pre_commit.constants as C @@ -48,17 +47,8 @@ def run_setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None: cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs) -@overload -def environment_dir(d: None, language_version: str) -> None: ... -@overload -def environment_dir(d: str, language_version: str) -> str: ... - - -def environment_dir(d: str | None, language_version: str) -> str | None: - if d is None: - return None - else: - return f'{d}-{language_version}' +def environment_dir(d: str, language_version: str) -> str: + return f'{d}-{language_version}' def assert_version_default(binary: str, version: str) -> None: diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 7670f9970..50bc64552 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -50,15 +50,16 @@ def _write_state(prefix: Prefix, venv: str, state: object) -> None: def _hook_installed(hook: Hook) -> bool: lang = languages[hook.language] + if lang.ENVIRONMENT_DIR is None: + return True + venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) return ( - venv is None or ( - ( - _read_state(hook.prefix, venv) == - _state(hook.additional_dependencies) - ) and - not lang.health_check(hook.prefix, hook.language_version) - ) + ( + _read_state(hook.prefix, venv) == + _state(hook.additional_dependencies) + ) and + not lang.health_check(hook.prefix, hook.language_version) ) From d05b7888ab7fa4cc74f55e050f0f57442df2250d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 19:46:17 -0500 Subject: [PATCH 086/342] move clean_path_on_failure out of each hook install --- pre_commit/languages/conda.py | 16 ++--- pre_commit/languages/coursier.py | 30 ++++----- pre_commit/languages/dart.py | 56 ++++++++-------- pre_commit/languages/docker.py | 6 +- pre_commit/languages/dotnet.py | 106 +++++++++++++++---------------- pre_commit/languages/golang.py | 50 +++++++-------- pre_commit/languages/lua.py | 32 +++++----- pre_commit/languages/node.py | 55 ++++++++-------- pre_commit/languages/perl.py | 10 ++- pre_commit/languages/python.py | 8 +-- pre_commit/languages/r.py | 94 ++++++++++++++------------- pre_commit/languages/ruby.py | 50 +++++++-------- pre_commit/languages/rust.py | 38 ++++++----- pre_commit/languages/swift.py | 16 ++--- pre_commit/repository.py | 60 +++++++++-------- 15 files changed, 296 insertions(+), 331 deletions(-) diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index f0195e4f7..76ae0781f 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -13,7 +13,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'conda' @@ -71,16 +70,15 @@ def install_environment( conda_exe = _conda_exe() env_dir = prefix.path(directory) - with clean_path_on_failure(env_dir): + cmd_output_b( + conda_exe, 'env', 'create', '-p', env_dir, '--file', + 'environment.yml', cwd=prefix.prefix_dir, + ) + if additional_dependencies: cmd_output_b( - conda_exe, 'env', 'create', '-p', env_dir, '--file', - 'environment.yml', cwd=prefix.prefix_dir, + conda_exe, 'install', '-p', env_dir, *additional_dependencies, + cwd=prefix.prefix_dir, ) - if additional_dependencies: - cmd_output_b( - conda_exe, 'install', '-p', env_dir, *additional_dependencies, - cwd=prefix.prefix_dir, - ) def run_hook( diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 9fe43ebd8..0d520f0fb 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -12,7 +12,6 @@ from pre_commit.languages import helpers from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure ENVIRONMENT_DIR = 'coursier' @@ -38,21 +37,20 @@ def install_environment( envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) channel = prefix.path('.pre-commit-channel') - with clean_path_on_failure(envdir): - for app_descriptor in os.listdir(channel): - _, app_file = os.path.split(app_descriptor) - app, _ = os.path.splitext(app_file) - helpers.run_setup_cmd( - prefix, - ( - executable, - 'install', - '--default-channels=false', - f'--channel={channel}', - app, - f'--dir={envdir}', - ), - ) + for app_descriptor in os.listdir(channel): + _, app_file = os.path.split(app_descriptor) + app, _ = os.path.splitext(app_file) + helpers.run_setup_cmd( + prefix, + ( + executable, + 'install', + '--default-channels=false', + f'--channel={channel}', + app, + f'--dir={envdir}', + ), + ) def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 55ecbf4fd..73fffdb86 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -14,7 +14,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import win_exe from pre_commit.util import yaml_load @@ -67,38 +66,37 @@ def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: env=dart_env, ) - with clean_path_on_failure(envdir): - os.makedirs(bin_dir) + os.makedirs(bin_dir) - with tempfile.TemporaryDirectory() as tmp: - _install_dir(prefix, tmp) + with tempfile.TemporaryDirectory() as tmp: + _install_dir(prefix, tmp) - for dep_s in additional_dependencies: - with tempfile.TemporaryDirectory() as dep_tmp: - dep, _, version = dep_s.partition(':') - if version: - dep_cmd: tuple[str, ...] = (dep, '--version', version) - else: - dep_cmd = (dep,) + for dep_s in additional_dependencies: + with tempfile.TemporaryDirectory() as dep_tmp: + dep, _, version = dep_s.partition(':') + if version: + dep_cmd: tuple[str, ...] = (dep, '--version', version) + else: + dep_cmd = (dep,) - helpers.run_setup_cmd( - prefix, - ('dart', 'pub', 'cache', 'add', *dep_cmd), - env={**os.environ, 'PUB_CACHE': dep_tmp}, - ) + helpers.run_setup_cmd( + prefix, + ('dart', 'pub', 'cache', 'add', *dep_cmd), + env={**os.environ, 'PUB_CACHE': dep_tmp}, + ) - # try and find the 'pubspec.yaml' that just got added - for root, _, filenames in os.walk(dep_tmp): - if 'pubspec.yaml' in filenames: - with tempfile.TemporaryDirectory() as copied: - pkg = os.path.join(copied, 'pkg') - shutil.copytree(root, pkg) - _install_dir(Prefix(pkg), dep_tmp) - break - else: - raise AssertionError( - f'could not find pubspec.yaml for {dep_s}', - ) + # try and find the 'pubspec.yaml' that just got added + for root, _, filenames in os.walk(dep_tmp): + if 'pubspec.yaml' in filenames: + with tempfile.TemporaryDirectory() as copied: + pkg = os.path.join(copied, 'pkg') + shutil.copytree(root, pkg) + _install_dir(Prefix(pkg), dep_tmp) + break + else: + raise AssertionError( + f'could not find pubspec.yaml for {dep_s}', + ) def run_hook( diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index eea9f7682..5d6146740 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -10,7 +10,6 @@ from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'docker' @@ -101,9 +100,8 @@ def install_environment( # Docker doesn't really have relevant disk environment, but pre-commit # still needs to cleanup its state files on failure - with clean_path_on_failure(directory): - build_docker_image(prefix, pull=True) - os.mkdir(directory) + build_docker_image(prefix, pull=True) + os.mkdir(directory) def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index e26b45c3a..d748c8131 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -16,7 +16,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure ENVIRONMENT_DIR = 'dotnetenv' BIN_DIR = 'bin' @@ -64,59 +63,58 @@ def install_environment( helpers.assert_no_additional_deps('dotnet', additional_dependencies) envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) - with clean_path_on_failure(envdir): - build_dir = 'pre-commit-build' - - # Build & pack nupkg file - helpers.run_setup_cmd( - prefix, - ( - 'dotnet', 'pack', - '--configuration', 'Release', - '--output', build_dir, - ), - ) - - nupkg_dir = prefix.path(build_dir) - nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')] - - if not nupkgs: - raise AssertionError('could not find any build outputs to install') - - for nupkg in nupkgs: - with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f: - nuspec, = (x for x in f.namelist() if x.endswith('.nuspec')) - with f.open(nuspec) as spec: - tree = xml.etree.ElementTree.parse(spec) - - namespace = re.match(r'{.*}', tree.getroot().tag) - if not namespace: - raise AssertionError('could not parse namespace from nuspec') - - tool_id_element = tree.find(f'.//{namespace[0]}id') - if tool_id_element is None: - raise AssertionError('expected to find an "id" element') - - tool_id = tool_id_element.text - if not tool_id: - raise AssertionError('"id" element missing tool name') - - # Install to bin dir - with _nuget_config_no_sources() as nuget_config: - helpers.run_setup_cmd( - prefix, - ( - 'dotnet', 'tool', 'install', - '--configfile', nuget_config, - '--tool-path', os.path.join(envdir, BIN_DIR), - '--add-source', build_dir, - tool_id, - ), - ) - - # Clean the git dir, ignoring the environment dir - clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') - helpers.run_setup_cmd(prefix, clean_cmd) + build_dir = 'pre-commit-build' + + # Build & pack nupkg file + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'pack', + '--configuration', 'Release', + '--output', build_dir, + ), + ) + + nupkg_dir = prefix.path(build_dir) + nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')] + + if not nupkgs: + raise AssertionError('could not find any build outputs to install') + + for nupkg in nupkgs: + with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f: + nuspec, = (x for x in f.namelist() if x.endswith('.nuspec')) + with f.open(nuspec) as spec: + tree = xml.etree.ElementTree.parse(spec) + + namespace = re.match(r'{.*}', tree.getroot().tag) + if not namespace: + raise AssertionError('could not parse namespace from nuspec') + + tool_id_element = tree.find(f'.//{namespace[0]}id') + if tool_id_element is None: + raise AssertionError('expected to find an "id" element') + + tool_id = tool_id_element.text + if not tool_id: + raise AssertionError('"id" element missing tool name') + + # Install to bin dir + with _nuget_config_no_sources() as nuget_config: + helpers.run_setup_cmd( + prefix, + ( + 'dotnet', 'tool', 'install', + '--configfile', nuget_config, + '--tool-path', os.path.join(envdir, BIN_DIR), + '--add-source', build_dir, + tool_id, + ), + ) + + # Clean the git dir, ignoring the environment dir + clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') + helpers.run_setup_cmd(prefix, clean_cmd) def run_hook( diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index a5f9dba02..36792393a 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -14,7 +14,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b from pre_commit.util import rmtree @@ -65,31 +64,30 @@ def install_environment( helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), ) - with clean_path_on_failure(directory): - remote = git.get_remote_url(prefix.prefix_dir) - repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote)) - - # Clone into the goenv we'll create - cmd = ('git', 'clone', '--recursive', '.', repo_src_dir) - helpers.run_setup_cmd(prefix, cmd) - - if sys.platform == 'cygwin': # pragma: no cover - _, gopath, _ = cmd_output('cygpath', '-w', directory) - gopath = gopath.strip() - else: - gopath = directory - env = dict(os.environ, GOPATH=gopath) - env.pop('GOBIN', None) - cmd_output_b('go', 'install', './...', cwd=repo_src_dir, env=env) - for dependency in additional_dependencies: - cmd_output_b( - 'go', 'install', dependency, cwd=repo_src_dir, env=env, - ) - # Same some disk space, we don't need these after installation - rmtree(prefix.path(directory, 'src')) - pkgdir = prefix.path(directory, 'pkg') - if os.path.exists(pkgdir): # pragma: no cover (go<1.10) - rmtree(pkgdir) + remote = git.get_remote_url(prefix.prefix_dir) + repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote)) + + # Clone into the goenv we'll create + cmd = ('git', 'clone', '--recursive', '.', repo_src_dir) + helpers.run_setup_cmd(prefix, cmd) + + if sys.platform == 'cygwin': # pragma: no cover + _, gopath, _ = cmd_output('cygpath', '-w', directory) + gopath = gopath.strip() + else: + gopath = directory + env = dict(os.environ, GOPATH=gopath) + env.pop('GOBIN', None) + cmd_output_b('go', 'install', './...', cwd=repo_src_dir, env=env) + for dependency in additional_dependencies: + cmd_output_b( + 'go', 'install', dependency, cwd=repo_src_dir, env=env, + ) + # Same some disk space, we don't need these after installation + rmtree(prefix.path(directory, 'src')) + pkgdir = prefix.path(directory, 'pkg') + if os.path.exists(pkgdir): # pragma: no cover (go<1.10) + rmtree(pkgdir) def run_hook( diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index 49aa7308c..cd38a2974 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -13,7 +13,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output ENVIRONMENT_DIR = 'lua_env' @@ -64,22 +63,21 @@ def install_environment( helpers.assert_version_default('lua', version) envdir = _envdir(prefix) - with clean_path_on_failure(envdir): - with in_env(prefix): - # luarocks doesn't bootstrap a tree prior to installing - # so ensure the directory exists. - os.makedirs(envdir, exist_ok=True) - - # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg - for rockspec in prefix.star('.rockspec'): - make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec) - helpers.run_setup_cmd(prefix, make_cmd) - - # luarocks can't install multiple packages at once - # so install them individually. - for dependency in additional_dependencies: - cmd = ('luarocks', '--tree', envdir, 'install', dependency) - helpers.run_setup_cmd(prefix, cmd) + with in_env(prefix): + # luarocks doesn't bootstrap a tree prior to installing + # so ensure the directory exists. + os.makedirs(envdir, exist_ok=True) + + # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg + for rockspec in prefix.star('.rockspec'): + make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec) + helpers.run_setup_cmd(prefix, make_cmd) + + # luarocks can't install multiple packages at once + # so install them individually. + for dependency in additional_dependencies: + cmd = ('luarocks', '--tree', envdir, 'install', dependency) + helpers.run_setup_cmd(prefix, cmd) def run_hook( diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 37a5b63f1..353fa1522 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -16,7 +16,6 @@ from pre_commit.languages import helpers from pre_commit.languages.python import bin_dir from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b from pre_commit.util import rmtree @@ -85,41 +84,37 @@ def health_check(prefix: Prefix, language_version: str) -> str | None: def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: - additional_dependencies = tuple(additional_dependencies) assert prefix.exists('package.json') envdir = _envdir(prefix, version) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath if sys.platform == 'win32': # pragma: no cover envdir = fr'\\?\{os.path.normpath(envdir)}' - with clean_path_on_failure(envdir): - cmd = [ - sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir, - ] - if version != C.DEFAULT: - cmd.extend(['-n', version]) - cmd_output_b(*cmd) - - with in_env(prefix, version): - # https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449 - # install as if we installed from git - - local_install_cmd = ( - 'npm', 'install', '--dev', '--prod', - '--ignore-prepublish', '--no-progress', '--no-save', - ) - helpers.run_setup_cmd(prefix, local_install_cmd) - - _, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir) - pkg = prefix.path(pkg.strip()) - - install = ('npm', 'install', '-g', pkg, *additional_dependencies) - helpers.run_setup_cmd(prefix, install) - - # clean these up after installation - if prefix.exists('node_modules'): # pragma: win32 no cover - rmtree(prefix.path('node_modules')) - os.remove(pkg) + cmd = [sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir] + if version != C.DEFAULT: + cmd.extend(['-n', version]) + cmd_output_b(*cmd) + + with in_env(prefix, version): + # https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449 + # install as if we installed from git + + local_install_cmd = ( + 'npm', 'install', '--dev', '--prod', + '--ignore-prepublish', '--no-progress', '--no-save', + ) + helpers.run_setup_cmd(prefix, local_install_cmd) + + _, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir) + pkg = prefix.path(pkg.strip()) + + install = ('npm', 'install', '-g', pkg, *additional_dependencies) + helpers.run_setup_cmd(prefix, install) + + # clean these up after installation + if prefix.exists('node_modules'): # pragma: win32 no cover + rmtree(prefix.path('node_modules')) + os.remove(pkg) def run_hook( diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 78bd65a2b..25c016766 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -12,7 +12,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure ENVIRONMENT_DIR = 'perl_env' get_default_version = helpers.basic_get_default_version @@ -52,11 +51,10 @@ def install_environment( ) -> None: helpers.assert_version_default('perl', version) - with clean_path_on_failure(_envdir(prefix, version)): - with in_env(prefix, version): - helpers.run_setup_cmd( - prefix, ('cpan', '-T', '.', *additional_dependencies), - ) + with in_env(prefix, version): + helpers.run_setup_cmd( + prefix, ('cpan', '-T', '.', *additional_dependencies), + ) def run_hook( diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 19fa247ef..6770499de 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -17,7 +17,6 @@ from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b from pre_commit.util import win_exe @@ -215,10 +214,9 @@ def install_environment( venv_cmd.extend(('-p', python)) install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies) - with clean_path_on_failure(envdir): - cmd_output_b(*venv_cmd, cwd='/') - with in_env(prefix, version): - helpers.run_setup_cmd(prefix, install_cmd) + cmd_output_b(*venv_cmd, cwd='/') + with in_env(prefix, version): + helpers.run_setup_cmd(prefix, install_cmd) def run_hook( diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index c050d451b..9bbfdbe2c 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -13,7 +13,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b from pre_commit.util import win_exe @@ -95,54 +94,53 @@ def install_environment( additional_dependencies: Sequence[str], ) -> None: env_dir = _get_env_dir(prefix, version) - with clean_path_on_failure(env_dir): - os.makedirs(env_dir, exist_ok=True) - shutil.copy(prefix.path('renv.lock'), env_dir) - shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) - - r_code_inst_environment = f"""\ - prefix_dir <- {prefix.prefix_dir!r} - options( - repos = c(CRAN = "https://cran.rstudio.com"), - renv.consent = TRUE - ) - source("renv/activate.R") - renv::restore() - activate_statement <- paste0( - 'suppressWarnings({{', - 'old <- setwd("', getwd(), '"); ', - 'source("renv/activate.R"); ', - 'setwd(old); ', - 'renv::load("', getwd(), '");}})' - ) - writeLines(activate_statement, 'activate.R') - is_package <- tryCatch( - {{ - path_desc <- file.path(prefix_dir, 'DESCRIPTION') - suppressWarnings(desc <- read.dcf(path_desc)) - "Package" %in% colnames(desc) - }}, - error = function(...) FALSE - ) - if (is_package) {{ - renv::install(prefix_dir) - }} - """ - - cmd_output_b( - _rscript_exec(), '--vanilla', '-e', - _inline_r_setup(r_code_inst_environment), - cwd=env_dir, + os.makedirs(env_dir, exist_ok=True) + shutil.copy(prefix.path('renv.lock'), env_dir) + shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) + + r_code_inst_environment = f"""\ + prefix_dir <- {prefix.prefix_dir!r} + options( + repos = c(CRAN = "https://cran.rstudio.com"), + renv.consent = TRUE + ) + source("renv/activate.R") + renv::restore() + activate_statement <- paste0( + 'suppressWarnings({{', + 'old <- setwd("', getwd(), '"); ', + 'source("renv/activate.R"); ', + 'setwd(old); ', + 'renv::load("', getwd(), '");}})' ) - if additional_dependencies: - r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))' - with in_env(prefix, version): - cmd_output_b( - _rscript_exec(), *RSCRIPT_OPTS, '-e', - _inline_r_setup(r_code_inst_add), - *additional_dependencies, - cwd=env_dir, - ) + writeLines(activate_statement, 'activate.R') + is_package <- tryCatch( + {{ + path_desc <- file.path(prefix_dir, 'DESCRIPTION') + suppressWarnings(desc <- read.dcf(path_desc)) + "Package" %in% colnames(desc) + }}, + error = function(...) FALSE + ) + if (is_package) {{ + renv::install(prefix_dir) + }} + """ + + cmd_output_b( + _rscript_exec(), '--vanilla', '-e', + _inline_r_setup(r_code_inst_environment), + cwd=env_dir, + ) + if additional_dependencies: + r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))' + with in_env(prefix, version): + cmd_output_b( + _rscript_exec(), *RSCRIPT_OPTS, '-e', + _inline_r_setup(r_code_inst_add), + *additional_dependencies, + cwd=env_dir, + ) def _inline_r_setup(code: str) -> str: diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 8955dd011..379427b02 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -17,7 +17,6 @@ from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError -from pre_commit.util import clean_path_on_failure from pre_commit.util import resource_bytesio ENVIRONMENT_DIR = 'rbenv' @@ -115,33 +114,30 @@ def _install_ruby( def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: - additional_dependencies = tuple(additional_dependencies) - directory = helpers.environment_dir(ENVIRONMENT_DIR, version) - with clean_path_on_failure(prefix.path(directory)): - if version != 'system': # pragma: win32 no cover - _install_rbenv(prefix, version) - with in_env(prefix, version): - # Need to call this before installing so rbenv's directories - # are set up - helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-')) - if version != C.DEFAULT: - _install_ruby(prefix, version) - # Need to call this after installing to set up the shims - helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) - + if version != 'system': # pragma: win32 no cover + _install_rbenv(prefix, version) with in_env(prefix, version): - helpers.run_setup_cmd( - prefix, ('gem', 'build', *prefix.star('.gemspec')), - ) - helpers.run_setup_cmd( - prefix, - ( - 'gem', 'install', - '--no-document', '--no-format-executable', - '--no-user-install', - *prefix.star('.gem'), *additional_dependencies, - ), - ) + # Need to call this before installing so rbenv's directories + # are set up + helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-')) + if version != C.DEFAULT: + _install_ruby(prefix, version) + # Need to call this after installing to set up the shims + helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) + + with in_env(prefix, version): + helpers.run_setup_cmd( + prefix, ('gem', 'build', *prefix.star('.gemspec')), + ) + helpers.run_setup_cmd( + prefix, + ( + 'gem', 'install', + '--no-document', '--no-format-executable', + '--no-user-install', + *prefix.star('.gem'), *additional_dependencies, + ), + ) def run_hook( diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 204f2aa79..67e7ae85c 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -18,7 +18,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b from pre_commit.util import make_executable from pre_commit.util import win_exe @@ -143,28 +142,27 @@ def install_environment( } lib_deps = set(additional_dependencies) - cli_deps - with clean_path_on_failure(directory): - packages_to_install: set[tuple[str, ...]] = {('--path', '.')} - for cli_dep in cli_deps: - cli_dep = cli_dep[len('cli:'):] - package, _, crate_version = cli_dep.partition(':') - if crate_version != '': - packages_to_install.add((package, '--version', crate_version)) - else: - packages_to_install.add((package,)) + packages_to_install: set[tuple[str, ...]] = {('--path', '.')} + for cli_dep in cli_deps: + cli_dep = cli_dep[len('cli:'):] + package, _, crate_version = cli_dep.partition(':') + if crate_version != '': + packages_to_install.add((package, '--version', crate_version)) + else: + packages_to_install.add((package,)) - with in_env(prefix, version): - if version != 'system': - install_rust_with_toolchain(_rust_toolchain(version)) + with in_env(prefix, version): + if version != 'system': + install_rust_with_toolchain(_rust_toolchain(version)) - if len(lib_deps) > 0: - _add_dependencies(prefix, lib_deps) + if len(lib_deps) > 0: + _add_dependencies(prefix, lib_deps) - for args in packages_to_install: - cmd_output_b( - 'cargo', 'install', '--bins', '--root', directory, *args, - cwd=prefix.prefix_dir, - ) + for args in packages_to_install: + cmd_output_b( + 'cargo', 'install', '--bins', '--root', directory, *args, + cwd=prefix.prefix_dir, + ) def run_hook( diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index 4c687030c..0fab596cc 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -12,7 +12,6 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix -from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'swift_env' @@ -46,14 +45,13 @@ def install_environment( ) # Build the swift package - with clean_path_on_failure(directory): - os.mkdir(directory) - cmd_output_b( - 'swift', 'build', - '-C', prefix.prefix_dir, - '-c', BUILD_CONFIG, - '--build-path', os.path.join(directory, BUILD_DIR), - ) + os.mkdir(directory) + cmd_output_b( + 'swift', 'build', + '-C', prefix.prefix_dir, + '-c', BUILD_CONFIG, + '--build-path', os.path.join(directory, BUILD_DIR), + ) def run_hook( diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 50bc64552..fa5322dc1 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -16,6 +16,7 @@ from pre_commit.languages.helpers import environment_dir from pre_commit.prefix import Prefix from pre_commit.store import Store +from pre_commit.util import clean_path_on_failure from pre_commit.util import rmtree @@ -26,12 +27,12 @@ def _state(additional_deps: Sequence[str]) -> object: return {'additional_dependencies': sorted(additional_deps)} -def _state_filename(prefix: Prefix, venv: str) -> str: - return prefix.path(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') +def _state_filename(venv: str) -> str: + return os.path.join(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') -def _read_state(prefix: Prefix, venv: str) -> object | None: - filename = _state_filename(prefix, venv) +def _read_state(venv: str) -> object | None: + filename = _state_filename(venv) if not os.path.exists(filename): return None else: @@ -39,26 +40,15 @@ def _read_state(prefix: Prefix, venv: str) -> object | None: return json.load(f) -def _write_state(prefix: Prefix, venv: str, state: object) -> None: - state_filename = _state_filename(prefix, venv) - staging = f'{state_filename}staging' - with open(staging, 'w') as state_file: - state_file.write(json.dumps(state)) - # Move the file into place atomically to indicate we've installed - os.replace(staging, state_filename) - - def _hook_installed(hook: Hook) -> bool: lang = languages[hook.language] if lang.ENVIRONMENT_DIR is None: return True venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) + venv = hook.prefix.path(venv) return ( - ( - _read_state(hook.prefix, venv) == - _state(hook.additional_dependencies) - ) and + _read_state(venv) == _state(hook.additional_dependencies) and not lang.health_check(hook.prefix, hook.language_version) ) @@ -70,26 +60,34 @@ def _hook_install(hook: Hook) -> None: lang = languages[hook.language] assert lang.ENVIRONMENT_DIR is not None + venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) + venv = hook.prefix.path(venv) # There's potentially incomplete cleanup from previous runs # Clean it up! - if hook.prefix.exists(venv): - rmtree(hook.prefix.path(venv)) + if os.path.exists(venv): + rmtree(venv) - lang.install_environment( - hook.prefix, hook.language_version, hook.additional_dependencies, - ) - health_error = lang.health_check(hook.prefix, hook.language_version) - if health_error: - raise AssertionError( - f'BUG: expected environment for {hook.language} to be healthy ' - f'immediately after install, please open an issue describing ' - f'your environment\n\n' - f'more info:\n\n{health_error}', + with clean_path_on_failure(venv): + lang.install_environment( + hook.prefix, hook.language_version, hook.additional_dependencies, ) - # Write our state to indicate we're installed - _write_state(hook.prefix, venv, _state(hook.additional_dependencies)) + health_error = lang.health_check(hook.prefix, hook.language_version) + if health_error: + raise AssertionError( + f'BUG: expected environment for {hook.language} to be healthy ' + f'immediately after install, please open an issue describing ' + f'your environment\n\n' + f'more info:\n\n{health_error}', + ) + # Write our state to indicate we're installed + state_filename = _state_filename(venv) + staging = f'{state_filename}staging' + with open(staging, 'w') as state_file: + state_file.write(json.dumps(_state(hook.additional_dependencies))) + # Move the file into place atomically to indicate we've installed + os.replace(staging, state_filename) def _hook( From 05c8911363a84ec062c2ccfde6d1279f0b5634b3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 21:11:56 -0500 Subject: [PATCH 087/342] simplify environment_dir --- pre_commit/languages/conda.py | 6 ++--- pre_commit/languages/coursier.py | 11 ++++---- pre_commit/languages/dart.py | 5 ++-- pre_commit/languages/docker.py | 4 +-- pre_commit/languages/dotnet.py | 5 ++-- pre_commit/languages/golang.py | 8 ++---- pre_commit/languages/helpers.py | 4 +-- pre_commit/languages/lua.py | 10 +++----- pre_commit/languages/node.py | 10 +++----- pre_commit/languages/perl.py | 8 ++---- pre_commit/languages/python.py | 8 +++--- pre_commit/languages/r.py | 8 ++---- pre_commit/languages/ruby.py | 10 +++----- pre_commit/languages/rust.py | 14 +++-------- pre_commit/languages/swift.py | 12 +++------ pre_commit/repository.py | 14 ++++++++--- tests/repository_test.py | 43 +++++++++++++++++++------------- 17 files changed, 77 insertions(+), 103 deletions(-) diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 76ae0781f..5a0a720f3 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -44,8 +44,7 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) - envdir = prefix.path(directory) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) with envcontext(get_env_patch(envdir)): yield @@ -65,11 +64,10 @@ def install_environment( additional_dependencies: Sequence[str], ) -> None: helpers.assert_version_default('conda', version) - directory = helpers.environment_dir(ENVIRONMENT_DIR, version) conda_exe = _conda_exe() - env_dir = prefix.path(directory) + env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) cmd_output_b( conda_exe, 'env', 'create', '-p', env_dir, '--file', 'environment.yml', cwd=prefix.prefix_dir, diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 0d520f0fb..fdea3cd71 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -35,7 +35,7 @@ def install_environment( 'executables in the application search path', ) - envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) channel = prefix.path('.pre-commit-channel') for app_descriptor in os.listdir(channel): _, app_file = os.path.split(app_descriptor) @@ -62,11 +62,10 @@ def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager def in_env( prefix: Prefix, + language_version: str, ) -> Generator[None, None, None]: # pragma: win32 no cover - target_dir = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, get_default_version()), - ) - with envcontext(get_env_patch(target_dir)): + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) + with envcontext(get_env_patch(envdir)): yield @@ -75,5 +74,5 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - with in_env(hook.prefix): + with in_env(hook.prefix, hook.language_version): return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 73fffdb86..9fbb63cc0 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -31,8 +31,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix) -> Generator[None, None, None]: - directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) - envdir = prefix.path(directory) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) with envcontext(get_env_patch(envdir)): yield @@ -44,7 +43,7 @@ def install_environment( ) -> None: helpers.assert_version_default('dart', version) - envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) bin_dir = os.path.join(envdir, 'bin') def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 5d6146740..c51cf7c10 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -94,9 +94,7 @@ def install_environment( helpers.assert_version_default('docker', version) helpers.assert_no_additional_deps('docker', additional_dependencies) - directory = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) + directory = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) # Docker doesn't really have relevant disk environment, but pre-commit # still needs to cleanup its state files on failure diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index d748c8131..0bb0210cd 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -32,8 +32,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix) -> Generator[None, None, None]: - directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) - envdir = prefix.path(directory) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) with envcontext(get_env_patch(envdir)): yield @@ -62,7 +61,7 @@ def install_environment( helpers.assert_version_default('dotnet', version) helpers.assert_no_additional_deps('dotnet', additional_dependencies) - envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) build_dir = 'pre-commit-build' # Build & pack nupkg file diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 36792393a..70f0e65d4 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -31,9 +31,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) with envcontext(get_env_patch(envdir)): yield @@ -60,9 +58,7 @@ def install_environment( additional_dependencies: Sequence[str], ) -> None: helpers.assert_version_default('golang', version) - directory = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) + directory = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) remote = git.get_remote_url(prefix.prefix_dir) repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote)) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index d462e86ca..098e95c5a 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -47,8 +47,8 @@ def run_setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None: cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs) -def environment_dir(d: str, language_version: str) -> str: - return f'{d}-{language_version}' +def environment_dir(prefix: Prefix, d: str, language_version: str) -> str: + return prefix.path(f'{d}-{language_version}') def assert_version_default(binary: str, version: str) -> None: diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index cd38a2974..26c8f1b7d 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -44,14 +44,10 @@ def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover ) -def _envdir(prefix: Prefix) -> str: # pragma: win32 no cover - directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT) - return prefix.path(directory) - - @contextlib.contextmanager # pragma: win32 no cover def in_env(prefix: Prefix) -> Generator[None, None, None]: - with envcontext(get_env_patch(_envdir(prefix))): + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) + with envcontext(get_env_patch(envdir)): yield @@ -62,7 +58,7 @@ def install_environment( ) -> None: # pragma: win32 no cover helpers.assert_version_default('lua', version) - envdir = _envdir(prefix) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) with in_env(prefix): # luarocks doesn't bootstrap a tree prior to installing # so ensure the directory exists. diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 353fa1522..8facfe007 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -36,11 +36,6 @@ def get_default_version() -> str: return C.DEFAULT -def _envdir(prefix: Prefix, version: str) -> str: - directory = helpers.environment_dir(ENVIRONMENT_DIR, version) - return prefix.path(directory) - - def get_env_patch(venv: str) -> PatchesT: if sys.platform == 'cygwin': # pragma: no cover _, win_venv, _ = cmd_output('cygpath', '-w', venv) @@ -68,7 +63,8 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - with envcontext(get_env_patch(_envdir(prefix, language_version))): + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) + with envcontext(get_env_patch(envdir)): yield @@ -85,7 +81,7 @@ def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: assert prefix.exists('package.json') - envdir = _envdir(prefix, version) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath if sys.platform == 'win32': # pragma: no cover diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 25c016766..95be65599 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -18,11 +18,6 @@ health_check = helpers.basic_health_check -def _envdir(prefix: Prefix, version: str) -> str: - directory = helpers.environment_dir(ENVIRONMENT_DIR, version) - return prefix.path(directory) - - def get_env_patch(venv: str) -> PatchesT: return ( ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), @@ -42,7 +37,8 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - with envcontext(get_env_patch(_envdir(prefix, language_version))): + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) + with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 6770499de..a7744d642 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -156,15 +156,13 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) - envdir = prefix.path(directory) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) with envcontext(get_env_patch(envdir)): yield def health_check(prefix: Prefix, language_version: str) -> str | None: - directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) - envdir = prefix.path(directory) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') # created with "old" virtualenv @@ -207,7 +205,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) venv_cmd = [sys.executable, '-mvirtualenv', envdir] python = norm_version(version) if python is not None: diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 9bbfdbe2c..d2ec83daa 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -34,15 +34,11 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - envdir = _get_env_dir(prefix, language_version) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) with envcontext(get_env_patch(envdir)): yield -def _get_env_dir(prefix: Prefix, version: str) -> str: - return prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) - - def _prefix_if_file_entry(entry: list[str], prefix: Prefix) -> Sequence[str]: if entry[1] == '-e': return entry[1:] @@ -93,7 +89,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - env_dir = _get_env_dir(prefix, version) + env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) os.makedirs(env_dir, exist_ok=True) shutil.copy(prefix.path('renv.lock'), env_dir) shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 379427b02..89af25459 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -71,9 +71,7 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - envdir = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, language_version), - ) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) with envcontext(get_env_patch(envdir, language_version)): yield @@ -88,14 +86,14 @@ def _install_rbenv( prefix: Prefix, version: str, ) -> None: # pragma: win32 no cover - directory = helpers.environment_dir(ENVIRONMENT_DIR, version) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) _extract_resource('rbenv.tar.gz', prefix.path('.')) - shutil.move(prefix.path('rbenv'), prefix.path(directory)) + shutil.move(prefix.path('rbenv'), envdir) # Only install ruby-build if the version is specified if version != C.DEFAULT: - plugins_dir = prefix.path(directory, 'plugins') + plugins_dir = os.path.join(envdir, 'plugins') _extract_resource('ruby-download.tar.gz', plugins_dir) _extract_resource('ruby-build.tar.gz', plugins_dir) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 67e7ae85c..0f6cd332d 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -48,11 +48,6 @@ def _rust_toolchain(language_version: str) -> str: return language_version -def _envdir(prefix: Prefix, version: str) -> str: - directory = helpers.environment_dir(ENVIRONMENT_DIR, version) - return prefix.path(directory) - - def get_env_patch(target_dir: str, version: str) -> PatchesT: return ( ('CARGO_HOME', target_dir), @@ -71,9 +66,8 @@ def in_env( prefix: Prefix, language_version: str, ) -> Generator[None, None, None]: - with envcontext( - get_env_patch(_envdir(prefix, language_version), language_version), - ): + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) + with envcontext(get_env_patch(envdir, language_version)): yield @@ -125,7 +119,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - directory = _envdir(prefix, version) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) # There are two cases where we might want to specify more dependencies: # as dependencies for the library being built, and as binary packages @@ -160,7 +154,7 @@ def install_environment( for args in packages_to_install: cmd_output_b( - 'cargo', 'install', '--bins', '--root', directory, *args, + 'cargo', 'install', '--bins', '--root', envdir, *args, cwd=prefix.prefix_dir, ) diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index 0fab596cc..7cc61d958 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -28,9 +28,7 @@ def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) with envcontext(get_env_patch(envdir)): yield @@ -40,17 +38,15 @@ def install_environment( ) -> None: # pragma: win32 no cover helpers.assert_version_default('swift', version) helpers.assert_no_additional_deps('swift', additional_dependencies) - directory = prefix.path( - helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT), - ) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) # Build the swift package - os.mkdir(directory) + os.mkdir(envdir) cmd_output_b( 'swift', 'build', '-C', prefix.prefix_dir, '-c', BUILD_CONFIG, - '--build-path', os.path.join(directory, BUILD_DIR), + '--build-path', os.path.join(envdir, BUILD_DIR), ) diff --git a/pre_commit/repository.py b/pre_commit/repository.py index fa5322dc1..ac6b84463 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -45,8 +45,11 @@ def _hook_installed(hook: Hook) -> bool: if lang.ENVIRONMENT_DIR is None: return True - venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) - venv = hook.prefix.path(venv) + venv = environment_dir( + hook.prefix, + lang.ENVIRONMENT_DIR, + hook.language_version, + ) return ( _read_state(venv) == _state(hook.additional_dependencies) and not lang.health_check(hook.prefix, hook.language_version) @@ -61,8 +64,11 @@ def _hook_install(hook: Hook) -> None: lang = languages[hook.language] assert lang.ENVIRONMENT_DIR is not None - venv = environment_dir(lang.ENVIRONMENT_DIR, hook.language_version) - venv = hook.prefix.path(venv) + venv = environment_dir( + hook.prefix, + lang.ENVIRONMENT_DIR, + hook.language_version, + ) # There's potentially incomplete cleanup from previous runs # Clean it up! diff --git a/tests/repository_test.py b/tests/repository_test.py index 6aa0f0073..fa8bf4319 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -463,11 +463,12 @@ def test_additional_rust_cli_dependencies_installed( # A small rust package with no dependencies. config['hooks'][0]['additional_dependencies'] = [dep] hook = _get_hook(config, store, 'rust-hook') - binaries = os.listdir( - hook.prefix.path( - helpers.environment_dir(rust.ENVIRONMENT_DIR, 'system'), 'bin', - ), + envdir = helpers.environment_dir( + hook.prefix, + rust.ENVIRONMENT_DIR, + 'system', ) + binaries = os.listdir(os.path.join(envdir, 'bin')) # normalize for windows binaries = [os.path.splitext(binary)[0] for binary in binaries] assert 'shellharden' in binaries @@ -482,11 +483,12 @@ def test_additional_rust_lib_dependencies_installed( deps = ['shellharden:3.1.0', 'git-version'] config['hooks'][0]['additional_dependencies'] = deps hook = _get_hook(config, store, 'rust-hook') - binaries = os.listdir( - hook.prefix.path( - helpers.environment_dir(rust.ENVIRONMENT_DIR, 'system'), 'bin', - ), + envdir = helpers.environment_dir( + hook.prefix, + rust.ENVIRONMENT_DIR, + 'system', ) + binaries = os.listdir(os.path.join(envdir, 'bin')) # normalize for windows binaries = [os.path.splitext(binary)[0] for binary in binaries] assert 'rust-hello-world' in binaries @@ -672,11 +674,12 @@ def test_additional_golang_dependencies_installed( deps = ['golang.org/x/example/hello@latest'] config['hooks'][0]['additional_dependencies'] = deps hook = _get_hook(config, store, 'golang-hook') - binaries = os.listdir( - hook.prefix.path( - helpers.environment_dir(golang.ENVIRONMENT_DIR, C.DEFAULT), 'bin', - ), + envdir = helpers.environment_dir( + hook.prefix, + golang.ENVIRONMENT_DIR, + C.DEFAULT, ) + binaries = os.listdir(os.path.join(envdir, 'bin')) # normalize for windows binaries = [os.path.splitext(binary)[0] for binary in binaries] assert 'hello' in binaries @@ -792,10 +795,14 @@ class MyKeyboardInterrupt(KeyboardInterrupt): # Should have made an environment, however this environment is broken! hook, = hooks - assert hook.prefix.exists( - helpers.environment_dir(python.ENVIRONMENT_DIR, hook.language_version), + envdir = helpers.environment_dir( + hook.prefix, + python.ENVIRONMENT_DIR, + hook.language_version, ) + assert os.path.exists(envdir) + # However, it should be perfectly runnable (reinstall after botched # install) install_hook_envs(hooks, store) @@ -811,10 +818,12 @@ def test_invalidated_virtualenv(tempdir_factory, store): hook = _get_hook(config, store, 'foo') # Simulate breaking of the virtualenv - libdir = hook.prefix.path( - helpers.environment_dir(python.ENVIRONMENT_DIR, hook.language_version), - 'lib', hook.language_version, + envdir = helpers.environment_dir( + hook.prefix, + python.ENVIRONMENT_DIR, + hook.language_version, ) + libdir = os.path.join(envdir, 'lib', hook.language_version) paths = [ os.path.join(libdir, p) for p in ('site.py', 'site.pyc', '__pycache__') ] From 0920cb33ee3faf614dec5ab83dd9f99a682e6a75 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 2 Jan 2023 16:00:27 -0500 Subject: [PATCH 088/342] simplify install state the additional bookkeeping has been unnecessary since b827694520be0f39bfc0599f3680b6c08b4516cf unfortunately this will cause a rebuild of all hooks in order to be forward/backward compatible -- shrugs --- pre_commit/constants.py | 2 -- pre_commit/repository.py | 27 ++++----------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 8fc5e55db..3f03ceed9 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -5,8 +5,6 @@ CONFIG_FILE = '.pre-commit-config.yaml' MANIFEST_FILE = '.pre-commit-hooks.yaml' -# Bump when installation changes in a backwards / forwards incompatible way -INSTALLED_STATE_VERSION = '1' # Bump when modifying `empty_template` LOCAL_REPO_VERSION = '1' diff --git a/pre_commit/repository.py b/pre_commit/repository.py index ac6b84463..dfa1a2fd8 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json import logging import os from typing import Any @@ -23,21 +22,8 @@ logger = logging.getLogger('pre_commit') -def _state(additional_deps: Sequence[str]) -> object: - return {'additional_dependencies': sorted(additional_deps)} - - def _state_filename(venv: str) -> str: - return os.path.join(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') - - -def _read_state(venv: str) -> object | None: - filename = _state_filename(venv) - if not os.path.exists(filename): - return None - else: - with open(filename) as f: - return json.load(f) + return os.path.join(venv, '.install_state_v2') def _hook_installed(hook: Hook) -> bool: @@ -51,7 +37,7 @@ def _hook_installed(hook: Hook) -> bool: hook.language_version, ) return ( - _read_state(venv) == _state(hook.additional_dependencies) and + os.path.exists(_state_filename(venv)) and not lang.health_check(hook.prefix, hook.language_version) ) @@ -87,13 +73,8 @@ def _hook_install(hook: Hook) -> None: f'your environment\n\n' f'more info:\n\n{health_error}', ) - # Write our state to indicate we're installed - state_filename = _state_filename(venv) - staging = f'{state_filename}staging' - with open(staging, 'w') as state_file: - state_file.write(json.dumps(_state(hook.additional_dependencies))) - # Move the file into place atomically to indicate we've installed - os.replace(staging, state_filename) + # touch state file to indicate we're installed + open(_state_filename(venv), 'a+').close() def _hook( From 990643c1e089a7924697b32d4f2dd57dbe37785f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 2 Jan 2023 18:39:42 -0500 Subject: [PATCH 089/342] Revert "simplify install state" --- pre_commit/constants.py | 2 ++ pre_commit/repository.py | 27 +++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 3f03ceed9..8fc5e55db 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -5,6 +5,8 @@ CONFIG_FILE = '.pre-commit-config.yaml' MANIFEST_FILE = '.pre-commit-hooks.yaml' +# Bump when installation changes in a backwards / forwards incompatible way +INSTALLED_STATE_VERSION = '1' # Bump when modifying `empty_template` LOCAL_REPO_VERSION = '1' diff --git a/pre_commit/repository.py b/pre_commit/repository.py index dfa1a2fd8..ac6b84463 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import logging import os from typing import Any @@ -22,8 +23,21 @@ logger = logging.getLogger('pre_commit') +def _state(additional_deps: Sequence[str]) -> object: + return {'additional_dependencies': sorted(additional_deps)} + + def _state_filename(venv: str) -> str: - return os.path.join(venv, '.install_state_v2') + return os.path.join(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') + + +def _read_state(venv: str) -> object | None: + filename = _state_filename(venv) + if not os.path.exists(filename): + return None + else: + with open(filename) as f: + return json.load(f) def _hook_installed(hook: Hook) -> bool: @@ -37,7 +51,7 @@ def _hook_installed(hook: Hook) -> bool: hook.language_version, ) return ( - os.path.exists(_state_filename(venv)) and + _read_state(venv) == _state(hook.additional_dependencies) and not lang.health_check(hook.prefix, hook.language_version) ) @@ -73,8 +87,13 @@ def _hook_install(hook: Hook) -> None: f'your environment\n\n' f'more info:\n\n{health_error}', ) - # touch state file to indicate we're installed - open(_state_filename(venv), 'a+').close() + # Write our state to indicate we're installed + state_filename = _state_filename(venv) + staging = f'{state_filename}staging' + with open(staging, 'w') as state_file: + state_file.write(json.dumps(_state(hook.additional_dependencies))) + # Move the file into place atomically to indicate we've installed + os.replace(staging, state_filename) def _hook( From 8529a0c1d35e422304a54cc2c01d18541287e171 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 1 Jan 2023 16:58:16 -0500 Subject: [PATCH 090/342] add pre_commit.yaml module --- pre_commit/clientlib.py | 2 +- pre_commit/commands/autoupdate.py | 4 ++-- pre_commit/commands/migrate_config.py | 2 +- pre_commit/commands/try_repo.py | 2 +- pre_commit/languages/dart.py | 2 +- pre_commit/util.py | 15 --------------- pre_commit/yaml.py | 18 ++++++++++++++++++ testing/fixtures.py | 4 ++-- tests/commands/autoupdate_test.py | 5 ++--- 9 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 pre_commit/yaml.py diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index e03d5d666..e191d3a00 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -14,7 +14,7 @@ import pre_commit.constants as C from pre_commit.errors import FatalError from pre_commit.languages.all import all_languages -from pre_commit.util import yaml_load +from pre_commit.yaml import yaml_load logger = logging.getLogger('pre_commit') diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 6da53112e..7ed6e7761 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -20,8 +20,8 @@ from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b -from pre_commit.util import yaml_dump -from pre_commit.util import yaml_load +from pre_commit.yaml import yaml_dump +from pre_commit.yaml import yaml_load class RevInfo(NamedTuple): diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index c3d0a509f..836936b25 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -5,7 +5,7 @@ import yaml -from pre_commit.util import yaml_load +from pre_commit.yaml import yaml_load def _is_header_line(line: str) -> bool: diff --git a/pre_commit/commands/try_repo.py b/pre_commit/commands/try_repo.py index 5244aeff4..539ed3c2b 100644 --- a/pre_commit/commands/try_repo.py +++ b/pre_commit/commands/try_repo.py @@ -12,8 +12,8 @@ from pre_commit.commands.run import run from pre_commit.store import Store from pre_commit.util import cmd_output_b -from pre_commit.util import yaml_dump from pre_commit.xargs import xargs +from pre_commit.yaml import yaml_dump logger = logging.getLogger(__name__) diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 9fbb63cc0..223567a54 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -15,7 +15,7 @@ from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import win_exe -from pre_commit.util import yaml_load +from pre_commit.yaml import yaml_load ENVIRONMENT_DIR = 'dartenv' diff --git a/pre_commit/util.py b/pre_commit/util.py index 324544c7e..d51fd32d9 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -2,7 +2,6 @@ import contextlib import errno -import functools import importlib.resources import os.path import shutil @@ -15,22 +14,8 @@ from typing import Generator from typing import IO -import yaml - from pre_commit import parse_shebang -Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader) -yaml_load = functools.partial(yaml.load, Loader=Loader) -Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) - - -def yaml_dump(o: Any, **kwargs: Any) -> str: - # when python/mypy#1484 is solved, this can be `functools.partial` - return yaml.dump( - o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False, - **kwargs, - ) - def force_bytes(exc: Any) -> bytes: with contextlib.suppress(TypeError): diff --git a/pre_commit/yaml.py b/pre_commit/yaml.py new file mode 100644 index 000000000..bdf4ec47d --- /dev/null +++ b/pre_commit/yaml.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import functools +from typing import Any + +import yaml + +Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader) +yaml_load = functools.partial(yaml.load, Loader=Loader) +Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) + + +def yaml_dump(o: Any, **kwargs: Any) -> str: + # when python/mypy#1484 is solved, this can be `functools.partial` + return yaml.dump( + o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False, + **kwargs, + ) diff --git a/testing/fixtures.py b/testing/fixtures.py index 5182a083e..79a11605e 100644 --- a/testing/fixtures.py +++ b/testing/fixtures.py @@ -12,8 +12,8 @@ from pre_commit.clientlib import CONFIG_SCHEMA from pre_commit.clientlib import load_manifest from pre_commit.util import cmd_output -from pre_commit.util import yaml_dump -from pre_commit.util import yaml_load +from pre_commit.yaml import yaml_dump +from pre_commit.yaml import yaml_load from testing.util import get_resource_path from testing.util import git_commit diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py index 3806b0e48..4bcb5d82a 100644 --- a/tests/commands/autoupdate_test.py +++ b/tests/commands/autoupdate_test.py @@ -4,12 +4,11 @@ from unittest import mock import pytest -import yaml import pre_commit.constants as C from pre_commit import envcontext from pre_commit import git -from pre_commit import util +from pre_commit import yaml from pre_commit.commands.autoupdate import _check_hooks_still_exist_at_rev from pre_commit.commands.autoupdate import autoupdate from pre_commit.commands.autoupdate import RepositoryCannotBeUpdatedError @@ -206,7 +205,7 @@ def test_autoupdate_with_core_useBuiltinFSMonitor(out_of_date, tmpdir, store): def test_autoupdate_pure_yaml(out_of_date, tmpdir, store): - with mock.patch.object(util, 'Dumper', yaml.SafeDumper): + with mock.patch.object(yaml, 'Dumper', yaml.yaml.SafeDumper): test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store) From 60a42e94195492fa27e869e5034f296989cfc4a7 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Mon, 2 Jan 2023 21:14:50 +0100 Subject: [PATCH 091/342] Remove `GOPATH` special build --- pre_commit/git.py | 5 ---- pre_commit/languages/golang.py | 47 ++++++++-------------------------- tests/languages/golang_test.py | 22 ---------------- 3 files changed, 10 insertions(+), 64 deletions(-) delete mode 100644 tests/languages/golang_test.py diff --git a/pre_commit/git.py b/pre_commit/git.py index a76118f0b..333dc7ba3 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -93,11 +93,6 @@ def get_git_common_dir(git_root: str = '.') -> str: return get_git_dir(git_root) -def get_remote_url(git_root: str) -> str: - _, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root) - return out.strip() - - def is_in_merge_conflict() -> bool: git_dir = get_git_dir('.') return ( diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 70f0e65d4..a57c38dcd 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -7,7 +7,6 @@ from typing import Sequence import pre_commit.constants as C -from pre_commit import git from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var @@ -15,7 +14,6 @@ from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output -from pre_commit.util import cmd_output_b from pre_commit.util import rmtree ENVIRONMENT_DIR = 'golangenv' @@ -36,53 +34,28 @@ def in_env(prefix: Prefix) -> Generator[None, None, None]: yield -def guess_go_dir(remote_url: str) -> str: - if remote_url.endswith('.git'): - remote_url = remote_url[:-1 * len('.git')] - looks_like_url = ( - not remote_url.startswith('file://') and - ('//' in remote_url or '@' in remote_url) - ) - remote_url = remote_url.replace(':', '/') - if looks_like_url: - _, _, remote_url = remote_url.rpartition('//') - _, _, remote_url = remote_url.rpartition('@') - return remote_url - else: - return 'unknown_src_dir' - - def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: helpers.assert_version_default('golang', version) - directory = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) - - remote = git.get_remote_url(prefix.prefix_dir) - repo_src_dir = os.path.join(directory, 'src', guess_go_dir(remote)) - - # Clone into the goenv we'll create - cmd = ('git', 'clone', '--recursive', '.', repo_src_dir) - helpers.run_setup_cmd(prefix, cmd) + env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) if sys.platform == 'cygwin': # pragma: no cover - _, gopath, _ = cmd_output('cygpath', '-w', directory) - gopath = gopath.strip() + gopath = cmd_output('cygpath', '-w', env_dir)[1].strip() else: - gopath = directory + gopath = env_dir env = dict(os.environ, GOPATH=gopath) env.pop('GOBIN', None) - cmd_output_b('go', 'install', './...', cwd=repo_src_dir, env=env) + + helpers.run_setup_cmd(prefix, ('go', 'install', './...'), env=env) for dependency in additional_dependencies: - cmd_output_b( - 'go', 'install', dependency, cwd=repo_src_dir, env=env, - ) - # Same some disk space, we don't need these after installation - rmtree(prefix.path(directory, 'src')) - pkgdir = prefix.path(directory, 'pkg') - if os.path.exists(pkgdir): # pragma: no cover (go<1.10) + helpers.run_setup_cmd(prefix, ('go', 'install', dependency), env=env) + + # save some disk space -- we don't need this after installation + pkgdir = os.path.join(env_dir, 'pkg') + if os.path.exists(pkgdir): # pragma: no branch (always true on windows?) rmtree(pkgdir) diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py deleted file mode 100644 index 9e393cb39..000000000 --- a/tests/languages/golang_test.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import pytest - -from pre_commit.languages.golang import guess_go_dir - - -@pytest.mark.parametrize( - ('url', 'expected'), - ( - ('/im/a/path/on/disk', 'unknown_src_dir'), - ('file:///im/a/path/on/disk', 'unknown_src_dir'), - ('git@github.com:golang/lint', 'github.com/golang/lint'), - ('git://github.com/golang/lint', 'github.com/golang/lint'), - ('http://github.com/golang/lint', 'github.com/golang/lint'), - ('https://github.com/golang/lint', 'github.com/golang/lint'), - ('ssh://git@github.com/golang/lint', 'github.com/golang/lint'), - ('git@github.com:golang/lint.git', 'github.com/golang/lint'), - ), -) -def test_guess_go_dir(url, expected): - assert guess_go_dir(url) == expected From bf1a1fa5fd6de3633b033847964b51a60ffbd0d5 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Thu, 5 Jan 2023 13:31:28 +0100 Subject: [PATCH 092/342] Fix command normalization when a custom env is passed --- pre_commit/parse_shebang.py | 18 +++++++++------ pre_commit/util.py | 2 +- tests/commands/install_uninstall_test.py | 29 ++++++++++++------------ tests/languages/rust_test.py | 7 ++++-- tests/parse_shebang_test.py | 6 ++--- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py index 3ac933c09..3ee04e8d7 100644 --- a/pre_commit/parse_shebang.py +++ b/pre_commit/parse_shebang.py @@ -20,13 +20,13 @@ def parse_filename(filename: str) -> tuple[str, ...]: def find_executable( - exe: str, _environ: Mapping[str, str] | None = None, + exe: str, *, env: Mapping[str, str] | None = None, ) -> str | None: exe = os.path.normpath(exe) if os.sep in exe: return exe - environ = _environ if _environ is not None else os.environ + environ = env if env is not None else os.environ if 'PATHEXT' in environ: exts = environ['PATHEXT'].split(os.pathsep) @@ -43,12 +43,12 @@ def find_executable( return None -def normexe(orig: str) -> str: +def normexe(orig: str, *, env: Mapping[str, str] | None = None) -> str: def _error(msg: str) -> NoReturn: raise ExecutableNotFoundError(f'Executable `{orig}` {msg}') if os.sep not in orig and (not os.altsep or os.altsep not in orig): - exe = find_executable(orig) + exe = find_executable(orig, env=env) if exe is None: _error('not found') return exe @@ -62,7 +62,11 @@ def _error(msg: str) -> NoReturn: return orig -def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]: +def normalize_cmd( + cmd: tuple[str, ...], + *, + env: Mapping[str, str] | None = None, +) -> tuple[str, ...]: """Fixes for the following issues on windows - https://bugs.python.org/issue8557 - windows does not parse shebangs @@ -70,12 +74,12 @@ def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]: This function also makes deep-path shebangs work just fine """ # Use PATH to determine the executable - exe = normexe(cmd[0]) + exe = normexe(cmd[0], env=env) # Figure out the shebang from the resulting command cmd = parse_filename(exe) + (exe,) + cmd[1:] # This could have given us back another bare executable - exe = normexe(cmd[0]) + exe = normexe(cmd[0], env=env) return (exe,) + cmd[1:] diff --git a/pre_commit/util.py b/pre_commit/util.py index d51fd32d9..8ea48446a 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -99,7 +99,7 @@ def cmd_output_b( _setdefault_kwargs(kwargs) try: - cmd = parse_shebang.normalize_cmd(cmd) + cmd = parse_shebang.normalize_cmd(cmd, env=kwargs.get('env')) except parse_shebang.ExecutableNotFoundError as e: returncode, stdout_b, stderr_b = e.to_output() else: diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index e3943773f..a1ecda867 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -248,7 +248,7 @@ def test_install_idempotent(tempdir_factory, store): def _path_without_us(): # Choose a path which *probably* doesn't include us env = dict(os.environ) - exe = find_executable('pre-commit', _environ=env) + exe = find_executable('pre-commit', env=env) while exe: parts = env['PATH'].split(os.pathsep) after = [ @@ -258,7 +258,7 @@ def _path_without_us(): if parts == after: raise AssertionError(exe, parts) env['PATH'] = os.pathsep.join(after) - exe = find_executable('pre-commit', _environ=env) + exe = find_executable('pre-commit', env=env) return env['PATH'] @@ -276,18 +276,19 @@ def test_environment_not_sourced(tempdir_factory, store): # Use a specific homedir to ignore --user installs homedir = tempdir_factory.get() - ret, out = git_commit( - env={ - 'HOME': homedir, - 'PATH': _path_without_us(), - # Git needs this to make a commit - 'GIT_AUTHOR_NAME': os.environ['GIT_AUTHOR_NAME'], - 'GIT_COMMITTER_NAME': os.environ['GIT_COMMITTER_NAME'], - 'GIT_AUTHOR_EMAIL': os.environ['GIT_AUTHOR_EMAIL'], - 'GIT_COMMITTER_EMAIL': os.environ['GIT_COMMITTER_EMAIL'], - }, - check=False, - ) + env = { + 'HOME': homedir, + 'PATH': _path_without_us(), + # Git needs this to make a commit + 'GIT_AUTHOR_NAME': os.environ['GIT_AUTHOR_NAME'], + 'GIT_COMMITTER_NAME': os.environ['GIT_COMMITTER_NAME'], + 'GIT_AUTHOR_EMAIL': os.environ['GIT_AUTHOR_EMAIL'], + 'GIT_COMMITTER_EMAIL': os.environ['GIT_COMMITTER_EMAIL'], + } + if os.name == 'nt' and 'PATHEXT' in os.environ: # pragma: no cover + env['PATHEXT'] = os.environ['PATHEXT'] + + ret, out = git_commit(env=env, check=False) assert ret == 1 assert out == ( '`pre-commit` not found. ' diff --git a/tests/languages/rust_test.py b/tests/languages/rust_test.py index f011e7199..b8167a9e3 100644 --- a/tests/languages/rust_test.py +++ b/tests/languages/rust_test.py @@ -1,5 +1,6 @@ from __future__ import annotations +from typing import Mapping from unittest import mock import pytest @@ -48,7 +49,9 @@ def test_installs_with_bootstrapped_rustup(tmpdir, language_version): original_find_executable = parse_shebang.find_executable - def mocked_find_executable(exe: str) -> str | None: + def mocked_find_executable( + exe: str, *, env: Mapping[str, str] | None = None, + ) -> str | None: """ Return `None` the first time `find_executable` is called to ensure that the bootstrapping code is executed, then just let the function @@ -59,7 +62,7 @@ def mocked_find_executable(exe: str) -> str | None: find_executable_exes.append(exe) if len(find_executable_exes) == 1: return None - return original_find_executable(exe) + return original_find_executable(exe, env=env) with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck: find_exe_mck.side_effect = mocked_find_executable diff --git a/tests/parse_shebang_test.py b/tests/parse_shebang_test.py index d7acbf577..2fcb29ee7 100644 --- a/tests/parse_shebang_test.py +++ b/tests/parse_shebang_test.py @@ -75,10 +75,10 @@ def test_find_executable_path_ext(in_tmpdir): env_path = {'PATH': os.path.dirname(exe_path)} env_path_ext = dict(env_path, PATHEXT=os.pathsep.join(('.exe', '.myext'))) assert parse_shebang.find_executable('run') is None - assert parse_shebang.find_executable('run', _environ=env_path) is None - ret = parse_shebang.find_executable('run.myext', _environ=env_path) + assert parse_shebang.find_executable('run', env=env_path) is None + ret = parse_shebang.find_executable('run.myext', env=env_path) assert ret == exe_path - ret = parse_shebang.find_executable('run', _environ=env_path_ext) + ret = parse_shebang.find_executable('run', env=env_path_ext) assert ret == exe_path From 619f2bf5a9a4b03766c3304ad4e01ec90bea17f1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 9 Jan 2023 12:31:05 -0500 Subject: [PATCH 093/342] eagerly catch invalid yaml in migrate-config --- pre_commit/commands/migrate_config.py | 9 +++++++++ tests/commands/migrate_config_test.py | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index 836936b25..6f7af4eba 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -3,8 +3,10 @@ import re import textwrap +import cfgv import yaml +from pre_commit.clientlib import InvalidConfigError from pre_commit.yaml import yaml_load @@ -44,6 +46,13 @@ def migrate_config(config_file: str, quiet: bool = False) -> int: with open(config_file) as f: orig_contents = contents = f.read() + with cfgv.reraise_as(InvalidConfigError): + with cfgv.validate_context(f'File {config_file}'): + try: + yaml_load(orig_contents) + except Exception as e: + raise cfgv.ValidationError(str(e)) + contents = _migrate_map(contents) contents = _migrate_sha_to_rev(contents) diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py index b80244e12..fca1ad92f 100644 --- a/tests/commands/migrate_config_test.py +++ b/tests/commands/migrate_config_test.py @@ -1,6 +1,9 @@ from __future__ import annotations +import pytest + import pre_commit.constants as C +from pre_commit.clientlib import InvalidConfigError from pre_commit.commands.migrate_config import migrate_config @@ -129,3 +132,13 @@ def test_migrate_config_sha_to_rev(tmpdir): ' rev: v1.2.0\n' ' hooks: []\n' ) + + +def test_migrate_config_invalid_yaml(tmpdir): + contents = '[' + cfg = tmpdir.join(C.CONFIG_FILE) + cfg.write(contents) + with tmpdir.as_cwd(), pytest.raises(InvalidConfigError) as excinfo: + migrate_config(C.CONFIG_FILE) + expected = '\n==> File .pre-commit-config.yaml\n=====> ' + assert str(excinfo.value).startswith(expected) From 9afd63948e2ba76cd0e351d022efd82534383146 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Tue, 3 Jan 2023 01:48:43 +0100 Subject: [PATCH 094/342] Make Go a first class language --- pre_commit/languages/golang.py | 116 ++++++++++++++++-- .../golang-hello-world/main.go | 8 +- tests/languages/golang_test.py | 43 +++++++ tests/repository_test.py | 27 +++- 4 files changed, 181 insertions(+), 13 deletions(-) create mode 100644 tests/languages/golang_test.py diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index a57c38dcd..756aa1640 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -1,9 +1,21 @@ from __future__ import annotations import contextlib +import functools +import json import os.path +import platform +import shutil import sys +import tarfile +import tempfile +import urllib.error +import urllib.request +import zipfile +from typing import ContextManager from typing import Generator +from typing import IO +from typing import Protocol from typing import Sequence import pre_commit.constants as C @@ -17,20 +29,100 @@ from pre_commit.util import rmtree ENVIRONMENT_DIR = 'golangenv' -get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +_ARCH_ALIASES = { + 'x86_64': 'amd64', + 'i386': '386', + 'aarch64': 'arm64', + 'armv8': 'arm64', + 'armv7l': 'armv6l', +} +_ARCH = platform.machine().lower() +_ARCH = _ARCH_ALIASES.get(_ARCH, _ARCH) + + +class ExtractAll(Protocol): + def extractall(self, path: str) -> None: ... + + +if sys.platform == 'win32': # pragma: win32 cover + _EXT = 'zip' + + def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]: + return zipfile.ZipFile(bio) +else: # pragma: win32 no cover + _EXT = 'tar.gz' + + def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]: + return tarfile.open(fileobj=bio) + + +@functools.lru_cache(maxsize=1) +def get_default_version() -> str: + if helpers.exe_exists('go'): + return 'system' + else: + return C.DEFAULT + + +def get_env_patch(venv: str, version: str) -> PatchesT: + if version == 'system': + return ( + ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ) -def get_env_patch(venv: str) -> PatchesT: return ( - ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))), + ('GOROOT', os.path.join(venv, '.go')), + ( + 'PATH', ( + os.path.join(venv, 'bin'), os.pathsep, + os.path.join(venv, '.go', 'bin'), os.pathsep, Var('PATH'), + ), + ), ) +@functools.lru_cache +def _infer_go_version(version: str) -> str: + if version != C.DEFAULT: + return version + resp = urllib.request.urlopen('https://go.dev/dl/?mode=json') + # TODO: 3.9+ .removeprefix('go') + return json.load(resp)[0]['version'][2:] + + +def _get_url(version: str) -> str: + os_name = platform.system().lower() + version = _infer_go_version(version) + return f'https://dl.google.com/go/go{version}.{os_name}-{_ARCH}.{_EXT}' + + +def _install_go(version: str, dest: str) -> None: + try: + resp = urllib.request.urlopen(_get_url(version)) + except urllib.error.HTTPError as e: # pragma: no cover + if e.code == 404: + raise ValueError( + f'Could not find a version matching your system requirements ' + f'(os={platform.system().lower()}; arch={_ARCH})', + ) from e + else: + raise + else: + with tempfile.TemporaryFile() as f: + shutil.copyfileobj(resp, f) + f.seek(0) + + with _open_archive(f) as archive: + archive.extractall(dest) + shutil.move(os.path.join(dest, 'go'), os.path.join(dest, '.go')) + + @contextlib.contextmanager -def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) - with envcontext(get_env_patch(envdir)): +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + with envcontext(get_env_patch(envdir, version)): yield @@ -39,15 +131,23 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('golang', version) env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + if version != 'system': + _install_go(version, env_dir) + if sys.platform == 'cygwin': # pragma: no cover gopath = cmd_output('cygpath', '-w', env_dir)[1].strip() else: gopath = env_dir + env = dict(os.environ, GOPATH=gopath) env.pop('GOBIN', None) + if version != 'system': + env['GOROOT'] = os.path.join(env_dir, '.go') + env['PATH'] = os.pathsep.join(( + os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'], + )) helpers.run_setup_cmd(prefix, ('go', 'install', './...'), env=env) for dependency in additional_dependencies: @@ -64,5 +164,5 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix): + with in_env(hook.prefix, hook.language_version): return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/testing/resources/golang_hooks_repo/golang-hello-world/main.go b/testing/resources/golang_hooks_repo/golang-hello-world/main.go index 1e3c591a2..168574384 100644 --- a/testing/resources/golang_hooks_repo/golang-hello-world/main.go +++ b/testing/resources/golang_hooks_repo/golang-hello-world/main.go @@ -3,7 +3,9 @@ package main import ( "fmt" + "runtime" "github.com/BurntSushi/toml" + "os" ) type Config struct { @@ -11,7 +13,11 @@ type Config struct { } func main() { + message := runtime.Version() + if len(os.Args) > 1 { + message = os.Args[1] + } var conf Config toml.Decode("What = 'world'\n", &conf) - fmt.Printf("hello %v\n", conf.What) + fmt.Printf("hello %v from %s\n", conf.What, message) } diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py new file mode 100644 index 000000000..0219261fb --- /dev/null +++ b/tests/languages/golang_test.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import re +from unittest import mock + +import pytest + +import pre_commit.constants as C +from pre_commit.languages import golang +from pre_commit.languages import helpers + + +ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__ + + +@pytest.fixture +def exe_exists_mck(): + with mock.patch.object(helpers, 'exe_exists') as mck: + yield mck + + +def test_golang_default_version_system_available(exe_exists_mck): + exe_exists_mck.return_value = True + assert ACTUAL_GET_DEFAULT_VERSION() == 'system' + + +def test_golang_default_version_system_not_available(exe_exists_mck): + exe_exists_mck.return_value = False + assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT + + +ACTUAL_INFER_GO_VERSION = golang._infer_go_version.__wrapped__ + + +def test_golang_infer_go_version_not_default(): + assert ACTUAL_INFER_GO_VERSION('1.19.4') == '1.19.4' + + +def test_golang_infer_go_version_default(): + version = ACTUAL_INFER_GO_VERSION(C.DEFAULT) + + assert version != C.DEFAULT + assert re.match(r'^\d+\.\d+\.\d+$', version) diff --git a/tests/repository_test.py b/tests/repository_test.py index fa8bf4319..2fa1cccea 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -380,17 +380,36 @@ def test_swift_hook(tempdir_factory, store): ) -def test_golang_hook(tempdir_factory, store): +def test_golang_system_hook(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'golang_hooks_repo', - 'golang-hook', [], b'hello world\n', + 'golang-hook', ['system'], b'hello world from system\n', + config_kwargs={ + 'hooks': [{ + 'id': 'golang-hook', + 'language_version': 'system', + }], + }, + ) + + +def test_golang_versioned_hook(tempdir_factory, store): + _test_hook_repo( + tempdir_factory, store, 'golang_hooks_repo', + 'golang-hook', [], b'hello world from go1.18.4\n', + config_kwargs={ + 'hooks': [{ + 'id': 'golang-hook', + 'language_version': '1.18.4', + }], + }, ) def test_golang_hook_still_works_when_gobin_is_set(tempdir_factory, store): gobin_dir = tempdir_factory.get() with envcontext((('GOBIN', gobin_dir),)): - test_golang_hook(tempdir_factory, store) + test_golang_system_hook(tempdir_factory, store) assert os.listdir(gobin_dir) == [] @@ -677,7 +696,7 @@ def test_additional_golang_dependencies_installed( envdir = helpers.environment_dir( hook.prefix, golang.ENVIRONMENT_DIR, - C.DEFAULT, + golang.get_default_version(), ) binaries = os.listdir(os.path.join(envdir, 'bin')) # normalize for windows From 37685a7f4200c50cf707ebf9cddd5700ab66f31a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 15 Jan 2023 09:56:30 -0500 Subject: [PATCH 095/342] the local repo no longer needs to be a git repo --- pre_commit/store.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pre_commit/store.py b/pre_commit/store.py index effebfb88..e42cc4897 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -204,16 +204,6 @@ def make_local_strategy(directory: str) -> None: with open(target_file, 'w') as f: f.write(contents) - env = git.no_git_env() - - # initialize the git repository so it looks more like cloned repos - def _git_cmd(*args: str) -> None: - cmd_output_b('git', *args, cwd=directory, env=env) - - git.init_repo(directory, '<>') - _git_cmd('add', '.') - git.commit(repo=directory) - return self._new_repo( 'local', C.LOCAL_REPO_VERSION, deps, make_local_strategy, ) From ae34a962d79d4e823214028c353f690dd2ad4306 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 2 Jan 2023 19:02:38 -0500 Subject: [PATCH 096/342] make in_env part of the language api --- pre_commit/commands/run.py | 3 ++- pre_commit/languages/all.py | 9 +++++++++ pre_commit/languages/conda.py | 10 +++------- pre_commit/languages/coursier.py | 12 ++++-------- pre_commit/languages/dart.py | 8 +++----- pre_commit/languages/docker.py | 4 ++-- pre_commit/languages/docker_image.py | 1 + pre_commit/languages/dotnet.py | 8 +++----- pre_commit/languages/fail.py | 1 + pre_commit/languages/golang.py | 3 +-- pre_commit/languages/helpers.py | 9 ++++++++- pre_commit/languages/lua.py | 12 +++++------- pre_commit/languages/node.py | 10 +++------- pre_commit/languages/perl.py | 10 +++------- pre_commit/languages/pygrep.py | 1 + pre_commit/languages/python.py | 10 +++------- pre_commit/languages/r.py | 14 +++++--------- pre_commit/languages/ruby.py | 12 ++++-------- pre_commit/languages/rust.py | 12 ++++-------- pre_commit/languages/script.py | 1 + pre_commit/languages/swift.py | 10 ++++------ pre_commit/languages/system.py | 2 +- tests/repository_test.py | 3 ++- 23 files changed, 73 insertions(+), 92 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 429e04c60..a398e84c5 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -189,7 +189,8 @@ def _run_single_hook( filenames = () time_before = time.time() language = languages[hook.language] - retcode, out = language.run_hook(hook, filenames, use_color) + with language.in_env(hook.prefix, hook.language_version): + retcode, out = language.run_hook(hook, filenames, use_color) duration = round(time.time() - time_before, 2) or 0 diff_after = _get_diff() diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index 7c7c58bde..6135272ac 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -1,5 +1,6 @@ from __future__ import annotations +from typing import ContextManager from typing import Protocol from typing import Sequence @@ -50,6 +51,14 @@ def install_environment( ) -> None: ... + # modify the environment for hook execution + def in_env( + self, + prefix: Prefix, + version: str, + ) -> ContextManager[None]: + ... + # execute a hook and return the exit code and output def run_hook( self, diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 5a0a720f3..612a8242d 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -40,11 +40,8 @@ def get_env_patch(env: str) -> PatchesT: @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -88,5 +85,4 @@ def run_hook( # can run them without which is much quicker and produces a better # output. # cmd = ('conda', 'run', '-p', env_dir) + hook.cmd - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index fdea3cd71..46eb4e0a2 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -59,12 +59,9 @@ def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover ) -@contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: # pragma: win32 no cover - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +@contextlib.contextmanager # pragma: win32 no cover +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -74,5 +71,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 223567a54..7d1322b00 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -7,7 +7,6 @@ from typing import Generator from typing import Sequence -import pre_commit.constants as C from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var @@ -30,8 +29,8 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -103,5 +102,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index c51cf7c10..dbdfd35c5 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -5,7 +5,6 @@ import os from typing import Sequence -import pre_commit.constants as C from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix @@ -16,6 +15,7 @@ PRE_COMMIT_LABEL = 'PRE_COMMIT' get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +in_env = helpers.no_env # no special environment for docker def _is_in_docker() -> bool: @@ -94,7 +94,7 @@ def install_environment( helpers.assert_version_default('docker', version) helpers.assert_no_additional_deps('docker', additional_dependencies) - directory = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) + directory = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) # Docker doesn't really have relevant disk environment, but pre-commit # still needs to cleanup its state files on failure diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index daa4d1ba3..b1cd3caf8 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -10,6 +10,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check install_environment = helpers.no_install +in_env = helpers.no_env def run_hook( diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 0bb0210cd..8d4d48e3d 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -9,7 +9,6 @@ from typing import Generator from typing import Sequence -import pre_commit.constants as C from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var @@ -31,8 +30,8 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -121,5 +120,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index 00b06a9a9..f051d5e40 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -9,6 +9,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check install_environment = helpers.no_install +in_env = helpers.no_env def run_hook( diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 756aa1640..b38e4994c 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -164,5 +164,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 098e95c5a..5b3a54ffd 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -1,10 +1,12 @@ from __future__ import annotations +import contextlib import multiprocessing import os import random import re from typing import Any +from typing import Generator from typing import NoReturn from typing import Sequence @@ -84,7 +86,12 @@ def no_install( version: str, additional_dependencies: Sequence[str], ) -> NoReturn: - raise AssertionError('This type is not installable') + raise AssertionError('This language is not installable') + + +@contextlib.contextmanager +def no_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + yield def target_concurrency(hook: Hook) -> int: diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index 26c8f1b7d..1c872f361 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -6,7 +6,6 @@ from typing import Generator from typing import Sequence -import pre_commit.constants as C from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var @@ -45,8 +44,8 @@ def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover -def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -58,8 +57,8 @@ def install_environment( ) -> None: # pragma: win32 no cover helpers.assert_version_default('lua', version) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) - with in_env(prefix): + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + with in_env(prefix, version): # luarocks doesn't bootstrap a tree prior to installing # so ensure the directory exists. os.makedirs(envdir, exist_ok=True) @@ -81,5 +80,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - with in_env(hook.prefix): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 8facfe007..7b4d2e7d4 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -59,11 +59,8 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -118,5 +115,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 95be65599..622c8a129 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -33,11 +33,8 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -58,5 +55,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index 2e2072b08..d9f779f77 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -16,6 +16,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check install_environment = helpers.no_install +in_env = helpers.no_env def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int: diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index a7744d642..28f4ab5d5 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -152,11 +152,8 @@ def norm_version(version: str) -> str | None: @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -222,5 +219,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index d2ec83daa..f5e1eaba2 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -30,11 +30,8 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -156,7 +153,6 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs( - hook, _cmd_from_hook(hook), file_args, color=color, - ) + return helpers.run_xargs( + hook, _cmd_from_hook(hook), file_args, color=color, + ) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 89af25459..2805aca63 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -67,12 +67,9 @@ def get_env_patch( @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) - with envcontext(get_env_patch(envdir, language_version)): +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + with envcontext(get_env_patch(envdir, version)): yield @@ -143,5 +140,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 0f6cd332d..9da8f82ce 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -62,12 +62,9 @@ def get_env_patch(target_dir: str, version: str) -> PatchesT: @contextlib.contextmanager -def in_env( - prefix: Prefix, - language_version: str, -) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) - with envcontext(get_env_patch(envdir, language_version)): +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + with envcontext(get_env_patch(envdir, version)): yield @@ -164,5 +161,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index d5e7677f9..5b7bdd5f1 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -9,6 +9,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check install_environment = helpers.no_install +in_env = helpers.no_env def run_hook( diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index 7cc61d958..ad00b94ac 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -5,7 +5,6 @@ from typing import Generator from typing import Sequence -import pre_commit.constants as C from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var @@ -27,8 +26,8 @@ def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover -def in_env(prefix: Prefix) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -38,7 +37,7 @@ def install_environment( ) -> None: # pragma: win32 no cover helpers.assert_version_default('swift', version) helpers.assert_no_additional_deps('swift', additional_dependencies) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, C.DEFAULT) + envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) # Build the swift package os.mkdir(envdir) @@ -55,5 +54,4 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - with in_env(hook.prefix): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/system.py b/pre_commit/languages/system.py index c64fb3650..9cc94f8c9 100644 --- a/pre_commit/languages/system.py +++ b/pre_commit/languages/system.py @@ -5,11 +5,11 @@ from pre_commit.hook import Hook from pre_commit.languages import helpers - ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check install_environment = helpers.no_install +in_env = helpers.no_env def run_hook( diff --git a/tests/repository_test.py b/tests/repository_test.py index 2fa1cccea..236c7983b 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -44,7 +44,8 @@ def _norm_out(b): def _hook_run(hook, filenames, color): - return languages[hook.language].run_hook(hook, filenames, color) + with languages[hook.language].in_env(hook.prefix, hook.language_version): + return languages[hook.language].run_hook(hook, filenames, color) def _get_hook_no_install(repo_config, store, hook_id): From 628c876b2d0e1fce25c38e6455a61351d57c714f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 Jan 2023 16:34:01 -0500 Subject: [PATCH 097/342] adjust the run_hook api to no longer take Hook --- pre_commit/commands/run.py | 9 +++++- pre_commit/hook.py | 5 --- pre_commit/languages/all.py | 7 +++-- pre_commit/languages/conda.py | 14 +-------- pre_commit/languages/coursier.py | 10 +----- pre_commit/languages/dart.py | 10 +----- pre_commit/languages/docker.py | 20 ++++++++---- pre_commit/languages/docker_image.py | 17 +++++++--- pre_commit/languages/dotnet.py | 10 +----- pre_commit/languages/fail.py | 10 ++++-- pre_commit/languages/golang.py | 10 +----- pre_commit/languages/helpers.py | 46 ++++++++++++++++++++++------ pre_commit/languages/lua.py | 10 +----- pre_commit/languages/node.py | 10 +----- pre_commit/languages/perl.py | 10 +----- pre_commit/languages/pygrep.py | 12 +++++--- pre_commit/languages/python.py | 10 +----- pre_commit/languages/r.py | 29 +++++++++++------- pre_commit/languages/ruby.py | 10 +----- pre_commit/languages/rust.py | 10 +----- pre_commit/languages/script.py | 18 ++++++++--- pre_commit/languages/swift.py | 15 +++------ pre_commit/languages/system.py | 12 +------- tests/languages/helpers_test.py | 28 ++++++++--------- tests/languages/r_test.py | 4 +-- tests/repository_test.py | 9 +++++- 26 files changed, 163 insertions(+), 192 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index a398e84c5..85fa59aa1 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -190,7 +190,14 @@ def _run_single_hook( time_before = time.time() language = languages[hook.language] with language.in_env(hook.prefix, hook.language_version): - retcode, out = language.run_hook(hook, filenames, use_color) + retcode, out = language.run_hook( + hook.prefix, + hook.entry, + hook.args, + filenames, + require_serial=hook.require_serial, + color=use_color, + ) duration = round(time.time() - time_before, 2) or 0 diff_after = _get_diff() diff --git a/pre_commit/hook.py b/pre_commit/hook.py index 202abb358..6d436ca30 100644 --- a/pre_commit/hook.py +++ b/pre_commit/hook.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import shlex from typing import Any from typing import NamedTuple from typing import Sequence @@ -37,10 +36,6 @@ class Hook(NamedTuple): stages: Sequence[str] verbose: bool - @property - def cmd(self) -> tuple[str, ...]: - return (*shlex.split(self.entry), *self.args) - @property def install_key(self) -> tuple[Prefix, str, str, tuple[str, ...]]: return ( diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index 6135272ac..c7aab65e7 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -4,7 +4,6 @@ from typing import Protocol from typing import Sequence -from pre_commit.hook import Hook from pre_commit.languages import conda from pre_commit.languages import coursier from pre_commit.languages import dart @@ -62,8 +61,12 @@ def in_env( # execute a hook and return the exit code and output def run_hook( self, - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: ... diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 612a8242d..e2fb01969 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -10,7 +10,6 @@ from pre_commit.envcontext import SubstitutionT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b @@ -18,6 +17,7 @@ ENVIRONMENT_DIR = 'conda' get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook def get_env_patch(env: str) -> PatchesT: @@ -74,15 +74,3 @@ def install_environment( conda_exe, 'install', '-p', env_dir, *additional_dependencies, cwd=prefix.prefix_dir, ) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - # TODO: Some rare commands need to be run using `conda run` but mostly we - # can run them without which is much quicker and produces a better - # output. - # cmd = ('conda', 'run', '-p', env_dir) + hook.cmd - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 46eb4e0a2..a6aea3fb2 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -8,7 +8,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix @@ -17,6 +16,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook def install_environment( @@ -64,11 +64,3 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: # pragma: win32 no cover - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 7d1322b00..e3c1c5855 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -10,7 +10,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import win_exe @@ -20,6 +19,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook def get_env_patch(venv: str) -> PatchesT: @@ -95,11 +95,3 @@ def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: raise AssertionError( f'could not find pubspec.yaml for {dep_s}', ) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index dbdfd35c5..18234567b 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -5,7 +5,6 @@ import os from typing import Sequence -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError @@ -123,16 +122,25 @@ def docker_cmd() -> tuple[str, ...]: # pragma: win32 no cover def run_hook( - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover # Rebuild the docker image in case it has gone missing, as many people do # automated cleanup of docker images. - build_docker_image(hook.prefix, pull=False) + build_docker_image(prefix, pull=False) - entry_exe, *cmd_rest = hook.cmd + entry_exe, *cmd_rest = helpers.hook_cmd(entry, args) - entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix)) + entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix)) cmd = (*docker_cmd(), *entry_tag, *cmd_rest) - return helpers.run_xargs(hook, cmd, file_args, color=color) + return helpers.run_xargs( + cmd, + file_args, + require_serial=require_serial, + color=color, + ) diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index b1cd3caf8..230983823 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -2,9 +2,9 @@ from typing import Sequence -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.languages.docker import docker_cmd +from pre_commit.prefix import Prefix ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version @@ -14,9 +14,18 @@ def run_hook( - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - cmd = docker_cmd() + hook.cmd - return helpers.run_xargs(hook, cmd, file_args, color=color) + cmd = docker_cmd() + helpers.hook_cmd(entry, args) + return helpers.run_xargs( + cmd, + file_args, + require_serial=require_serial, + color=color, + ) diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 8d4d48e3d..4c3955e85 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -12,7 +12,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix @@ -21,6 +20,7 @@ get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook def get_env_patch(venv: str) -> PatchesT: @@ -113,11 +113,3 @@ def install_environment( # Clean the git dir, ignoring the environment dir clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') helpers.run_setup_cmd(prefix, clean_cmd) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index f051d5e40..13b2bc12c 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -2,8 +2,8 @@ from typing import Sequence -from pre_commit.hook import Hook from pre_commit.languages import helpers +from pre_commit.prefix import Prefix ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version @@ -13,10 +13,14 @@ def run_hook( - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: - out = f'{hook.entry}\n\n'.encode() + out = f'{entry}\n\n'.encode() out += b'\n'.join(f.encode() for f in file_args) + b'\n' return 1, out diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index b38e4994c..3c4b652fa 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -22,7 +22,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output @@ -30,6 +29,7 @@ ENVIRONMENT_DIR = 'golangenv' health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook _ARCH_ALIASES = { 'x86_64': 'amd64', @@ -157,11 +157,3 @@ def install_environment( pkgdir = os.path.join(env_dir, 'pkg') if os.path.exists(pkgdir): # pragma: no branch (always true on windows?) rmtree(pkgdir) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 5b3a54ffd..074f98e9f 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -5,6 +5,7 @@ import os import random import re +import shlex from typing import Any from typing import Generator from typing import NoReturn @@ -12,7 +13,6 @@ import pre_commit.constants as C from pre_commit import parse_shebang -from pre_commit.hook import Hook from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b from pre_commit.xargs import xargs @@ -94,8 +94,8 @@ def no_env(prefix: Prefix, version: str) -> Generator[None, None, None]: yield -def target_concurrency(hook: Hook) -> int: - if hook.require_serial or 'PRE_COMMIT_NO_CONCURRENCY' in os.environ: +def target_concurrency() -> int: + if 'PRE_COMMIT_NO_CONCURRENCY' in os.environ: return 1 else: # Travis appears to have a bunch of CPUs, but we can't use them all. @@ -119,13 +119,39 @@ def _shuffled(seq: Sequence[str]) -> list[str]: def run_xargs( - hook: Hook, cmd: tuple[str, ...], file_args: Sequence[str], - **kwargs: Any, + *, + require_serial: bool, + color: bool, ) -> tuple[int, bytes]: - # Shuffle the files so that they more evenly fill out the xargs partitions, - # but do it deterministically in case a hook cares about ordering. - file_args = _shuffled(file_args) - kwargs['target_concurrency'] = target_concurrency(hook) - return xargs(cmd, file_args, **kwargs) + if require_serial: + jobs = 1 + else: + # Shuffle the files so that they more evenly fill out the xargs + # partitions, but do it deterministically in case a hook cares about + # ordering. + file_args = _shuffled(file_args) + jobs = target_concurrency() + return xargs(cmd, file_args, target_concurrency=jobs, color=color) + + +def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]: + return (*shlex.split(entry), *args) + + +def basic_run_hook( + prefix: Prefix, + entry: str, + args: Sequence[str], + file_args: Sequence[str], + *, + require_serial: bool, + color: bool, +) -> tuple[int, bytes]: + return run_xargs( + hook_cmd(entry, args), + file_args, + require_serial=require_serial, + color=color, + ) diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index 1c872f361..ffc40b505 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -9,7 +9,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output @@ -17,6 +16,7 @@ ENVIRONMENT_DIR = 'lua_env' get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook def _get_lua_version() -> str: # pragma: win32 no cover @@ -73,11 +73,3 @@ def install_environment( for dependency in additional_dependencies: cmd = ('luarocks', '--tree', envdir, 'install', dependency) helpers.run_setup_cmd(prefix, cmd) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: # pragma: win32 no cover - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 7b4d2e7d4..9688da359 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -12,7 +12,6 @@ from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.languages.python import bin_dir from pre_commit.prefix import Prefix @@ -21,6 +20,7 @@ from pre_commit.util import rmtree ENVIRONMENT_DIR = 'node_env' +run_hook = helpers.basic_run_hook @functools.lru_cache(maxsize=1) @@ -108,11 +108,3 @@ def install_environment( if prefix.exists('node_modules'): # pragma: win32 no cover rmtree(prefix.path('node_modules')) os.remove(pkg) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 622c8a129..2530c0ee1 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -9,13 +9,13 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix ENVIRONMENT_DIR = 'perl_env' get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook def get_env_patch(venv: str) -> PatchesT: @@ -48,11 +48,3 @@ def install_environment( helpers.run_setup_cmd( prefix, ('cpan', '-T', '.', *additional_dependencies), ) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index d9f779f77..93e2a65bd 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -8,8 +8,8 @@ from typing import Sequence from pre_commit import output -from pre_commit.hook import Hook from pre_commit.languages import helpers +from pre_commit.prefix import Prefix from pre_commit.xargs import xargs ENVIRONMENT_DIR = None @@ -88,12 +88,16 @@ class Choice(NamedTuple): def run_hook( - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: - exe = (sys.executable, '-m', __name__) + tuple(hook.args) + (hook.entry,) - return xargs(exe, file_args, color=color) + cmd = (sys.executable, '-m', __name__, *args, entry) + return xargs(cmd, file_args, color=color) def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 28f4ab5d5..c373646bc 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -12,7 +12,6 @@ from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix @@ -22,6 +21,7 @@ from pre_commit.util import win_exe ENVIRONMENT_DIR = 'py_env' +run_hook = helpers.basic_run_hook @functools.lru_cache(maxsize=None) @@ -212,11 +212,3 @@ def install_environment( cmd_output_b(*venv_cmd, cwd='/') with in_env(prefix, version): helpers.run_setup_cmd(prefix, install_cmd) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index f5e1eaba2..7ed3eafcd 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -10,7 +10,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b @@ -70,15 +69,15 @@ def _entry_validate(entry: list[str]) -> None: ) -def _cmd_from_hook(hook: Hook) -> tuple[str, ...]: - entry = shlex.split(hook.entry) - _entry_validate(entry) +def _cmd_from_hook( + prefix: Prefix, + entry: str, + args: Sequence[str], +) -> tuple[str, ...]: + cmd = shlex.split(entry) + _entry_validate(cmd) - return ( - entry[0], *RSCRIPT_OPTS, - *_prefix_if_file_entry(entry, hook.prefix), - *hook.args, - ) + return (cmd[0], *RSCRIPT_OPTS, *_prefix_if_file_entry(cmd, prefix), *args) def install_environment( @@ -149,10 +148,18 @@ def _inline_r_setup(code: str) -> str: def run_hook( - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: + cmd = _cmd_from_hook(prefix, entry, args) return helpers.run_xargs( - hook, _cmd_from_hook(hook), file_args, color=color, + cmd, + file_args, + require_serial=require_serial, + color=color, ) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 2805aca63..4416f7280 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -13,7 +13,6 @@ from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError @@ -21,6 +20,7 @@ ENVIRONMENT_DIR = 'rbenv' health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook @functools.lru_cache(maxsize=1) @@ -133,11 +133,3 @@ def install_environment( *prefix.star('.gem'), *additional_dependencies, ), ) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 9da8f82ce..391fd8657 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -15,7 +15,6 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b @@ -24,6 +23,7 @@ ENVIRONMENT_DIR = 'rustenv' health_check = helpers.basic_health_check +run_hook = helpers.basic_run_hook @functools.lru_cache(maxsize=1) @@ -154,11 +154,3 @@ def install_environment( 'cargo', 'install', '--bins', '--root', envdir, *args, cwd=prefix.prefix_dir, ) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 5b7bdd5f1..41fffdf07 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -2,8 +2,8 @@ from typing import Sequence -from pre_commit.hook import Hook from pre_commit.languages import helpers +from pre_commit.prefix import Prefix ENVIRONMENT_DIR = None get_default_version = helpers.basic_get_default_version @@ -13,9 +13,19 @@ def run_hook( - hook: Hook, + prefix: Prefix, + entry: str, + args: Sequence[str], file_args: Sequence[str], + *, + require_serial: bool, color: bool, ) -> tuple[int, bytes]: - cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:]) - return helpers.run_xargs(hook, cmd, file_args, color=color) + cmd = helpers.hook_cmd(entry, args) + cmd = (prefix.path(cmd[0]), *cmd[1:]) + return helpers.run_xargs( + cmd, + file_args, + require_serial=require_serial, + color=color, + ) diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index ad00b94ac..c66ad5fb0 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -8,16 +8,17 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.hook import Hook from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b +BUILD_DIR = '.build' +BUILD_CONFIG = 'release' + ENVIRONMENT_DIR = 'swift_env' get_default_version = helpers.basic_get_default_version health_check = helpers.basic_health_check -BUILD_DIR = '.build' -BUILD_CONFIG = 'release' +run_hook = helpers.basic_run_hook def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover @@ -47,11 +48,3 @@ def install_environment( '-c', BUILD_CONFIG, '--build-path', os.path.join(envdir, BUILD_DIR), ) - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: # pragma: win32 no cover - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/system.py b/pre_commit/languages/system.py index 9cc94f8c9..204cad727 100644 --- a/pre_commit/languages/system.py +++ b/pre_commit/languages/system.py @@ -1,8 +1,5 @@ from __future__ import annotations -from typing import Sequence - -from pre_commit.hook import Hook from pre_commit.languages import helpers ENVIRONMENT_DIR = None @@ -10,11 +7,4 @@ health_check = helpers.basic_health_check install_environment = helpers.no_install in_env = helpers.no_env - - -def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, -) -> tuple[int, bytes]: - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) +run_hook = helpers.basic_run_hook diff --git a/tests/languages/helpers_test.py b/tests/languages/helpers_test.py index f333e79d5..c209e7e6d 100644 --- a/tests/languages/helpers_test.py +++ b/tests/languages/helpers_test.py @@ -12,7 +12,6 @@ from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError -from testing.auto_namedtuple import auto_namedtuple @pytest.fixture @@ -94,31 +93,22 @@ def test_assert_no_additional_deps(): ) -SERIAL_FALSE = auto_namedtuple(require_serial=False) -SERIAL_TRUE = auto_namedtuple(require_serial=True) - - def test_target_concurrency_normal(): with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): with mock.patch.dict(os.environ, {}, clear=True): - assert helpers.target_concurrency(SERIAL_FALSE) == 123 - - -def test_target_concurrency_cpu_count_require_serial_true(): - with mock.patch.dict(os.environ, {}, clear=True): - assert helpers.target_concurrency(SERIAL_TRUE) == 1 + assert helpers.target_concurrency() == 123 def test_target_concurrency_testing_env_var(): with mock.patch.dict( os.environ, {'PRE_COMMIT_NO_CONCURRENCY': '1'}, clear=True, ): - assert helpers.target_concurrency(SERIAL_FALSE) == 1 + assert helpers.target_concurrency() == 1 def test_target_concurrency_on_travis(): with mock.patch.dict(os.environ, {'TRAVIS': '1'}, clear=True): - assert helpers.target_concurrency(SERIAL_FALSE) == 2 + assert helpers.target_concurrency() == 2 def test_target_concurrency_cpu_count_not_implemented(): @@ -126,10 +116,20 @@ def test_target_concurrency_cpu_count_not_implemented(): multiprocessing, 'cpu_count', side_effect=NotImplementedError, ): with mock.patch.dict(os.environ, {}, clear=True): - assert helpers.target_concurrency(SERIAL_FALSE) == 1 + assert helpers.target_concurrency() == 1 def test_shuffled_is_deterministic(): seq = [str(i) for i in range(10)] expected = ['4', '0', '5', '1', '8', '6', '2', '3', '7', '9'] assert helpers._shuffled(seq) == expected + + +def test_xargs_require_serial_is_not_shuffled(): + ret, out = helpers.run_xargs( + ('echo',), [str(i) for i in range(10)], + require_serial=True, + color=False, + ) + assert ret == 0 + assert out.strip() == b'0 1 2 3 4 5 6 7 8 9' diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index c653a3ccf..d23441401 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -23,7 +23,7 @@ def _test_r_parsing( repo = make_repo(tempdir_factory, 'r_hooks_repo') config = make_config_from_repo(repo) hook = _get_hook_no_install(config, store, hook_id) - ret = r._cmd_from_hook(hook) + ret = r._cmd_from_hook(hook.prefix, hook.entry, hook.args) expected_path = os.path.join(hook.prefix.prefix_dir, f'{hook_id}.R') expected = ( 'Rscript', @@ -111,7 +111,7 @@ def test_r_parsing_file_local(tempdir_factory, store): }], } hook = _get_hook_no_install(config, store, 'local-r') - ret = r._cmd_from_hook(hook) + ret = r._cmd_from_hook(hook.prefix, hook.entry, hook.args) assert ret == ( 'Rscript', '--no-save', '--no-restore', '--no-site-file', '--no-environ', diff --git a/tests/repository_test.py b/tests/repository_test.py index 236c7983b..4043491bc 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -45,7 +45,14 @@ def _norm_out(b): def _hook_run(hook, filenames, color): with languages[hook.language].in_env(hook.prefix, hook.language_version): - return languages[hook.language].run_hook(hook, filenames, color) + return languages[hook.language].run_hook( + hook.prefix, + hook.entry, + hook.args, + filenames, + require_serial=hook.require_serial, + color=color, + ) def _get_hook_no_install(repo_config, store, hook_id): From 70bfd76ced283f48ab8c8a5f17e9218c0b0b5d37 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 Jan 2023 18:33:40 -0500 Subject: [PATCH 098/342] coursier: additional_dependencies support --- .github/actions/pre-test/action.yml | 1 + pre_commit/languages/coursier.py | 47 ++++++++++++------- testing/get-coursier.ps1 | 11 ----- testing/get-coursier.sh | 34 ++++++++++---- testing/language_helpers.py | 33 +++++++++++++ .../.pre-commit-channel/echo-java.json | 8 ---- .../.pre-commit-hooks.yaml | 5 -- testing/util.py | 4 -- tests/languages/coursier_test.py | 45 ++++++++++++++++++ tests/repository_test.py | 10 ---- 10 files changed, 132 insertions(+), 66 deletions(-) delete mode 100755 testing/get-coursier.ps1 create mode 100644 testing/language_helpers.py delete mode 100644 testing/resources/coursier_hooks_repo/.pre-commit-channel/echo-java.json delete mode 100644 testing/resources/coursier_hooks_repo/.pre-commit-hooks.yaml create mode 100644 tests/languages/coursier_test.py diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml index a7bf0abed..608c0cd11 100644 --- a/.github/actions/pre-test/action.yml +++ b/.github/actions/pre-test/action.yml @@ -19,6 +19,7 @@ runs: echo 'C:\Strawberry\perl\site\bin' >> "$GITHUB_PATH" echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" + testing/get-coursier.sh testing/get-dart.sh - name: setup (linux) shell: bash diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index a6aea3fb2..69c877d32 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -1,13 +1,14 @@ from __future__ import annotations import contextlib -import os +import os.path from typing import Generator from typing import Sequence from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var +from pre_commit.errors import FatalError from pre_commit.languages import helpers from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix @@ -23,9 +24,8 @@ def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], -) -> None: # pragma: win32 no cover +) -> None: helpers.assert_version_default('coursier', version) - helpers.assert_no_additional_deps('coursier', additional_dependencies) # Support both possible executable names (either "cs" or "coursier") executable = find_executable('cs') or find_executable('coursier') @@ -37,29 +37,40 @@ def install_environment( envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) channel = prefix.path('.pre-commit-channel') - for app_descriptor in os.listdir(channel): - _, app_file = os.path.split(app_descriptor) - app, _ = os.path.splitext(app_file) - helpers.run_setup_cmd( - prefix, - ( - executable, - 'install', - '--default-channels=false', - f'--channel={channel}', - app, - f'--dir={envdir}', - ), + if os.path.isdir(channel): + for app_descriptor in os.listdir(channel): + _, app_file = os.path.split(app_descriptor) + app, _ = os.path.splitext(app_file) + helpers.run_setup_cmd( + prefix, + ( + executable, + 'install', + '--default-channels=false', + '--channel', channel, + '--dir', envdir, + app, + ), + ) + elif not additional_dependencies: + raise FatalError( + 'expected .pre-commit-channel dir or additional_dependencies', ) + if additional_dependencies: + install_cmd = ( + executable, 'install', '--dir', envdir, *additional_dependencies, + ) + helpers.run_setup_cmd(prefix, install_cmd) + -def get_env_patch(target_dir: str) -> PatchesT: # pragma: win32 no cover +def get_env_patch(target_dir: str) -> PatchesT: return ( ('PATH', (target_dir, os.pathsep, Var('PATH'))), ) -@contextlib.contextmanager # pragma: win32 no cover +@contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): diff --git a/testing/get-coursier.ps1 b/testing/get-coursier.ps1 deleted file mode 100755 index 42e563549..000000000 --- a/testing/get-coursier.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -$wc = New-Object System.Net.WebClient - -$coursier_url = "https://github.com/coursier/coursier/releases/download/v2.0.5/cs-x86_64-pc-win32.exe" -$coursier_dest = "C:\coursier\cs.exe" -$coursier_hash ="d63d497f7805261e1cd657b8aaa626f6b8f7264cdb68219b2e6be9dd882033a9" - -New-Item -Path "C:\" -Name "coursier" -ItemType "directory" -$wc.DownloadFile($coursier_url, $coursier_dest) -if ((Get-FileHash $coursier_dest -Algorithm SHA256).Hash -ne $coursier_hash) { - throw "Invalid coursier file" -} diff --git a/testing/get-coursier.sh b/testing/get-coursier.sh index 6033c3e35..958e73b24 100755 --- a/testing/get-coursier.sh +++ b/testing/get-coursier.sh @@ -1,15 +1,29 @@ #!/usr/bin/env bash -# This is a script used in CI to install coursier set -euo pipefail -COURSIER_URL="https://github.com/coursier/coursier/releases/download/v2.0.0/cs-x86_64-pc-linux" -COURSIER_HASH="e2e838b75bc71b16bcb77ce951ad65660c89bda7957c79a0628ec7146d35122f" -ARTIFACT="/tmp/coursier/cs" +if [ "$OSTYPE" = msys ]; then + URL='https://github.com/coursier/coursier/releases/download/v2.1.0-RC4/cs-x86_64-pc-win32.zip' + SHA256='0d07386ff0f337e3e6264f7dde29d137dda6eaa2385f29741435e0b93ccdb49d' + TARGET='/tmp/coursier/cs.zip' -mkdir -p /tmp/coursier -rm -f "$ARTIFACT" -curl --location --silent --output "$ARTIFACT" "$COURSIER_URL" -echo "$COURSIER_HASH $ARTIFACT" | sha256sum --check -chmod ugo+x /tmp/coursier/cs + unpack() { + unzip "$TARGET" -d /tmp/coursier + mv /tmp/coursier/cs-*.exe /tmp/coursier/cs.exe + cygpath -w /tmp/coursier >> "$GITHUB_PATH" + } +else + URL='https://github.com/coursier/coursier/releases/download/v2.1.0-RC4/cs-x86_64-pc-linux.gz' + SHA256='176e92e08ab292531aa0c4993dbc9f2c99dec79578752f3b9285f54f306db572' + TARGET=/tmp/coursier/cs.gz + + unpack() { + gunzip "$TARGET" + chmod +x /tmp/coursier/cs + echo /tmp/coursier >> "$GITHUB_PATH" + } +fi -echo '/tmp/coursier' >> "$GITHUB_PATH" +mkdir -p /tmp/coursier +curl --location --silent --output "$TARGET" "$URL" +echo "$SHA256 $TARGET" | sha256sum --check +unpack diff --git a/testing/language_helpers.py b/testing/language_helpers.py new file mode 100644 index 000000000..02e47a002 --- /dev/null +++ b/testing/language_helpers.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import os +from typing import Sequence + +import pre_commit.constants as C +from pre_commit.languages.all import Language +from pre_commit.prefix import Prefix + + +def run_language( + path: os.PathLike[str], + language: Language, + exe: str, + args: Sequence[str] = (), + file_args: Sequence[str] = (), + version: str = C.DEFAULT, + deps: Sequence[str] = (), +) -> tuple[int, bytes]: + prefix = Prefix(str(path)) + + language.install_environment(prefix, version, deps) + with language.in_env(prefix, version): + ret, out = language.run_hook( + prefix, + exe, + args, + file_args, + require_serial=True, + color=False, + ) + out = out.replace(b'\r\n', b'\n') + return ret, out diff --git a/testing/resources/coursier_hooks_repo/.pre-commit-channel/echo-java.json b/testing/resources/coursier_hooks_repo/.pre-commit-channel/echo-java.json deleted file mode 100644 index 37f401e2c..000000000 --- a/testing/resources/coursier_hooks_repo/.pre-commit-channel/echo-java.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "repositories": [ - "central" - ], - "dependencies": [ - "io.get-coursier:echo:latest.stable" - ] -} diff --git a/testing/resources/coursier_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/coursier_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index d4a143b3d..000000000 --- a/testing/resources/coursier_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: echo-java - name: echo-java - description: echo from java - entry: echo-java - language: coursier diff --git a/testing/util.py b/testing/util.py index e807f0482..324f1f6c3 100644 --- a/testing/util.py +++ b/testing/util.py @@ -42,10 +42,6 @@ def cmd_output_mocked_pre_commit_home( return ret, out.replace('\r\n', '\n'), None -skipif_cant_run_coursier = pytest.mark.skipif( - os.name == 'nt' or parse_shebang.find_executable('cs') is None, - reason="coursier isn't installed or can't be found", -) skipif_cant_run_docker = pytest.mark.skipif( os.name == 'nt' or not docker_is_running(), reason="Docker isn't running or can't be accessed", diff --git a/tests/languages/coursier_test.py b/tests/languages/coursier_test.py new file mode 100644 index 000000000..dbb746ca8 --- /dev/null +++ b/tests/languages/coursier_test.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import pytest + +from pre_commit.errors import FatalError +from pre_commit.languages import coursier +from testing.language_helpers import run_language + + +def test_coursier_hook(tmp_path): + echo_java_json = '''\ +{ + "repositories": ["central"], + "dependencies": ["io.get-coursier:echo:latest.stable"] +} +''' + + channel_dir = tmp_path.joinpath('.pre-commit-channel') + channel_dir.mkdir() + channel_dir.joinpath('echo-java.json').write_text(echo_java_json) + + ret = run_language( + tmp_path, + coursier, + 'echo-java', + args=('Hello', 'World', 'from', 'coursier'), + ) + assert ret == (0, b'Hello World from coursier\n') + + +def test_coursier_hook_additional_dependencies(tmp_path): + ret = run_language( + tmp_path, + coursier, + 'scalafmt --version', + deps=('scalafmt:3.6.1',), + ) + assert ret == (0, b'scalafmt 3.6.1\n') + + +def test_error_if_no_deps_or_channel(tmp_path): + with pytest.raises(FatalError) as excinfo: + run_language(tmp_path, coursier, 'dne') + msg, = excinfo.value.args + assert msg == 'expected .pre-commit-channel dir or additional_dependencies' diff --git a/tests/repository_test.py b/tests/repository_test.py index 4043491bc..5e4dff1e0 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -32,7 +32,6 @@ from testing.fixtures import modify_manifest from testing.util import cwd from testing.util import get_resource_path -from testing.util import skipif_cant_run_coursier from testing.util import skipif_cant_run_docker from testing.util import skipif_cant_run_lua from testing.util import skipif_cant_run_swift @@ -199,15 +198,6 @@ def test_language_versioned_python_hook(tempdir_factory, store): ) -@skipif_cant_run_coursier # pragma: win32 no cover -def test_run_a_coursier_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'coursier_hooks_repo', - 'echo-java', - ['Hello World from coursier'], b'Hello World from coursier\n', - ) - - @skipif_cant_run_docker # pragma: win32 no cover def test_run_a_docker_hook(tempdir_factory, store): _test_hook_repo( From f1b5f6637481704b687b2f3bbda49500af7849c1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 17 Jan 2023 11:39:48 -0500 Subject: [PATCH 099/342] test conda language directly --- pre_commit/store.py | 40 +++++++++--------- .../conda_hooks_repo/.pre-commit-hooks.yaml | 10 ----- .../conda_hooks_repo/environment.yml | 6 --- tests/languages/conda_test.py | 36 +++++++++++++++- tests/repository_test.py | 41 ------------------- tests/store_test.py | 3 +- 6 files changed, 57 insertions(+), 79 deletions(-) delete mode 100644 testing/resources/conda_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/conda_hooks_repo/environment.yml diff --git a/pre_commit/store.py b/pre_commit/store.py index e42cc4897..6ddc7c481 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -36,6 +36,26 @@ def _get_default_directory() -> str: return os.path.realpath(ret) +_LOCAL_RESOURCES = ( + 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', + 'package.json', 'pre-commit-package-dev-1.rockspec', + 'pre_commit_placeholder_package.gemspec', 'setup.py', + 'environment.yml', 'Makefile.PL', 'pubspec.yaml', + 'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv', +) + + +def _make_local_repo(directory: str) -> None: + for resource in _LOCAL_RESOURCES: + resource_dirname, resource_basename = os.path.split(resource) + contents = resource_text(f'empty_template_{resource_basename}') + target_dir = os.path.join(directory, resource_dirname) + target_file = os.path.join(target_dir, resource_basename) + os.makedirs(target_dir, exist_ok=True) + with open(target_file, 'w') as f: + f.write(contents) + + class Store: get_default_directory = staticmethod(_get_default_directory) @@ -185,27 +205,9 @@ def _git_cmd(*args: str) -> None: return self._new_repo(repo, ref, deps, clone_strategy) - LOCAL_RESOURCES = ( - 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', - 'package.json', 'pre-commit-package-dev-1.rockspec', - 'pre_commit_placeholder_package.gemspec', 'setup.py', - 'environment.yml', 'Makefile.PL', 'pubspec.yaml', - 'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv', - ) - def make_local(self, deps: Sequence[str]) -> str: - def make_local_strategy(directory: str) -> None: - for resource in self.LOCAL_RESOURCES: - resource_dirname, resource_basename = os.path.split(resource) - contents = resource_text(f'empty_template_{resource_basename}') - target_dir = os.path.join(directory, resource_dirname) - target_file = os.path.join(target_dir, resource_basename) - os.makedirs(target_dir, exist_ok=True) - with open(target_file, 'w') as f: - f.write(contents) - return self._new_repo( - 'local', C.LOCAL_REPO_VERSION, deps, make_local_strategy, + 'local', C.LOCAL_REPO_VERSION, deps, _make_local_repo, ) def _create_config_table(self, db: sqlite3.Connection) -> None: diff --git a/testing/resources/conda_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/conda_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index a0d274c23..000000000 --- a/testing/resources/conda_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,10 +0,0 @@ -- id: sys-exec - name: sys-exec - entry: python -c 'import os; import sys; print(sys.executable.split(os.path.sep)[-2]) if os.name == "nt" else print(sys.executable.split(os.path.sep)[-3])' - language: conda - files: \.py$ -- id: additional-deps - name: additional-deps - entry: python - language: conda - files: \.py$ diff --git a/testing/resources/conda_hooks_repo/environment.yml b/testing/resources/conda_hooks_repo/environment.yml deleted file mode 100644 index e23c079fd..000000000 --- a/testing/resources/conda_hooks_repo/environment.yml +++ /dev/null @@ -1,6 +0,0 @@ -channels: - - conda-forge - - defaults -dependencies: - - python - - pip diff --git a/tests/languages/conda_test.py b/tests/languages/conda_test.py index 5023b2afb..83aaebed3 100644 --- a/tests/languages/conda_test.py +++ b/tests/languages/conda_test.py @@ -1,9 +1,13 @@ from __future__ import annotations +import os.path + import pytest from pre_commit import envcontext -from pre_commit.languages.conda import _conda_exe +from pre_commit.languages import conda +from pre_commit.store import _make_local_repo +from testing.language_helpers import run_language @pytest.mark.parametrize( @@ -37,4 +41,32 @@ ) def test_conda_exe(ctx, expected): with envcontext.envcontext(ctx): - assert _conda_exe() == expected + assert conda._conda_exe() == expected + + +def test_conda_language(tmp_path): + environment_yml = '''\ +channels: [conda-forge, defaults] +dependencies: [python, pip] +''' + tmp_path.joinpath('environment.yml').write_text(environment_yml) + + ret, out = run_language( + tmp_path, + conda, + 'python -c "import sys; print(sys.prefix)"', + ) + assert ret == 0 + assert os.path.basename(out.strip()) == b'conda-default' + + +def test_conda_additional_deps(tmp_path): + _make_local_repo(tmp_path) + + ret = run_language( + tmp_path, + conda, + 'python -c "import botocore; print(1)"', + deps=('botocore',), + ) + assert ret == (0, b'1\n') diff --git a/tests/repository_test.py b/tests/repository_test.py index 5e4dff1e0..0bf27967d 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -88,47 +88,6 @@ def _test_hook_repo( assert _norm_out(out) == expected -def test_conda_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'conda_hooks_repo', - 'sys-exec', [os.devnull], - b'conda-default\n', - ) - - -def test_conda_with_additional_dependencies_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'conda_hooks_repo', - 'additional-deps', [os.devnull], - b'OK\n', - config_kwargs={ - 'hooks': [{ - 'id': 'additional-deps', - 'args': ['-c', 'import tzdata; print("OK")'], - 'additional_dependencies': ['python-tzdata'], - }], - }, - ) - - -def test_local_conda_additional_dependencies(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'local-conda', - 'name': 'local-conda', - 'entry': 'python', - 'language': 'conda', - 'args': ['-c', 'import botocore; print("OK")'], - 'additional_dependencies': ['botocore'], - }], - } - hook = _get_hook(config, store, 'local-conda') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - assert _norm_out(out) == b'OK\n' - - def test_python_hook(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'python_hooks_repo', diff --git a/tests/store_test.py b/tests/store_test.py index 818776623..c42ce6537 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -9,6 +9,7 @@ from pre_commit import git from pre_commit.store import _get_default_directory +from pre_commit.store import _LOCAL_RESOURCES from pre_commit.store import Store from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output @@ -188,7 +189,7 @@ def test_local_resources_reflects_reality(): for res in os.listdir('pre_commit/resources') if res.startswith('empty_template_') } - assert on_disk == {os.path.basename(x) for x in Store.LOCAL_RESOURCES} + assert on_disk == {os.path.basename(x) for x in _LOCAL_RESOURCES} def test_mark_config_as_used(store, tmpdir): From c36f03cd2e8ae948b35516affa8a4b71c6fd3289 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 Jan 2023 17:10:58 -0500 Subject: [PATCH 100/342] test swift language directly --- testing/resources/swift_hooks_repo/.gitignore | 4 --- .../swift_hooks_repo/.pre-commit-hooks.yaml | 6 ---- .../resources/swift_hooks_repo/Package.swift | 7 ----- .../Sources/swift_hooks_repo/main.swift | 1 - testing/util.py | 5 --- tests/languages/swift_test.py | 31 +++++++++++++++++++ tests/repository_test.py | 9 ------ 7 files changed, 31 insertions(+), 32 deletions(-) delete mode 100644 testing/resources/swift_hooks_repo/.gitignore delete mode 100644 testing/resources/swift_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/swift_hooks_repo/Package.swift delete mode 100644 testing/resources/swift_hooks_repo/Sources/swift_hooks_repo/main.swift create mode 100644 tests/languages/swift_test.py diff --git a/testing/resources/swift_hooks_repo/.gitignore b/testing/resources/swift_hooks_repo/.gitignore deleted file mode 100644 index 02c087533..000000000 --- a/testing/resources/swift_hooks_repo/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -/.build -/Packages -/*.xcodeproj diff --git a/testing/resources/swift_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/swift_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index c08df87d4..000000000 --- a/testing/resources/swift_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,6 +0,0 @@ -- id: swift-hooks-repo - name: Swift hooks repo example - description: Runs the hello world app generated by swift package init --type executable (binary called swift_hooks_repo here) - entry: swift_hooks_repo - language: swift - files: \.(swift)$ diff --git a/testing/resources/swift_hooks_repo/Package.swift b/testing/resources/swift_hooks_repo/Package.swift deleted file mode 100644 index 04976d3ff..000000000 --- a/testing/resources/swift_hooks_repo/Package.swift +++ /dev/null @@ -1,7 +0,0 @@ -// swift-tools-version:5.0 -import PackageDescription - -let package = Package( - name: "swift_hooks_repo", - targets: [.target(name: "swift_hooks_repo")] -) diff --git a/testing/resources/swift_hooks_repo/Sources/swift_hooks_repo/main.swift b/testing/resources/swift_hooks_repo/Sources/swift_hooks_repo/main.swift deleted file mode 100644 index f7cf60e14..000000000 --- a/testing/resources/swift_hooks_repo/Sources/swift_hooks_repo/main.swift +++ /dev/null @@ -1 +0,0 @@ -print("Hello, world!") diff --git a/testing/util.py b/testing/util.py index 324f1f6c3..a5ae06d05 100644 --- a/testing/util.py +++ b/testing/util.py @@ -6,7 +6,6 @@ import pytest -from pre_commit import parse_shebang from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b @@ -50,10 +49,6 @@ def cmd_output_mocked_pre_commit_home( os.name == 'nt', reason="lua isn't installed or can't be found", ) -skipif_cant_run_swift = pytest.mark.skipif( - parse_shebang.find_executable('swift') is None, - reason="swift isn't installed or can't be found", -) xfailif_windows = pytest.mark.xfail(os.name == 'nt', reason='windows') diff --git a/tests/languages/swift_test.py b/tests/languages/swift_test.py new file mode 100644 index 000000000..e0a8ea425 --- /dev/null +++ b/tests/languages/swift_test.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import sys + +import pytest + +from pre_commit.languages import swift +from testing.language_helpers import run_language + + +@pytest.mark.skipif( + sys.platform == 'win32', + reason='swift is not supported on windows', +) +def test_swift_language(tmp_path): # pragma: win32 no cover + package_swift = '''\ +// swift-tools-version:5.0 +import PackageDescription + +let package = Package( + name: "swift_hooks_repo", + targets: [.target(name: "swift_hooks_repo")] +) +''' + tmp_path.joinpath('Package.swift').write_text(package_swift) + src_dir = tmp_path.joinpath('Sources/swift_hooks_repo') + src_dir.mkdir(parents=True) + src_dir.joinpath('main.swift').write_text('print("Hello, world!")\n') + + expected = (0, b'Hello, world!\n') + assert run_language(tmp_path, swift, 'swift_hooks_repo') == expected diff --git a/tests/repository_test.py b/tests/repository_test.py index 0bf27967d..fc2769849 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -34,7 +34,6 @@ from testing.util import get_resource_path from testing.util import skipif_cant_run_docker from testing.util import skipif_cant_run_lua -from testing.util import skipif_cant_run_swift from testing.util import xfailif_windows @@ -329,14 +328,6 @@ def test_system_hook_with_spaces(tempdir_factory, store): ) -@skipif_cant_run_swift # pragma: win32 no cover -def test_swift_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'swift_hooks_repo', - 'swift-hooks-repo', [], b'Hello, world!\n', - ) - - def test_golang_system_hook(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'golang_hooks_repo', From 966c67a8321e301d844f776cb438c4b5808abbc6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 17 Jan 2023 14:16:13 -0500 Subject: [PATCH 101/342] speed up R unit tests --- pre_commit/languages/r.py | 2 +- .../r_hooks_repo/.pre-commit-hooks.yaml | 23 ---- tests/languages/r_test.py | 121 +++++++----------- 3 files changed, 46 insertions(+), 100 deletions(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 7ed3eafcd..dc3986057 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -64,7 +64,7 @@ def _entry_validate(entry: list[str]) -> None: raise ValueError('You can supply at most one expression.') elif len(entry) > 2: raise ValueError( - 'The only valid syntax is `Rscript -e {expr}`', + 'The only valid syntax is `Rscript -e {expr}`' 'or `Rscript path/to/hook/script`', ) diff --git a/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml index b3545d969..ef46cc0a2 100644 --- a/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml +++ b/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml @@ -1,26 +1,3 @@ -# parsing file -- id: parse-file-no-opts-no-args - name: Say hi - entry: Rscript parse-file-no-opts-no-args.R - language: r - types: [r] -- id: parse-file-no-opts-args - name: Say hi - entry: Rscript parse-file-no-opts-args.R - args: [--no-cache] - language: r - types: [r] -## parsing expr -- id: parse-expr-no-opts-no-args-1 - name: Say hi - entry: Rscript -e '1+1' - language: r - types: [r] -- id: parse-expr-args-in-entry-2 - name: Say hi - entry: Rscript -e '1+1' -e '3' --no-cache3 - language: r - types: [r] # real world - id: hello-world name: Say hi diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index d23441401..0c5e56382 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -6,117 +6,86 @@ from pre_commit import envcontext from pre_commit.languages import r +from pre_commit.prefix import Prefix from pre_commit.util import win_exe -from testing.fixtures import make_config_from_repo -from testing.fixtures import make_repo -from tests.repository_test import _get_hook_no_install - - -def _test_r_parsing( - tempdir_factory, - store, - hook_id, - expected_hook_expr=(), - expected_args=(), - config=None, -): - repo = make_repo(tempdir_factory, 'r_hooks_repo') - config = make_config_from_repo(repo) - hook = _get_hook_no_install(config, store, hook_id) - ret = r._cmd_from_hook(hook.prefix, hook.entry, hook.args) - expected_path = os.path.join(hook.prefix.prefix_dir, f'{hook_id}.R') - expected = ( + + +def test_r_parsing_file_no_opts_no_args(tmp_path): + cmd = r._cmd_from_hook(Prefix(str(tmp_path)), 'Rscript some-script.R', ()) + assert cmd == ( 'Rscript', '--no-save', '--no-restore', '--no-site-file', '--no-environ', - *(expected_hook_expr or (expected_path,)), - *expected_args, + str(tmp_path.joinpath('some-script.R')), ) - assert ret == expected - - -def test_r_parsing_file_no_opts_no_args(tempdir_factory, store): - hook_id = 'parse-file-no-opts-no-args' - _test_r_parsing(tempdir_factory, store, hook_id) -def test_r_parsing_file_opts_no_args(tempdir_factory, store): +def test_r_parsing_file_opts_no_args(): with pytest.raises(ValueError) as excinfo: r._entry_validate(['Rscript', '--no-init', '/path/to/file']) - msg = excinfo.value.args + msg, = excinfo.value.args assert msg == ( - 'The only valid syntax is `Rscript -e {expr}`', - 'or `Rscript path/to/hook/script`', + 'The only valid syntax is `Rscript -e {expr}`' + 'or `Rscript path/to/hook/script`' ) -def test_r_parsing_file_no_opts_args(tempdir_factory, store): - hook_id = 'parse-file-no-opts-args' - expected_args = ['--no-cache'] - _test_r_parsing( - tempdir_factory, store, hook_id, expected_args=expected_args, +def test_r_parsing_file_no_opts_args(tmp_path): + cmd = r._cmd_from_hook( + Prefix(str(tmp_path)), + 'Rscript some-script.R', + ('--no-cache',), + ) + assert cmd == ( + 'Rscript', + '--no-save', '--no-restore', '--no-site-file', '--no-environ', + str(tmp_path.joinpath('some-script.R')), + '--no-cache', ) -def test_r_parsing_expr_no_opts_no_args1(tempdir_factory, store): - hook_id = 'parse-expr-no-opts-no-args-1' - _test_r_parsing( - tempdir_factory, store, hook_id, expected_hook_expr=('-e', '1+1'), +def test_r_parsing_expr_no_opts_no_args1(tmp_path): + cmd = r._cmd_from_hook(Prefix(str(tmp_path)), "Rscript -e '1+1'", ()) + assert cmd == ( + 'Rscript', + '--no-save', '--no-restore', '--no-site-file', '--no-environ', + '-e', '1+1', ) -def test_r_parsing_expr_no_opts_no_args2(tempdir_factory, store): - with pytest.raises(ValueError) as execinfo: +def test_r_parsing_expr_no_opts_no_args2(): + with pytest.raises(ValueError) as excinfo: r._entry_validate(['Rscript', '-e', '1+1', '-e', 'letters']) - msg = execinfo.value.args - assert msg == ('You can supply at most one expression.',) + msg, = excinfo.value.args + assert msg == 'You can supply at most one expression.' -def test_r_parsing_expr_opts_no_args2(tempdir_factory, store): - with pytest.raises(ValueError) as execinfo: +def test_r_parsing_expr_opts_no_args2(): + with pytest.raises(ValueError) as excinfo: r._entry_validate( ['Rscript', '--vanilla', '-e', '1+1', '-e', 'letters'], ) - msg = execinfo.value.args + msg, = excinfo.value.args assert msg == ( - 'The only valid syntax is `Rscript -e {expr}`', - 'or `Rscript path/to/hook/script`', + 'The only valid syntax is `Rscript -e {expr}`' + 'or `Rscript path/to/hook/script`' ) -def test_r_parsing_expr_args_in_entry2(tempdir_factory, store): - with pytest.raises(ValueError) as execinfo: +def test_r_parsing_expr_args_in_entry2(): + with pytest.raises(ValueError) as excinfo: r._entry_validate(['Rscript', '-e', 'expr1', '--another-arg']) - msg = execinfo.value.args - assert msg == ('You can supply at most one expression.',) + msg, = excinfo.value.args + assert msg == 'You can supply at most one expression.' -def test_r_parsing_expr_non_Rscirpt(tempdir_factory, store): - with pytest.raises(ValueError) as execinfo: +def test_r_parsing_expr_non_Rscirpt(): + with pytest.raises(ValueError) as excinfo: r._entry_validate(['AnotherScript', '-e', '{{}}']) - msg = execinfo.value.args - assert msg == ('entry must start with `Rscript`.',) - - -def test_r_parsing_file_local(tempdir_factory, store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'local-r', - 'name': 'local-r', - 'entry': 'Rscript path/to/script.R', - 'language': 'r', - }], - } - hook = _get_hook_no_install(config, store, 'local-r') - ret = r._cmd_from_hook(hook.prefix, hook.entry, hook.args) - assert ret == ( - 'Rscript', - '--no-save', '--no-restore', '--no-site-file', '--no-environ', - hook.prefix.path('path/to/script.R'), - ) + msg, = excinfo.value.args + assert msg == 'entry must start with `Rscript`.' def test_rscript_exec_relative_to_r_home(): From d24055cb40a4473754cb7560408a2c15544b387b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 17 Jan 2023 17:34:04 -0500 Subject: [PATCH 102/342] test perl language directly --- testing/resources/perl_hooks_repo/.gitignore | 7 -- .../perl_hooks_repo/.pre-commit-hooks.yaml | 5 -- testing/resources/perl_hooks_repo/MANIFEST | 4 -- testing/resources/perl_hooks_repo/Makefile.PL | 10 --- .../perl_hooks_repo/bin/pre-commit-perl-hello | 7 -- .../perl_hooks_repo/lib/PreCommitHello.pm | 12 ---- tests/languages/perl_test.py | 69 +++++++++++++++++++ tests/repository_test.py | 24 ------- 8 files changed, 69 insertions(+), 69 deletions(-) delete mode 100644 testing/resources/perl_hooks_repo/.gitignore delete mode 100644 testing/resources/perl_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/perl_hooks_repo/MANIFEST delete mode 100644 testing/resources/perl_hooks_repo/Makefile.PL delete mode 100755 testing/resources/perl_hooks_repo/bin/pre-commit-perl-hello delete mode 100644 testing/resources/perl_hooks_repo/lib/PreCommitHello.pm create mode 100644 tests/languages/perl_test.py diff --git a/testing/resources/perl_hooks_repo/.gitignore b/testing/resources/perl_hooks_repo/.gitignore deleted file mode 100644 index 7af994045..000000000 --- a/testing/resources/perl_hooks_repo/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/MYMETA.json -/MYMETA.yml -/Makefile -/PreCommitHello-*.tar.* -/PreCommitHello-*/ -/blib/ -/pm_to_blib diff --git a/testing/resources/perl_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/perl_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 11e6f6cd9..000000000 --- a/testing/resources/perl_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: perl-hook - name: perl example hook - entry: pre-commit-perl-hello - language: perl - files: '' diff --git a/testing/resources/perl_hooks_repo/MANIFEST b/testing/resources/perl_hooks_repo/MANIFEST deleted file mode 100644 index 4a20084c6..000000000 --- a/testing/resources/perl_hooks_repo/MANIFEST +++ /dev/null @@ -1,4 +0,0 @@ -MANIFEST -Makefile.PL -bin/pre-commit-perl-hello -lib/PreCommitHello.pm diff --git a/testing/resources/perl_hooks_repo/Makefile.PL b/testing/resources/perl_hooks_repo/Makefile.PL deleted file mode 100644 index 6c70e1071..000000000 --- a/testing/resources/perl_hooks_repo/Makefile.PL +++ /dev/null @@ -1,10 +0,0 @@ -use strict; -use warnings; - -use ExtUtils::MakeMaker; - -WriteMakefile( - NAME => "PreCommitHello", - VERSION_FROM => "lib/PreCommitHello.pm", - EXE_FILES => [qw(bin/pre-commit-perl-hello)], -); diff --git a/testing/resources/perl_hooks_repo/bin/pre-commit-perl-hello b/testing/resources/perl_hooks_repo/bin/pre-commit-perl-hello deleted file mode 100755 index 9474009a1..000000000 --- a/testing/resources/perl_hooks_repo/bin/pre-commit-perl-hello +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; -use PreCommitHello; - -PreCommitHello::hello(); diff --git a/testing/resources/perl_hooks_repo/lib/PreCommitHello.pm b/testing/resources/perl_hooks_repo/lib/PreCommitHello.pm deleted file mode 100644 index c76521cea..000000000 --- a/testing/resources/perl_hooks_repo/lib/PreCommitHello.pm +++ /dev/null @@ -1,12 +0,0 @@ -package PreCommitHello; - -use strict; -use warnings; - -our $VERSION = "0.1.0"; - -sub hello { - print "Hello from perl-commit Perl!\n"; -} - -1; diff --git a/tests/languages/perl_test.py b/tests/languages/perl_test.py new file mode 100644 index 000000000..042478dbb --- /dev/null +++ b/tests/languages/perl_test.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from pre_commit.languages import perl +from pre_commit.store import _make_local_repo +from pre_commit.util import make_executable +from testing.language_helpers import run_language + + +def test_perl_install(tmp_path): + makefile_pl = '''\ +use strict; +use warnings; + +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => "PreCommitHello", + VERSION_FROM => "lib/PreCommitHello.pm", + EXE_FILES => [qw(bin/pre-commit-perl-hello)], +); +''' + bin_perl_hello = '''\ +#!/usr/bin/env perl + +use strict; +use warnings; +use PreCommitHello; + +PreCommitHello::hello(); +''' + lib_hello_pm = '''\ +package PreCommitHello; + +use strict; +use warnings; + +our $VERSION = "0.1.0"; + +sub hello { + print "Hello from perl-commit Perl!\n"; +} + +1; +''' + tmp_path.joinpath('Makefile.PL').write_text(makefile_pl) + bin_dir = tmp_path.joinpath('bin') + bin_dir.mkdir() + exe = bin_dir.joinpath('pre-commit-perl-hello') + exe.write_text(bin_perl_hello) + make_executable(exe) + lib_dir = tmp_path.joinpath('lib') + lib_dir.mkdir() + lib_dir.joinpath('PreCommitHello.pm').write_text(lib_hello_pm) + + ret = run_language(tmp_path, perl, 'pre-commit-perl-hello') + assert ret == (0, b'Hello from perl-commit Perl!\n') + + +def test_perl_additional_dependencies(tmp_path): + _make_local_repo(str(tmp_path)) + + ret, out = run_language( + tmp_path, + perl, + 'perltidy --version', + deps=('SHANCOCK/Perl-Tidy-20211029.tar.gz',), + ) + assert ret == 0 + assert out.startswith(b'This is perltidy, v20211029') diff --git a/tests/repository_test.py b/tests/repository_test.py index fc2769849..2389c4483 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -981,30 +981,6 @@ def test_manifest_hooks(tempdir_factory, store): ) -def test_perl_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'perl_hooks_repo', - 'perl-hook', [], b'Hello from perl-commit Perl!\n', - ) - - -def test_local_perl_additional_dependencies(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'hello', - 'name': 'hello', - 'entry': 'perltidy --version', - 'language': 'perl', - 'additional_dependencies': ['SHANCOCK/Perl-Tidy-20211029.tar.gz'], - }], - } - hook = _get_hook(config, store, 'hello') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - assert _norm_out(out).startswith(b'This is perltidy, v20211029') - - @pytest.mark.parametrize( 'repo', ( From 043565d28a0cccda9892baa414ee52c2f5b61372 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 17 Jan 2023 18:44:14 -0500 Subject: [PATCH 103/342] test dart directly --- .../dart_repo/.pre-commit-hooks.yaml | 4 -- .../dart_repo/bin/hello-world-dart.dart | 6 -- testing/resources/dart_repo/pubspec.yaml | 10 --- tests/languages/dart_test.py | 62 +++++++++++++++++++ tests/repository_test.py | 40 ------------ 5 files changed, 62 insertions(+), 60 deletions(-) delete mode 100644 testing/resources/dart_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/dart_repo/bin/hello-world-dart.dart delete mode 100644 testing/resources/dart_repo/pubspec.yaml create mode 100644 tests/languages/dart_test.py diff --git a/testing/resources/dart_repo/.pre-commit-hooks.yaml b/testing/resources/dart_repo/.pre-commit-hooks.yaml deleted file mode 100644 index e0dc5a2a9..000000000 --- a/testing/resources/dart_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,4 +0,0 @@ -- id: hello-world-dart - name: hello world dart - entry: hello-world-dart - language: dart diff --git a/testing/resources/dart_repo/bin/hello-world-dart.dart b/testing/resources/dart_repo/bin/hello-world-dart.dart deleted file mode 100644 index 5d8d6a6af..000000000 --- a/testing/resources/dart_repo/bin/hello-world-dart.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:ansicolor/ansicolor.dart'; - -void main() { - AnsiPen pen = new AnsiPen()..red(); - print("hello hello " + pen("world")); -} diff --git a/testing/resources/dart_repo/pubspec.yaml b/testing/resources/dart_repo/pubspec.yaml deleted file mode 100644 index bc719d055..000000000 --- a/testing/resources/dart_repo/pubspec.yaml +++ /dev/null @@ -1,10 +0,0 @@ -environment: - sdk: '>=2.10.0 <3.0.0' - -name: hello_world_dart - -executables: - hello-world-dart: - -dependencies: - ansicolor: ^2.0.1 diff --git a/tests/languages/dart_test.py b/tests/languages/dart_test.py new file mode 100644 index 000000000..5bb5aa68f --- /dev/null +++ b/tests/languages/dart_test.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import re_assert + +from pre_commit.languages import dart +from pre_commit.store import _make_local_repo +from testing.language_helpers import run_language + + +def test_dart(tmp_path): + pubspec_yaml = '''\ +environment: + sdk: '>=2.10.0 <3.0.0' + +name: hello_world_dart + +executables: + hello-world-dart: + +dependencies: + ansicolor: ^2.0.1 +''' + hello_world_dart_dart = '''\ +import 'package:ansicolor/ansicolor.dart'; + +void main() { + AnsiPen pen = new AnsiPen()..red(); + print("hello hello " + pen("world")); +} +''' + tmp_path.joinpath('pubspec.yaml').write_text(pubspec_yaml) + bin_dir = tmp_path.joinpath('bin') + bin_dir.mkdir() + bin_dir.joinpath('hello-world-dart.dart').write_text(hello_world_dart_dart) + + expected = (0, b'hello hello world\n') + assert run_language(tmp_path, dart, 'hello-world-dart') == expected + + +def test_dart_additional_deps(tmp_path): + _make_local_repo(str(tmp_path)) + + ret = run_language( + tmp_path, + dart, + 'hello-world-dart', + deps=('hello_world_dart',), + ) + assert ret == (0, b'hello hello world\n') + + +def test_dart_additional_deps_versioned(tmp_path): + _make_local_repo(str(tmp_path)) + + ret, out = run_language( + tmp_path, + dart, + 'secure-random -l 4 -b 16', + deps=('encrypt:5.0.0',), + ) + assert ret == 0 + re_assert.Matches('^[a-f0-9]{8}\n$').assert_matches(out.decode()) diff --git a/tests/repository_test.py b/tests/repository_test.py index 2389c4483..0d01f0f65 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -997,46 +997,6 @@ def test_dotnet_hook(tempdir_factory, store, repo): ) -def test_dart_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'dart_repo', - 'hello-world-dart', [], b'hello hello world\n', - ) - - -def test_local_dart_additional_dependencies(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'local-dart', - 'name': 'local-dart', - 'entry': 'hello-world-dart', - 'language': 'dart', - 'additional_dependencies': ['hello_world_dart'], - }], - } - hook = _get_hook(config, store, 'local-dart') - ret, out = _hook_run(hook, (), color=False) - assert (ret, _norm_out(out)) == (0, b'hello hello world\n') - - -def test_local_dart_additional_dependencies_versioned(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'local-dart', - 'name': 'local-dart', - 'entry': 'secure-random -l 4 -b 16', - 'language': 'dart', - 'additional_dependencies': ['encrypt:5.0.0'], - }], - } - hook = _get_hook(config, store, 'local-dart') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - re_assert.Matches('^[a-f0-9]{8}\r?\n$').assert_matches(out.decode()) - - def test_non_installable_hook_error_for_language_version(store, caplog): config = { 'repo': 'local', From 7512e3b7e1d367464a3b8acad63166a1e55119d1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 17 Jan 2023 23:25:00 -0500 Subject: [PATCH 104/342] test r language directly --- .../r_hooks_repo/.pre-commit-hooks.yaml | 25 - testing/resources/r_hooks_repo/DESCRIPTION | 19 - .../resources/r_hooks_repo/additional-deps.R | 2 - testing/resources/r_hooks_repo/hello-world.R | 5 - testing/resources/r_hooks_repo/renv.lock | 27 -- testing/resources/r_hooks_repo/renv/LICENSE | 7 - .../resources/r_hooks_repo/renv/activate.R | 440 ------------------ tests/languages/r_test.py | 99 ++++ tests/repository_test.py | 48 -- 9 files changed, 99 insertions(+), 573 deletions(-) delete mode 100644 testing/resources/r_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/r_hooks_repo/DESCRIPTION delete mode 100755 testing/resources/r_hooks_repo/additional-deps.R delete mode 100755 testing/resources/r_hooks_repo/hello-world.R delete mode 100644 testing/resources/r_hooks_repo/renv.lock delete mode 100644 testing/resources/r_hooks_repo/renv/LICENSE delete mode 100644 testing/resources/r_hooks_repo/renv/activate.R diff --git a/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index ef46cc0a2..000000000 --- a/testing/resources/r_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# real world -- id: hello-world - name: Say hi - entry: Rscript hello-world.R - args: [blibla] - language: r - types: [r] -- id: hello-world-inline - name: Say hi - entry: | - Rscript -e - 'stopifnot( - packageVersion("rprojroot") == "1.0", - packageVersion("gli.clu") == "0.0.0.9000" - ) - cat(commandArgs(trailingOnly = TRUE), "from R!\n", sep = ", ") - ' - args: ['Hi-there'] - language: r - types: [r] -- id: additional-deps - name: Check additional deps - entry: Rscript additional-deps.R - language: r - types: [r] diff --git a/testing/resources/r_hooks_repo/DESCRIPTION b/testing/resources/r_hooks_repo/DESCRIPTION deleted file mode 100644 index 0e597a8a6..000000000 --- a/testing/resources/r_hooks_repo/DESCRIPTION +++ /dev/null @@ -1,19 +0,0 @@ -Package: gli.clu -Title: What the Package Does (One Line, Title Case) -Type: Package -Version: 0.0.0.9000 -Authors@R: - person(given = "First", - family = "Last", - role = c("aut", "cre"), - email = "first.last@example.com", - comment = c(ORCID = "YOUR-ORCID-ID")) -Description: What the package does (one paragraph). -License: `use_mit_license()`, `use_gpl3_license()` or friends to - pick a license -Encoding: UTF-8 -LazyData: true -Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 -Imports: - rprojroot diff --git a/testing/resources/r_hooks_repo/additional-deps.R b/testing/resources/r_hooks_repo/additional-deps.R deleted file mode 100755 index bc145951b..000000000 --- a/testing/resources/r_hooks_repo/additional-deps.R +++ /dev/null @@ -1,2 +0,0 @@ -suppressPackageStartupMessages(library("cachem")) -cat("OK\n") diff --git a/testing/resources/r_hooks_repo/hello-world.R b/testing/resources/r_hooks_repo/hello-world.R deleted file mode 100755 index bf8d92f42..000000000 --- a/testing/resources/r_hooks_repo/hello-world.R +++ /dev/null @@ -1,5 +0,0 @@ -stopifnot( - packageVersion('rprojroot') == '1.0', - packageVersion('gli.clu') == '0.0.0.9000' -) -cat("Hello, World, from R!\n") diff --git a/testing/resources/r_hooks_repo/renv.lock b/testing/resources/r_hooks_repo/renv.lock deleted file mode 100644 index d7d5fdcc9..000000000 --- a/testing/resources/r_hooks_repo/renv.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "R": { - "Version": "4.0.3", - "Repositories": [ - { - "Name": "CRAN", - "URL": "https://cloud.r-project.org" - } - ] - }, - "Packages": { - "renv": { - "Package": "renv", - "Version": "0.12.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "5c0cdb37f063c58cdab3c7e9fbb8bd2c" - }, - "rprojroot": { - "Package": "rprojroot", - "Version": "1.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "86704667fe0860e4fec35afdfec137f3" - } - } -} diff --git a/testing/resources/r_hooks_repo/renv/LICENSE b/testing/resources/r_hooks_repo/renv/LICENSE deleted file mode 100644 index 253c5d1ab..000000000 --- a/testing/resources/r_hooks_repo/renv/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright 2021 RStudio, PBC - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/testing/resources/r_hooks_repo/renv/activate.R b/testing/resources/r_hooks_repo/renv/activate.R deleted file mode 100644 index d8d092cc6..000000000 --- a/testing/resources/r_hooks_repo/renv/activate.R +++ /dev/null @@ -1,440 +0,0 @@ - -local({ - - # the requested version of renv - version <- "0.12.5" - - # the project directory - project <- getwd() - - # avoid recursion - if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) - return(invisible(TRUE)) - - # signal that we're loading renv during R startup - Sys.setenv("RENV_R_INITIALIZING" = "true") - on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) - - # signal that we've consented to use renv - options(renv.consent = TRUE) - - # load the 'utils' package eagerly -- this ensures that renv shims, which - # mask 'utils' packages, will come first on the search path - library(utils, lib.loc = .Library) - - # check to see if renv has already been loaded - if ("renv" %in% loadedNamespaces()) { - - # if renv has already been loaded, and it's the requested version of renv, - # nothing to do - spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") - if (identical(spec[["version"]], version)) - return(invisible(TRUE)) - - # otherwise, unload and attempt to load the correct version of renv - unloadNamespace("renv") - - } - - # load bootstrap tools - bootstrap <- function(version, library) { - - # attempt to download renv - tarball <- tryCatch(renv_bootstrap_download(version), error = identity) - if (inherits(tarball, "error")) - stop("failed to download renv ", version) - - # now attempt to install - status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) - if (inherits(status, "error")) - stop("failed to install renv ", version) - - } - - renv_bootstrap_tests_running <- function() { - getOption("renv.tests.running", default = FALSE) - } - - renv_bootstrap_repos <- function() { - - # check for repos override - repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) - if (!is.na(repos)) - return(repos) - - # if we're testing, re-use the test repositories - if (renv_bootstrap_tests_running()) - return(getOption("renv.tests.repos")) - - # retrieve current repos - repos <- getOption("repos") - - # ensure @CRAN@ entries are resolved - repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" - - # add in renv.bootstrap.repos if set - default <- c(CRAN = "https://cloud.r-project.org") - extra <- getOption("renv.bootstrap.repos", default = default) - repos <- c(repos, extra) - - # remove duplicates that might've snuck in - dupes <- duplicated(repos) | duplicated(names(repos)) - repos[!dupes] - - } - - renv_bootstrap_download <- function(version) { - - # if the renv version number has 4 components, assume it must - # be retrieved via github - nv <- numeric_version(version) - components <- unclass(nv)[[1]] - - methods <- if (length(components) == 4L) { - list( - renv_bootstrap_download_github - ) - } else { - list( - renv_bootstrap_download_cran_latest, - renv_bootstrap_download_cran_archive - ) - } - - for (method in methods) { - path <- tryCatch(method(version), error = identity) - if (is.character(path) && file.exists(path)) - return(path) - } - - stop("failed to download renv ", version) - - } - - renv_bootstrap_download_impl <- function(url, destfile) { - - mode <- "wb" - - # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 - fixup <- - Sys.info()[["sysname"]] == "Windows" && - substring(url, 1L, 5L) == "file:" - - if (fixup) - mode <- "w+b" - - utils::download.file( - url = url, - destfile = destfile, - mode = mode, - quiet = TRUE - ) - - } - - renv_bootstrap_download_cran_latest <- function(version) { - - repos <- renv_bootstrap_download_cran_latest_find(version) - - message("* Downloading renv ", version, " from CRAN ... ", appendLF = FALSE) - - info <- tryCatch( - utils::download.packages( - pkgs = "renv", - repos = repos, - destdir = tempdir(), - quiet = TRUE - ), - condition = identity - ) - - if (inherits(info, "condition")) { - message("FAILED") - return(FALSE) - } - - message("OK") - info[1, 2] - - } - - renv_bootstrap_download_cran_latest_find <- function(version) { - - all <- renv_bootstrap_repos() - - for (repos in all) { - - db <- tryCatch( - as.data.frame( - x = utils::available.packages(repos = repos), - stringsAsFactors = FALSE - ), - error = identity - ) - - if (inherits(db, "error")) - next - - entry <- db[db$Package %in% "renv" & db$Version %in% version, ] - if (nrow(entry) == 0) - next - - return(repos) - - } - - fmt <- "renv %s is not available from your declared package repositories" - stop(sprintf(fmt, version)) - - } - - renv_bootstrap_download_cran_archive <- function(version) { - - name <- sprintf("renv_%s.tar.gz", version) - repos <- renv_bootstrap_repos() - urls <- file.path(repos, "src/contrib/Archive/renv", name) - destfile <- file.path(tempdir(), name) - - message("* Downloading renv ", version, " from CRAN archive ... ", appendLF = FALSE) - - for (url in urls) { - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (identical(status, 0L)) { - message("OK") - return(destfile) - } - - } - - message("FAILED") - return(FALSE) - - } - - renv_bootstrap_download_github <- function(version) { - - enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") - if (!identical(enabled, "TRUE")) - return(FALSE) - - # prepare download options - pat <- Sys.getenv("GITHUB_PAT") - if (nzchar(Sys.which("curl")) && nzchar(pat)) { - fmt <- "--location --fail --header \"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "curl", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { - fmt <- "--header=\"Authorization: token %s\"" - extra <- sprintf(fmt, pat) - saved <- options("download.file.method", "download.file.extra") - options(download.file.method = "wget", download.file.extra = extra) - on.exit(do.call(base::options, saved), add = TRUE) - } - - message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) - - url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) - name <- sprintf("renv_%s.tar.gz", version) - destfile <- file.path(tempdir(), name) - - status <- tryCatch( - renv_bootstrap_download_impl(url, destfile), - condition = identity - ) - - if (!identical(status, 0L)) { - message("FAILED") - return(FALSE) - } - - message("OK") - return(destfile) - - } - - renv_bootstrap_install <- function(version, tarball, library) { - - # attempt to install it into project library - message("* Installing renv ", version, " ... ", appendLF = FALSE) - dir.create(library, showWarnings = FALSE, recursive = TRUE) - - # invoke using system2 so we can capture and report output - bin <- R.home("bin") - exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" - r <- file.path(bin, exe) - args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) - output <- system2(r, args, stdout = TRUE, stderr = TRUE) - message("Done!") - - # check for successful install - status <- attr(output, "status") - if (is.numeric(status) && !identical(status, 0L)) { - header <- "Error installing renv:" - lines <- paste(rep.int("=", nchar(header)), collapse = "") - text <- c(header, lines, output) - writeLines(text, con = stderr()) - } - - status - - } - - renv_bootstrap_prefix <- function() { - - # construct version prefix - version <- paste(R.version$major, R.version$minor, sep = ".") - prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") - - # include SVN revision for development versions of R - # (to avoid sharing platform-specific artefacts with released versions of R) - devel <- - identical(R.version[["status"]], "Under development (unstable)") || - identical(R.version[["nickname"]], "Unsuffered Consequences") - - if (devel) - prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") - - # build list of path components - components <- c(prefix, R.version$platform) - - # include prefix if provided by user - prefix <- Sys.getenv("RENV_PATHS_PREFIX") - if (nzchar(prefix)) - components <- c(prefix, components) - - # build prefix - paste(components, collapse = "/") - - } - - renv_bootstrap_library_root_name <- function(project) { - - # use project name as-is if requested - asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") - if (asis) - return(basename(project)) - - # otherwise, disambiguate based on project's path - id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) - paste(basename(project), id, sep = "-") - - } - - renv_bootstrap_library_root <- function(project) { - - path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) - if (!is.na(path)) - return(path) - - path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) - if (!is.na(path)) { - name <- renv_bootstrap_library_root_name(project) - return(file.path(path, name)) - } - - file.path(project, "renv/library") - - } - - renv_bootstrap_validate_version <- function(version) { - - loadedversion <- utils::packageDescription("renv", fields = "Version") - if (version == loadedversion) - return(TRUE) - - # assume four-component versions are from GitHub; three-component - # versions are from CRAN - components <- strsplit(loadedversion, "[.-]")[[1]] - remote <- if (length(components) == 4L) - paste("rstudio/renv", loadedversion, sep = "@") - else - paste("renv", loadedversion, sep = "@") - - fmt <- paste( - "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", - "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", - "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", - sep = "\n" - ) - - msg <- sprintf(fmt, loadedversion, version, remote) - warning(msg, call. = FALSE) - - FALSE - - } - - renv_bootstrap_hash_text <- function(text) { - - hashfile <- tempfile("renv-hash-") - on.exit(unlink(hashfile), add = TRUE) - - writeLines(text, con = hashfile) - tools::md5sum(hashfile) - - } - - renv_bootstrap_load <- function(project, libpath, version) { - - # try to load renv from the project library - if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) - return(FALSE) - - # warn if the version of renv loaded does not match - renv_bootstrap_validate_version(version) - - # load the project - renv::load(project) - - TRUE - - } - - # construct path to library root - root <- renv_bootstrap_library_root(project) - - # construct library prefix for platform - prefix <- renv_bootstrap_prefix() - - # construct full libpath - libpath <- file.path(root, prefix) - - # attempt to load - if (renv_bootstrap_load(project, libpath, version)) - return(TRUE) - - # load failed; inform user we're about to bootstrap - prefix <- paste("# Bootstrapping renv", version) - postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") - header <- paste(prefix, postfix) - message(header) - - # perform bootstrap - bootstrap(version, libpath) - - # exit early if we're just testing bootstrap - if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) - return(TRUE) - - # try again to load - if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { - message("* Successfully installed and loaded renv ", version, ".") - return(renv::load()) - } - - # failed to download or load renv; warn the user - msg <- c( - "Failed to find an renv installation: the project will not be loaded.", - "Use `renv::activate()` to re-initialize the project." - ) - - warning(paste(msg, collapse = "\n"), call. = FALSE) - -}) diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index 0c5e56382..763fe8e9e 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -1,13 +1,16 @@ from __future__ import annotations import os.path +import shutil import pytest from pre_commit import envcontext from pre_commit.languages import r from pre_commit.prefix import Prefix +from pre_commit.store import _make_local_repo from pre_commit.util import win_exe +from testing.language_helpers import run_language def test_r_parsing_file_no_opts_no_args(tmp_path): @@ -97,3 +100,99 @@ def test_rscript_exec_relative_to_r_home(): def test_path_rscript_exec_no_r_home_set(): with envcontext.envcontext((('R_HOME', envcontext.UNSET),)): assert r._rscript_exec() == 'Rscript' + + +def test_r_hook(tmp_path): + renv_lock = '''\ +{ + "R": { + "Version": "4.0.3", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cloud.r-project.org" + } + ] + }, + "Packages": { + "renv": { + "Package": "renv", + "Version": "0.12.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5c0cdb37f063c58cdab3c7e9fbb8bd2c" + }, + "rprojroot": { + "Package": "rprojroot", + "Version": "1.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "86704667fe0860e4fec35afdfec137f3" + } + } +} +''' + description = '''\ +Package: gli.clu +Title: What the Package Does (One Line, Title Case) +Type: Package +Version: 0.0.0.9000 +Authors@R: + person(given = "First", + family = "Last", + role = c("aut", "cre"), + email = "first.last@example.com", + comment = c(ORCID = "YOUR-ORCID-ID")) +Description: What the package does (one paragraph). +License: `use_mit_license()`, `use_gpl3_license()` or friends to + pick a license +Encoding: UTF-8 +LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.1.1 +Imports: + rprojroot +''' + hello_world_r = '''\ +stopifnot( + packageVersion('rprojroot') == '1.0', + packageVersion('gli.clu') == '0.0.0.9000' +) +cat("Hello, World, from R!\n") +''' + + tmp_path.joinpath('renv.lock').write_text(renv_lock) + tmp_path.joinpath('DESCRIPTION').write_text(description) + tmp_path.joinpath('hello-world.R').write_text(hello_world_r) + renv_dir = tmp_path.joinpath('renv') + renv_dir.mkdir() + shutil.copy( + os.path.join( + os.path.dirname(__file__), + '../../pre_commit/resources/empty_template_activate.R', + ), + renv_dir.joinpath('activate.R'), + ) + + expected = (0, b'Hello, World, from R!\n') + assert run_language(tmp_path, r, 'Rscript hello-world.R') == expected + + +def test_r_inline(tmp_path): + _make_local_repo(str(tmp_path)) + + cmd = '''\ +Rscript -e ' + stopifnot(packageVersion("rprojroot") == "1.0") + cat(commandArgs(trailingOnly = TRUE), "from R!\n", sep=", ") +' +''' + + ret = run_language( + tmp_path, + r, + cmd, + deps=('rprojroot@1.0',), + args=('hi', 'hello'), + ) + assert ret == (0, b'hi, hello, from R!\n') diff --git a/tests/repository_test.py b/tests/repository_test.py index 0d01f0f65..bcb671261 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -227,54 +227,6 @@ def test_node_hook_with_npm_userconfig_set(tempdir_factory, store, tmpdir): test_run_a_node_hook(tempdir_factory, store) -def test_r_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'r_hooks_repo', - 'hello-world', [os.devnull], - b'Hello, World, from R!\n', - ) - - -def test_r_inline_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'r_hooks_repo', - 'hello-world-inline', ['some-file'], - b'Hi-there, some-file, from R!\n', - ) - - -def test_r_with_additional_dependencies_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'r_hooks_repo', - 'additional-deps', [os.devnull], - b'OK\n', - config_kwargs={ - 'hooks': [{ - 'id': 'additional-deps', - 'additional_dependencies': ['cachem@1.0.4'], - }], - }, - ) - - -def test_r_local_with_additional_dependencies_hook(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'local-r', - 'name': 'local-r', - 'entry': 'Rscript -e', - 'language': 'r', - 'args': ['if (packageVersion("R6") == "2.1.3") cat("OK\n")'], - 'additional_dependencies': ['R6@2.1.3'], - }], - } - hook = _get_hook(config, store, 'local-r') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - assert _norm_out(out) == b'OK\n' - - def test_run_a_ruby_hook(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'ruby_hooks_repo', From f042540311b9c23ba56fa12b87211fb495219c81 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 17 Jan 2023 23:43:31 -0500 Subject: [PATCH 105/342] test lua directly --- .../resources/lua_repo/.pre-commit-hooks.yaml | 4 -- .../resources/lua_repo/bin/hello-world-lua | 3 - .../resources/lua_repo/hello-dev-1.rockspec | 15 ----- testing/util.py | 4 -- tests/languages/lua_test.py | 58 +++++++++++++++++++ tests/repository_test.py | 27 --------- 6 files changed, 58 insertions(+), 53 deletions(-) delete mode 100644 testing/resources/lua_repo/.pre-commit-hooks.yaml delete mode 100755 testing/resources/lua_repo/bin/hello-world-lua delete mode 100644 testing/resources/lua_repo/hello-dev-1.rockspec create mode 100644 tests/languages/lua_test.py diff --git a/testing/resources/lua_repo/.pre-commit-hooks.yaml b/testing/resources/lua_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 767ef972c..000000000 --- a/testing/resources/lua_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,4 +0,0 @@ -- id: hello-world-lua - name: hello world lua - entry: hello-world-lua - language: lua diff --git a/testing/resources/lua_repo/bin/hello-world-lua b/testing/resources/lua_repo/bin/hello-world-lua deleted file mode 100755 index 2a0e00246..000000000 --- a/testing/resources/lua_repo/bin/hello-world-lua +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env lua - -print('hello world') diff --git a/testing/resources/lua_repo/hello-dev-1.rockspec b/testing/resources/lua_repo/hello-dev-1.rockspec deleted file mode 100644 index 82486e08a..000000000 --- a/testing/resources/lua_repo/hello-dev-1.rockspec +++ /dev/null @@ -1,15 +0,0 @@ -package = "hello" -version = "dev-1" - -source = { - url = "git+ssh://git@github.com/pre-commit/pre-commit.git" -} -description = {} -dependencies = {} -build = { - type = "builtin", - modules = {}, - install = { - bin = {"bin/hello-world-lua"} - }, -} diff --git a/testing/util.py b/testing/util.py index a5ae06d05..b6c3804e6 100644 --- a/testing/util.py +++ b/testing/util.py @@ -45,10 +45,6 @@ def cmd_output_mocked_pre_commit_home( os.name == 'nt' or not docker_is_running(), reason="Docker isn't running or can't be accessed", ) -skipif_cant_run_lua = pytest.mark.skipif( - os.name == 'nt', - reason="lua isn't installed or can't be found", -) xfailif_windows = pytest.mark.xfail(os.name == 'nt', reason='windows') diff --git a/tests/languages/lua_test.py b/tests/languages/lua_test.py new file mode 100644 index 000000000..b2767b727 --- /dev/null +++ b/tests/languages/lua_test.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import sys + +import pytest + +from pre_commit.languages import lua +from pre_commit.util import make_executable +from testing.language_helpers import run_language + +pytestmark = pytest.mark.skipif( + sys.platform == 'win32', + reason='lua is not supported on windows', +) + + +def test_lua(tmp_path): # pragma: win32 no cover + rockspec = '''\ +package = "hello" +version = "dev-1" + +source = { + url = "git+ssh://git@github.com/pre-commit/pre-commit.git" +} +description = {} +dependencies = {} +build = { + type = "builtin", + modules = {}, + install = { + bin = {"bin/hello-world-lua"} + }, +} +''' + hello_world_lua = '''\ +#!/usr/bin/env lua +print('hello world') +''' + tmp_path.joinpath('hello-dev-1.rockspec').write_text(rockspec) + bin_dir = tmp_path.joinpath('bin') + bin_dir.mkdir() + bin_file = bin_dir.joinpath('hello-world-lua') + bin_file.write_text(hello_world_lua) + make_executable(bin_file) + + expected = (0, b'hello world\n') + assert run_language(tmp_path, lua, 'hello-world-lua') == expected + + +def test_lua_additional_dependencies(tmp_path): # pragma: win32 no cover + ret, out = run_language( + tmp_path, + lua, + 'luacheck --version', + deps=('luacheck',), + ) + assert ret == 0 + assert out.startswith(b'Luacheck: ') diff --git a/tests/repository_test.py b/tests/repository_test.py index 0d01f0f65..a617da1df 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -33,7 +33,6 @@ from testing.util import cwd from testing.util import get_resource_path from testing.util import skipif_cant_run_docker -from testing.util import skipif_cant_run_lua from testing.util import xfailif_windows @@ -1041,29 +1040,3 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog): 'using language `system` which does not install an environment. ' 'Perhaps you meant to use a specific language?' ) - - -@skipif_cant_run_lua # pragma: win32 no cover -def test_lua_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'lua_repo', - 'hello-world-lua', [], b'hello world\n', - ) - - -@skipif_cant_run_lua # pragma: win32 no cover -def test_local_lua_additional_dependencies(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'local-lua', - 'name': 'local-lua', - 'entry': 'luacheck --version', - 'language': 'lua', - 'additional_dependencies': ['luacheck'], - }], - } - hook = _get_hook(config, store, 'local-lua') - ret, out = _hook_run(hook, (), color=False) - assert b'Luacheck' in out - assert ret == 0 From 14c38d18fcc608644db077f7c862f9892a981668 Mon Sep 17 00:00:00 2001 From: Jamie Alessio Date: Sat, 21 Jan 2023 11:05:13 -0800 Subject: [PATCH 106/342] Upgrade to ruby-build v20221225 --- pre_commit/resources/ruby-build.tar.gz | Bin 74032 -> 76466 bytes testing/make-archives | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz index 35419f63aebe33b6710851aef70937cd9ef163ba..b6eacf59ba31c87032e76c2d1f39a87bb2b52489 100644 GIT binary patch literal 76466 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

Q#vRC5;yit2Sd`xrHX$e_C?P2bQUVfE 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

@UO6IsN-Ld{vdH%#= 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

FB_o9 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%0Da>5o|PaFgyRW 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 Date: Mon, 23 Jan 2023 19:24:10 -0500 Subject: [PATCH 107/342] introduce install state v2 to replace v1 the v1 state is unnecessary since new repos are created for new additional_dependencies --- pre_commit/constants.py | 2 -- pre_commit/repository.py | 25 ++++++++++++++++++------- tests/repository_test.py | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 8fc5e55db..3f03ceed9 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -5,8 +5,6 @@ CONFIG_FILE = '.pre-commit-config.yaml' MANIFEST_FILE = '.pre-commit-hooks.yaml' -# Bump when installation changes in a backwards / forwards incompatible way -INSTALLED_STATE_VERSION = '1' # Bump when modifying `empty_template` LOCAL_REPO_VERSION = '1' diff --git a/pre_commit/repository.py b/pre_commit/repository.py index ac6b84463..616faf54c 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -23,16 +23,20 @@ logger = logging.getLogger('pre_commit') -def _state(additional_deps: Sequence[str]) -> object: - return {'additional_dependencies': sorted(additional_deps)} +def _state_filename_v1(venv: str) -> str: + return os.path.join(venv, '.install_state_v1') + + +def _state_filename_v2(venv: str) -> str: + return os.path.join(venv, '.install_state_v2') -def _state_filename(venv: str) -> str: - return os.path.join(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') +def _state(additional_deps: Sequence[str]) -> object: + return {'additional_dependencies': sorted(additional_deps)} def _read_state(venv: str) -> object | None: - filename = _state_filename(venv) + filename = _state_filename_v1(venv) if not os.path.exists(filename): return None else: @@ -51,7 +55,10 @@ def _hook_installed(hook: Hook) -> bool: hook.language_version, ) return ( - _read_state(venv) == _state(hook.additional_dependencies) and + ( + os.path.exists(_state_filename_v2(venv)) or + _read_state(venv) == _state(hook.additional_dependencies) + ) and not lang.health_check(hook.prefix, hook.language_version) ) @@ -87,14 +94,18 @@ def _hook_install(hook: Hook) -> None: f'your environment\n\n' f'more info:\n\n{health_error}', ) + + # TODO: remove v1 state writing, no longer needed after pre-commit 3.0 # Write our state to indicate we're installed - state_filename = _state_filename(venv) + state_filename = _state_filename_v1(venv) staging = f'{state_filename}staging' with open(staging, 'w') as state_file: state_file.write(json.dumps(_state(hook.additional_dependencies))) # Move the file into place atomically to indicate we've installed os.replace(staging, state_filename) + open(_state_filename_v2(venv), 'a+').close() + def _hook( *hook_dicts: dict[str, Any], diff --git a/tests/repository_test.py b/tests/repository_test.py index 8d3034bb9..da8785963 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -23,6 +23,7 @@ from pre_commit.languages import rust from pre_commit.languages.all import languages from pre_commit.prefix import Prefix +from pre_commit.repository import _hook_installed from pre_commit.repository import all_hooks from pre_commit.repository import install_hook_envs from pre_commit.util import cmd_output @@ -562,6 +563,21 @@ def test_additional_dependencies_roll_forward(tempdir_factory, store): assert 'mccabe' not in cmd_output('pip', 'freeze', '-l')[1] +@pytest.mark.parametrize('v', ('v1', 'v2')) +def test_repository_state_compatibility(tempdir_factory, store, v): + path = make_repo(tempdir_factory, 'python_hooks_repo') + + config = make_config_from_repo(path) + hook = _get_hook(config, store, 'foo') + envdir = helpers.environment_dir( + hook.prefix, + python.ENVIRONMENT_DIR, + hook.language_version, + ) + os.remove(os.path.join(envdir, f'.install_state_{v}')) + assert _hook_installed(hook) is True + + def test_additional_ruby_dependencies_installed(tempdir_factory, store): path = make_repo(tempdir_factory, 'ruby_hooks_repo') config = make_config_from_repo(path) From 6b88fe577c44472d234e8d4d8ee89ca36e03ae2a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 23 Jan 2023 20:40:13 -0500 Subject: [PATCH 108/342] v3.0.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd0de5f73..59e0e202b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +3.0.0 - 2023-01-23 +================== + +### Features +- Make `language: golang` bootstrap `go` if not present. + - #2651 PR by @taoufik07. + - #2649 issue by @taoufik07. +- `language: coursier` now supports `additional_dependencies` and `repo: local` + - #2702 PR by @asottile. +- Upgrade `ruby-build` to `20221225`. + - #2718 PR by @jalessio. + +### Fixes +- Improve error message for invalid yaml for `pre-commit autoupdate`. + - #2686 PR by @asottile. + - #2685 issue by @CarstenGrohmann. +- `repo: local` no longer provisions an empty `git` repo. + - #2699 PR by @asottile. + +### Updating +- Drop support for python<3.8 + - #2655 PR by @asottile. +- Drop support for top-level list, use `pre-commit migrate-config` to update. + - #2656 PR by @asottile. +- Drop support for `sha` to specify revision, use `pre-commit migrate-config` + to update. + - #2657 PR by @asottile. +- Remove `pre-commit-validate-config` and `pre-commit-validate-manifest`, use + `pre-commit validate-config` and `pre-commit validate-manifest` instead. + - #2658 PR by @asottile. +- `language: golang` hooks must use `go.mod` to specify dependencies + - #2672 PR by @taoufik07. + + 2.21.0 - 2022-12-25 =================== diff --git a/setup.cfg b/setup.cfg index ca1f7d8bd..929f4c327 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 2.21.0 +version = 3.0.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 83e05e607e6b8cfde97c05e067d156be09a298a9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 25 Jan 2023 14:03:39 -0500 Subject: [PATCH 109/342] ensure coursier hooks are available offline after install --- pre_commit/languages/coursier.py | 45 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 69c877d32..60757588d 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -28,45 +28,44 @@ def install_environment( helpers.assert_version_default('coursier', version) # Support both possible executable names (either "cs" or "coursier") - executable = find_executable('cs') or find_executable('coursier') - if executable is None: + cs = find_executable('cs') or find_executable('coursier') + if cs is None: raise AssertionError( 'pre-commit requires system-installed "cs" or "coursier" ' 'executables in the application search path', ) envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) - channel = prefix.path('.pre-commit-channel') - if os.path.isdir(channel): - for app_descriptor in os.listdir(channel): - _, app_file = os.path.split(app_descriptor) - app, _ = os.path.splitext(app_file) - helpers.run_setup_cmd( - prefix, - ( - executable, - 'install', + + def _install(*opts: str) -> None: + assert cs is not None + helpers.run_setup_cmd(prefix, (cs, 'fetch', *opts)) + helpers.run_setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts)) + + with in_env(prefix, version): + channel = prefix.path('.pre-commit-channel') + if os.path.isdir(channel): + for app_descriptor in os.listdir(channel): + _, app_file = os.path.split(app_descriptor) + app, _ = os.path.splitext(app_file) + _install( '--default-channels=false', '--channel', channel, - '--dir', envdir, app, - ), + ) + elif not additional_dependencies: + raise FatalError( + 'expected .pre-commit-channel dir or additional_dependencies', ) - elif not additional_dependencies: - raise FatalError( - 'expected .pre-commit-channel dir or additional_dependencies', - ) - if additional_dependencies: - install_cmd = ( - executable, 'install', '--dir', envdir, *additional_dependencies, - ) - helpers.run_setup_cmd(prefix, install_cmd) + if additional_dependencies: + _install(*additional_dependencies) def get_env_patch(target_dir: str) -> PatchesT: return ( ('PATH', (target_dir, os.pathsep, Var('PATH'))), + ('COURSIER_CACHE', os.path.join(target_dir, '.cs-cache')), ) From dd8e717ed6022209a2b0cecf5c75460eb60e548e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 26 Jan 2023 11:09:17 -0500 Subject: [PATCH 110/342] v3.0.1 --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e0e202b..d55ff7325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +3.0.1 - 2023-01-26 +================== + +### Fixes +- Ensure coursier hooks are available offline after install. + - #2723 PR by @asottile. + 3.0.0 - 2023-01-23 ================== diff --git a/setup.cfg b/setup.cfg index 929f4c327..1dbace59c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.0.0 +version = 3.0.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From f4bd44996c888f48bc3a37d5ab19514325cb3f01 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Jan 2023 16:44:44 -0500 Subject: [PATCH 111/342] also ignore Gemfile in project this starts failing with ruby 3.2.0 --- pre_commit/languages/ruby.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 4416f7280..b4d4b45af 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -39,6 +39,7 @@ def get_env_patch( ('GEM_HOME', os.path.join(venv, 'gems')), ('GEM_PATH', UNSET), ('BUNDLE_IGNORE_CONFIG', '1'), + ('BUNDLE_GEMFILE', os.devnull), ) if language_version == 'system': patches += ( From 6e8051b9e644505f2755ed576cb4b7220f6db8b4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Jan 2023 16:20:50 -0500 Subject: [PATCH 112/342] speed up ruby tests by picking a prebuilt in 22.04 --- .../ruby_versioned_hooks_repo/.pre-commit-hooks.yaml | 2 +- tests/languages/ruby_test.py | 4 ++-- tests/repository_test.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml index 364d47d8f..c97939ad9 100644 --- a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml +++ b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml @@ -2,5 +2,5 @@ name: Ruby Hook entry: ruby_hook language: ruby - language_version: 3.1.0 + language_version: 3.2.0 files: \.rb$ diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index 29f3c802e..63a16eb11 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -71,10 +71,10 @@ def test_install_ruby_default(fake_gem_prefix): @xfailif_windows # pragma: win32 no cover def test_install_ruby_with_version(fake_gem_prefix): - ruby.install_environment(fake_gem_prefix, '3.1.0', ()) + ruby.install_environment(fake_gem_prefix, '3.2.0', ()) # Should be able to activate and use rbenv install - with ruby.in_env(fake_gem_prefix, '3.1.0'): + with ruby.in_env(fake_gem_prefix, '3.2.0'): cmd_output('rbenv', 'install', '--help') diff --git a/tests/repository_test.py b/tests/repository_test.py index da8785963..ff2d7c323 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -247,7 +247,7 @@ def test_run_versioned_ruby_hook(tempdir_factory, store): tempdir_factory, store, 'ruby_versioned_hooks_repo', 'ruby_hook', [os.devnull], - b'3.1.0\nHello world from a ruby hook\n', + b'3.2.0\nHello world from a ruby hook\n', ) @@ -269,7 +269,7 @@ def test_run_ruby_hook_with_disable_shared_gems( tempdir_factory, store, 'ruby_versioned_hooks_repo', 'ruby_hook', [os.devnull], - b'3.1.0\nHello world from a ruby hook\n', + b'3.2.0\nHello world from a ruby hook\n', ) From 420902f67cbd2117e93f797191eaa9dab4be6904 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 29 Jan 2023 17:27:42 -0500 Subject: [PATCH 113/342] fix r local hooks `language: r` acts more like `language: script` so we have to *not* append the prefix when run with `repo: local` --- pre_commit/commands/run.py | 1 + pre_commit/languages/all.py | 1 + pre_commit/languages/docker.py | 1 + pre_commit/languages/docker_image.py | 1 + pre_commit/languages/fail.py | 1 + pre_commit/languages/helpers.py | 1 + pre_commit/languages/pygrep.py | 1 + pre_commit/languages/r.py | 17 ++++++++++++---- pre_commit/languages/script.py | 1 + testing/language_helpers.py | 2 ++ tests/languages/r_test.py | 29 ++++++++++++++++++++++++++-- tests/repository_test.py | 1 + 12 files changed, 51 insertions(+), 6 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 85fa59aa1..e44e70364 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -195,6 +195,7 @@ def _run_single_hook( hook.entry, hook.args, filenames, + is_local=hook.src == 'local', require_serial=hook.require_serial, color=use_color, ) diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index c7aab65e7..d952ae1ab 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -66,6 +66,7 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 18234567b..e80c95978 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -127,6 +127,7 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index 230983823..8e5f2c04c 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -19,6 +19,7 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index 13b2bc12c..33df067e4 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -18,6 +18,7 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index 074f98e9f..d1be409c8 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -146,6 +146,7 @@ def basic_run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index 93e2a65bd..f0eb9a959 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -93,6 +93,7 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index dc3986057..e2383658a 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -35,8 +35,13 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: yield -def _prefix_if_file_entry(entry: list[str], prefix: Prefix) -> Sequence[str]: - if entry[1] == '-e': +def _prefix_if_file_entry( + entry: list[str], + prefix: Prefix, + *, + is_local: bool, +) -> Sequence[str]: + if entry[1] == '-e' or is_local: return entry[1:] else: return (prefix.path(entry[1]),) @@ -73,11 +78,14 @@ def _cmd_from_hook( prefix: Prefix, entry: str, args: Sequence[str], + *, + is_local: bool, ) -> tuple[str, ...]: cmd = shlex.split(entry) _entry_validate(cmd) - return (cmd[0], *RSCRIPT_OPTS, *_prefix_if_file_entry(cmd, prefix), *args) + cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local) + return (cmd[0], *RSCRIPT_OPTS, *cmd_part, *args) def install_environment( @@ -153,10 +161,11 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: - cmd = _cmd_from_hook(prefix, entry, args) + cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local) return helpers.run_xargs( cmd, file_args, diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 41fffdf07..08325f469 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -18,6 +18,7 @@ def run_hook( args: Sequence[str], file_args: Sequence[str], *, + is_local: bool, require_serial: bool, color: bool, ) -> tuple[int, bytes]: diff --git a/testing/language_helpers.py b/testing/language_helpers.py index 02e47a002..f9ae0b1da 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -16,6 +16,7 @@ def run_language( file_args: Sequence[str] = (), version: str = C.DEFAULT, deps: Sequence[str] = (), + is_local: bool = False, ) -> tuple[int, bytes]: prefix = Prefix(str(path)) @@ -26,6 +27,7 @@ def run_language( exe, args, file_args, + is_local=is_local, require_serial=True, color=False, ) diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index 763fe8e9e..02c559cb4 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -14,7 +14,12 @@ def test_r_parsing_file_no_opts_no_args(tmp_path): - cmd = r._cmd_from_hook(Prefix(str(tmp_path)), 'Rscript some-script.R', ()) + cmd = r._cmd_from_hook( + Prefix(str(tmp_path)), + 'Rscript some-script.R', + (), + is_local=False, + ) assert cmd == ( 'Rscript', '--no-save', '--no-restore', '--no-site-file', '--no-environ', @@ -38,6 +43,7 @@ def test_r_parsing_file_no_opts_args(tmp_path): Prefix(str(tmp_path)), 'Rscript some-script.R', ('--no-cache',), + is_local=False, ) assert cmd == ( 'Rscript', @@ -48,7 +54,12 @@ def test_r_parsing_file_no_opts_args(tmp_path): def test_r_parsing_expr_no_opts_no_args1(tmp_path): - cmd = r._cmd_from_hook(Prefix(str(tmp_path)), "Rscript -e '1+1'", ()) + cmd = r._cmd_from_hook( + Prefix(str(tmp_path)), + "Rscript -e '1+1'", + (), + is_local=False, + ) assert cmd == ( 'Rscript', '--no-save', '--no-restore', '--no-site-file', '--no-environ', @@ -56,6 +67,20 @@ def test_r_parsing_expr_no_opts_no_args1(tmp_path): ) +def test_r_parsing_local_hook_path_is_not_expanded(tmp_path): + cmd = r._cmd_from_hook( + Prefix(str(tmp_path)), + 'Rscript path/to/thing.R', + (), + is_local=True, + ) + assert cmd == ( + 'Rscript', + '--no-save', '--no-restore', '--no-site-file', '--no-environ', + 'path/to/thing.R', + ) + + def test_r_parsing_expr_no_opts_no_args2(): with pytest.raises(ValueError) as excinfo: r._entry_validate(['Rscript', '-e', '1+1', '-e', 'letters']) diff --git a/tests/repository_test.py b/tests/repository_test.py index ff2d7c323..85cf45812 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -48,6 +48,7 @@ def _hook_run(hook, filenames, color): hook.entry, hook.args, filenames, + is_local=hook.src == 'local', require_serial=hook.require_serial, color=color, ) From 2adca78c6feb99d0e9b14158fa38e599ec7e84a6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 29 Jan 2023 18:27:10 -0500 Subject: [PATCH 114/342] test rust directly --- testing/language_helpers.py | 4 +- .../rust_hooks_repo/.pre-commit-hooks.yaml | 5 - testing/resources/rust_hooks_repo/Cargo.lock | 3 - testing/resources/rust_hooks_repo/Cargo.toml | 3 - testing/resources/rust_hooks_repo/src/main.rs | 3 - tests/languages/rust_test.py | 101 ++++++++++-------- tests/repository_test.py | 66 ------------ 7 files changed, 59 insertions(+), 126 deletions(-) delete mode 100644 testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/rust_hooks_repo/Cargo.lock delete mode 100644 testing/resources/rust_hooks_repo/Cargo.toml delete mode 100644 testing/resources/rust_hooks_repo/src/main.rs diff --git a/testing/language_helpers.py b/testing/language_helpers.py index 02e47a002..45fefbabd 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -3,7 +3,6 @@ import os from typing import Sequence -import pre_commit.constants as C from pre_commit.languages.all import Language from pre_commit.prefix import Prefix @@ -14,10 +13,11 @@ def run_language( exe: str, args: Sequence[str] = (), file_args: Sequence[str] = (), - version: str = C.DEFAULT, + version: str | None = None, deps: Sequence[str] = (), ) -> tuple[int, bytes]: prefix = Prefix(str(path)) + version = version or language.get_default_version() language.install_environment(prefix, version, deps) with language.in_env(prefix, version): diff --git a/testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index df1269ff8..000000000 --- a/testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: rust-hook - name: rust example hook - entry: rust-hello-world - language: rust - files: '' diff --git a/testing/resources/rust_hooks_repo/Cargo.lock b/testing/resources/rust_hooks_repo/Cargo.lock deleted file mode 100644 index 36fbfda2b..000000000 --- a/testing/resources/rust_hooks_repo/Cargo.lock +++ /dev/null @@ -1,3 +0,0 @@ -[[package]] -name = "rust-hello-world" -version = "0.1.0" diff --git a/testing/resources/rust_hooks_repo/Cargo.toml b/testing/resources/rust_hooks_repo/Cargo.toml deleted file mode 100644 index cd83b4358..000000000 --- a/testing/resources/rust_hooks_repo/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[package] -name = "rust-hello-world" -version = "0.1.0" diff --git a/testing/resources/rust_hooks_repo/src/main.rs b/testing/resources/rust_hooks_repo/src/main.rs deleted file mode 100644 index ad379d6ea..000000000 --- a/testing/resources/rust_hooks_repo/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("hello world"); -} diff --git a/tests/languages/rust_test.py b/tests/languages/rust_test.py index b8167a9e3..5c17f5b69 100644 --- a/tests/languages/rust_test.py +++ b/tests/languages/rust_test.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import Mapping from unittest import mock import pytest @@ -8,8 +7,8 @@ import pre_commit.constants as C from pre_commit import parse_shebang from pre_commit.languages import rust -from pre_commit.prefix import Prefix -from pre_commit.util import cmd_output +from pre_commit.store import _make_local_repo +from testing.language_helpers import run_language ACTUAL_GET_DEFAULT_VERSION = rust.get_default_version.__wrapped__ @@ -30,64 +29,78 @@ def test_uses_default_when_rust_is_not_available(cmd_output_b_mck): assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT -@pytest.mark.parametrize('language_version', (C.DEFAULT, '1.56.0')) -def test_installs_with_bootstrapped_rustup(tmpdir, language_version): - tmpdir.join('src', 'main.rs').ensure().write( +def _make_hello_world(tmp_path): + src_dir = tmp_path.joinpath('src') + src_dir.mkdir() + src_dir.joinpath('main.rs').write_text( 'fn main() {\n' ' println!("Hello, world!");\n' '}\n', ) - tmpdir.join('Cargo.toml').ensure().write( + tmp_path.joinpath('Cargo.toml').write_text( '[package]\n' 'name = "hello_world"\n' 'version = "0.1.0"\n' 'edition = "2021"\n', ) - prefix = Prefix(str(tmpdir)) - find_executable_exes = [] - original_find_executable = parse_shebang.find_executable +def test_installs_rust_missing_rustup(tmp_path): + _make_hello_world(tmp_path) - def mocked_find_executable( - exe: str, *, env: Mapping[str, str] | None = None, - ) -> str | None: - """ - Return `None` the first time `find_executable` is called to ensure - that the bootstrapping code is executed, then just let the function - work as normal. + # pretend like `rustup` doesn't exist so it gets bootstrapped + calls = [] + orig = parse_shebang.find_executable - Also log the arguments to ensure that everything works as expected. - """ - find_executable_exes.append(exe) - if len(find_executable_exes) == 1: + def mck(exe, env=None): + calls.append(exe) + if len(calls) == 1: + assert exe == 'rustup' return None - return original_find_executable(exe, env=env) + return orig(exe, env=env) - with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck: - find_exe_mck.side_effect = mocked_find_executable - rust.install_environment(prefix, language_version, ()) - assert find_executable_exes == ['rustup', 'rustup', 'cargo'] + with mock.patch.object(parse_shebang, 'find_executable', side_effect=mck): + ret = run_language(tmp_path, rust, 'hello_world', version='1.56.0') + assert calls == ['rustup', 'rustup', 'cargo', 'hello_world'] + assert ret == (0, b'Hello, world!\n') - with rust.in_env(prefix, language_version): - assert cmd_output('hello_world')[1] == 'Hello, world!\n' +@pytest.mark.parametrize('version', (C.DEFAULT, '1.56.0')) +def test_language_version_with_rustup(tmp_path, version): + assert parse_shebang.find_executable('rustup') is not None -def test_installs_with_existing_rustup(tmpdir): - tmpdir.join('src', 'main.rs').ensure().write( - 'fn main() {\n' - ' println!("Hello, world!");\n' - '}\n', - ) - tmpdir.join('Cargo.toml').ensure().write( - '[package]\n' - 'name = "hello_world"\n' - 'version = "0.1.0"\n' - 'edition = "2021"\n', + _make_hello_world(tmp_path) + + ret = run_language(tmp_path, rust, 'hello_world', version=version) + assert ret == (0, b'Hello, world!\n') + + +@pytest.mark.parametrize('dep', ('cli:shellharden:4.2.0', 'cli:shellharden')) +def test_rust_cli_additional_dependencies(tmp_path, dep): + _make_local_repo(str(tmp_path)) + + t_sh = tmp_path.joinpath('t.sh') + t_sh.write_text('echo $hi\n') + + assert rust.get_default_version() == 'system' + ret = run_language( + tmp_path, + rust, + 'shellharden --transform', + deps=(dep,), + args=(str(t_sh),), ) - prefix = Prefix(str(tmpdir)) + assert ret == (0, b'echo "$hi"\n') - assert parse_shebang.find_executable('rustup') is not None - rust.install_environment(prefix, '1.56.0', ()) - with rust.in_env(prefix, '1.56.0'): - assert cmd_output('hello_world')[1] == 'Hello, world!\n' + +def test_run_lib_additional_dependencies(tmp_path): + _make_hello_world(tmp_path) + + deps = ('shellharden:4.2.0', 'git-version') + ret = run_language(tmp_path, rust, 'hello_world', deps=deps) + assert ret == (0, b'Hello, world!\n') + + bin_dir = tmp_path.joinpath('rustenv-system', 'bin') + assert bin_dir.is_dir() + assert not bin_dir.joinpath('shellharden').exists() + assert not bin_dir.joinpath('shellharden.exe').exists() diff --git a/tests/repository_test.py b/tests/repository_test.py index ff2d7c323..aea7ffbc7 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -20,7 +20,6 @@ from pre_commit.languages import node from pre_commit.languages import python from pre_commit.languages import ruby -from pre_commit.languages import rust from pre_commit.languages.all import languages from pre_commit.prefix import Prefix from pre_commit.repository import _hook_installed @@ -366,54 +365,6 @@ def test_golang_with_recursive_submodule(tmpdir, tempdir_factory, store): assert _norm_out(out) == b'hello hello world\n' -def test_rust_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'rust_hooks_repo', - 'rust-hook', [], b'hello world\n', - ) - - -@pytest.mark.parametrize('dep', ('cli:shellharden:3.1.0', 'cli:shellharden')) -def test_additional_rust_cli_dependencies_installed( - tempdir_factory, store, dep, -): - path = make_repo(tempdir_factory, 'rust_hooks_repo') - config = make_config_from_repo(path) - # A small rust package with no dependencies. - config['hooks'][0]['additional_dependencies'] = [dep] - hook = _get_hook(config, store, 'rust-hook') - envdir = helpers.environment_dir( - hook.prefix, - rust.ENVIRONMENT_DIR, - 'system', - ) - binaries = os.listdir(os.path.join(envdir, 'bin')) - # normalize for windows - binaries = [os.path.splitext(binary)[0] for binary in binaries] - assert 'shellharden' in binaries - - -def test_additional_rust_lib_dependencies_installed( - tempdir_factory, store, -): - path = make_repo(tempdir_factory, 'rust_hooks_repo') - config = make_config_from_repo(path) - # A small rust package with no dependencies. - deps = ['shellharden:3.1.0', 'git-version'] - config['hooks'][0]['additional_dependencies'] = deps - hook = _get_hook(config, store, 'rust-hook') - envdir = helpers.environment_dir( - hook.prefix, - rust.ENVIRONMENT_DIR, - 'system', - ) - binaries = os.listdir(os.path.join(envdir, 'bin')) - # normalize for windows - binaries = [os.path.splitext(binary)[0] for binary in binaries] - assert 'rust-hello-world' in binaries - assert 'shellharden' not in binaries - - def test_missing_executable(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'not_found_exe', @@ -636,23 +587,6 @@ def test_local_golang_additional_dependencies(store): assert _norm_out(out) == b'Hello, Go examples!\n' -def test_local_rust_additional_dependencies(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'hello', - 'name': 'hello', - 'entry': 'hello', - 'language': 'rust', - 'additional_dependencies': ['cli:hello-cli:0.2.2'], - }], - } - hook = _get_hook(config, store, 'hello') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - assert _norm_out(out) == b'Hello World!\n' - - def test_fail_hooks(store): config = { 'repo': 'local', From 6abb05a60c4087a10c6ce196cd3a8bce065fa6f1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 29 Jan 2023 18:36:45 -0500 Subject: [PATCH 115/342] v3.0.2 --- CHANGELOG.md | 10 ++++++++++ setup.cfg | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55ff7325..c0657e630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +3.0.2 - 2023-01-29 +================== + +### Fixes +- Prevent local `Gemfile` from interfering with hook execution. + - #2727 PR by @asottile. +- Fix `language: r`, `repo: local` hooks + - pre-commit-ci/issues#107 by @lorenzwalthert. + - #2728 PR by @asottile. + 3.0.1 - 2023-01-26 ================== diff --git a/setup.cfg b/setup.cfg index 1dbace59c..37511c09e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.0.1 +version = 3.0.2 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 5b50acbd2c3f52f0e8dee3f11e08905430c4aef7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Jan 2023 21:36:13 -0500 Subject: [PATCH 116/342] test ruby directly --- testing/resources/ruby_hooks_repo/.gitignore | 1 - .../ruby_hooks_repo/.pre-commit-hooks.yaml | 5 - .../resources/ruby_hooks_repo/bin/ruby_hook | 3 - .../resources/ruby_hooks_repo/lib/.gitignore | 0 .../ruby_hooks_repo/ruby_hook.gemspec | 9 -- .../ruby_versioned_hooks_repo/.gitignore | 1 - .../.pre-commit-hooks.yaml | 6 - .../ruby_versioned_hooks_repo/bin/ruby_hook | 4 - .../ruby_versioned_hooks_repo/lib/.gitignore | 0 .../ruby_hook.gemspec | 9 -- tests/languages/ruby_test.py | 125 ++++++++++++------ tests/repository_test.py | 58 -------- 12 files changed, 87 insertions(+), 134 deletions(-) delete mode 100644 testing/resources/ruby_hooks_repo/.gitignore delete mode 100644 testing/resources/ruby_hooks_repo/.pre-commit-hooks.yaml delete mode 100755 testing/resources/ruby_hooks_repo/bin/ruby_hook delete mode 100644 testing/resources/ruby_hooks_repo/lib/.gitignore delete mode 100644 testing/resources/ruby_hooks_repo/ruby_hook.gemspec delete mode 100644 testing/resources/ruby_versioned_hooks_repo/.gitignore delete mode 100644 testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml delete mode 100755 testing/resources/ruby_versioned_hooks_repo/bin/ruby_hook delete mode 100644 testing/resources/ruby_versioned_hooks_repo/lib/.gitignore delete mode 100644 testing/resources/ruby_versioned_hooks_repo/ruby_hook.gemspec diff --git a/testing/resources/ruby_hooks_repo/.gitignore b/testing/resources/ruby_hooks_repo/.gitignore deleted file mode 100644 index c111b3313..000000000 --- a/testing/resources/ruby_hooks_repo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.gem diff --git a/testing/resources/ruby_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/ruby_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index aa15872fb..000000000 --- a/testing/resources/ruby_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: ruby_hook - name: Ruby Hook - entry: ruby_hook - language: ruby - files: \.rb$ diff --git a/testing/resources/ruby_hooks_repo/bin/ruby_hook b/testing/resources/ruby_hooks_repo/bin/ruby_hook deleted file mode 100755 index 5a7e5ed25..000000000 --- a/testing/resources/ruby_hooks_repo/bin/ruby_hook +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby - -puts 'Hello world from a ruby hook' diff --git a/testing/resources/ruby_hooks_repo/lib/.gitignore b/testing/resources/ruby_hooks_repo/lib/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/testing/resources/ruby_hooks_repo/ruby_hook.gemspec b/testing/resources/ruby_hooks_repo/ruby_hook.gemspec deleted file mode 100644 index 75f4e8f7d..000000000 --- a/testing/resources/ruby_hooks_repo/ruby_hook.gemspec +++ /dev/null @@ -1,9 +0,0 @@ -Gem::Specification.new do |s| - s.name = 'ruby_hook' - s.version = '0.1.0' - s.authors = ['Anthony Sottile'] - s.summary = 'A ruby hook!' - s.description = 'A ruby hook!' - s.files = ['bin/ruby_hook'] - s.executables = ['ruby_hook'] -end diff --git a/testing/resources/ruby_versioned_hooks_repo/.gitignore b/testing/resources/ruby_versioned_hooks_repo/.gitignore deleted file mode 100644 index c111b3313..000000000 --- a/testing/resources/ruby_versioned_hooks_repo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.gem diff --git a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index c97939ad9..000000000 --- a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,6 +0,0 @@ -- id: ruby_hook - name: Ruby Hook - entry: ruby_hook - language: ruby - language_version: 3.2.0 - files: \.rb$ diff --git a/testing/resources/ruby_versioned_hooks_repo/bin/ruby_hook b/testing/resources/ruby_versioned_hooks_repo/bin/ruby_hook deleted file mode 100755 index 2406f04cf..000000000 --- a/testing/resources/ruby_versioned_hooks_repo/bin/ruby_hook +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby - -puts RUBY_VERSION -puts 'Hello world from a ruby hook' diff --git a/testing/resources/ruby_versioned_hooks_repo/lib/.gitignore b/testing/resources/ruby_versioned_hooks_repo/lib/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/testing/resources/ruby_versioned_hooks_repo/ruby_hook.gemspec b/testing/resources/ruby_versioned_hooks_repo/ruby_hook.gemspec deleted file mode 100644 index 75f4e8f7d..000000000 --- a/testing/resources/ruby_versioned_hooks_repo/ruby_hook.gemspec +++ /dev/null @@ -1,9 +0,0 @@ -Gem::Specification.new do |s| - s.name = 'ruby_hook' - s.version = '0.1.0' - s.authors = ['Anthony Sottile'] - s.summary = 'A ruby hook!' - s.description = 'A ruby hook!' - s.files = ['bin/ruby_hook'] - s.executables = ['ruby_hook'] -end diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index 63a16eb11..b312c7fda 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os.path import tarfile from unittest import mock @@ -8,10 +7,12 @@ import pre_commit.constants as C from pre_commit import parse_shebang +from pre_commit.envcontext import envcontext from pre_commit.languages import ruby -from pre_commit.prefix import Prefix -from pre_commit.util import cmd_output +from pre_commit.store import _make_local_repo from pre_commit.util import resource_bytesio +from testing.language_helpers import run_language +from testing.util import cwd from testing.util import xfailif_windows @@ -34,56 +35,104 @@ def test_uses_system_if_both_gem_and_ruby_are_available(find_exe_mck): assert ACTUAL_GET_DEFAULT_VERSION() == 'system' -@pytest.fixture -def fake_gem_prefix(tmpdir): +@pytest.mark.parametrize( + 'filename', + ('rbenv.tar.gz', 'ruby-build.tar.gz', 'ruby-download.tar.gz'), +) +def test_archive_root_stat(filename): + with resource_bytesio(filename) as f: + with tarfile.open(fileobj=f) as tarf: + root, _, _ = filename.partition('.') + assert oct(tarf.getmember(root).mode) == '0o755' + + +def _setup_hello_world(tmp_path): + bin_dir = tmp_path.joinpath('bin') + bin_dir.mkdir() + bin_dir.joinpath('ruby_hook').write_text( + '#!/usr/bin/env ruby\n' + "puts 'Hello world from a ruby hook'\n", + ) gemspec = '''\ Gem::Specification.new do |s| - s.name = 'pre_commit_placeholder_package' - s.version = '0.0.0' - s.summary = 'placeholder gem for pre-commit hooks' + s.name = 'ruby_hook' + s.version = '0.1.0' s.authors = ['Anthony Sottile'] + s.summary = 'A ruby hook!' + s.description = 'A ruby hook!' + s.files = ['bin/ruby_hook'] + s.executables = ['ruby_hook'] end ''' - tmpdir.join('placeholder_gem.gemspec').write(gemspec) - yield Prefix(tmpdir) + tmp_path.joinpath('ruby_hook.gemspec').write_text(gemspec) -@xfailif_windows # pragma: win32 no cover -def test_install_ruby_system(fake_gem_prefix): - ruby.install_environment(fake_gem_prefix, 'system', ()) +def test_ruby_hook_system(tmp_path): + assert ruby.get_default_version() == 'system' + + _setup_hello_world(tmp_path) + + ret = run_language(tmp_path, ruby, 'ruby_hook') + assert ret == (0, b'Hello world from a ruby hook\n') - # Should be able to activate and use rbenv install - with ruby.in_env(fake_gem_prefix, 'system'): - _, out, _ = cmd_output('gem', 'list') - assert 'pre_commit_placeholder_package' in out + +def test_ruby_with_user_install_set(tmp_path): + gemrc = tmp_path.joinpath('gemrc') + gemrc.write_text('gem: --user-install\n') + + with envcontext((('GEMRC', str(gemrc)),)): + test_ruby_hook_system(tmp_path) + + +def test_ruby_additional_deps(tmp_path): + _make_local_repo(tmp_path) + + ret = run_language( + tmp_path, + ruby, + 'ruby -e', + args=('require "tins"',), + deps=('tins',), + ) + assert ret == (0, b'') @xfailif_windows # pragma: win32 no cover -def test_install_ruby_default(fake_gem_prefix): - ruby.install_environment(fake_gem_prefix, C.DEFAULT, ()) - # Should have created rbenv directory - assert os.path.exists(fake_gem_prefix.path('rbenv-default')) +def test_ruby_hook_default(tmp_path): + _setup_hello_world(tmp_path) - # Should be able to activate using our script and access rbenv - with ruby.in_env(fake_gem_prefix, 'default'): - cmd_output('rbenv', '--help') + out, ret = run_language(tmp_path, ruby, 'rbenv --help', version='default') + assert out == 0 + assert ret.startswith(b'Usage: rbenv ') @xfailif_windows # pragma: win32 no cover -def test_install_ruby_with_version(fake_gem_prefix): - ruby.install_environment(fake_gem_prefix, '3.2.0', ()) +def test_ruby_hook_language_version(tmp_path): + _setup_hello_world(tmp_path) + tmp_path.joinpath('bin', 'ruby_hook').write_text( + '#!/usr/bin/env ruby\n' + 'puts RUBY_VERSION\n' + "puts 'Hello world from a ruby hook'\n", + ) - # Should be able to activate and use rbenv install - with ruby.in_env(fake_gem_prefix, '3.2.0'): - cmd_output('rbenv', 'install', '--help') + ret = run_language(tmp_path, ruby, 'ruby_hook', version='3.2.0') + assert ret == (0, b'3.2.0\nHello world from a ruby hook\n') -@pytest.mark.parametrize( - 'filename', - ('rbenv.tar.gz', 'ruby-build.tar.gz', 'ruby-download.tar.gz'), -) -def test_archive_root_stat(filename): - with resource_bytesio(filename) as f: - with tarfile.open(fileobj=f) as tarf: - root, _, _ = filename.partition('.') - assert oct(tarf.getmember(root).mode) == '0o755' +@xfailif_windows # pragma: win32 no cover +def test_ruby_with_bundle_disable_shared_gems(tmp_path): + workdir = tmp_path.joinpath('workdir') + workdir.mkdir() + # this Gemfile is missing `source` + workdir.joinpath('Gemfile').write_text('gem "lol_hai"\n') + # this bundle config causes things to be written elsewhere + bundle = workdir.joinpath('.bundle') + bundle.mkdir() + bundle.joinpath('config').write_text( + 'BUNDLE_DISABLE_SHARED_GEMS: true\n' + 'BUNDLE_PATH: vendor/gem\n', + ) + + with cwd(workdir): + # `3.2.0` has new enough `gem` requiring `source` and reading `.bundle` + test_ruby_hook_language_version(tmp_path) diff --git a/tests/repository_test.py b/tests/repository_test.py index 6565e1068..2cd4c0fa5 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -19,7 +19,6 @@ from pre_commit.languages import helpers from pre_commit.languages import node from pre_commit.languages import python -from pre_commit.languages import ruby from pre_commit.languages.all import languages from pre_commit.prefix import Prefix from pre_commit.repository import _hook_installed @@ -33,7 +32,6 @@ from testing.util import cwd from testing.util import get_resource_path from testing.util import skipif_cant_run_docker -from testing.util import xfailif_windows def _norm_out(b): @@ -227,52 +225,6 @@ def test_node_hook_with_npm_userconfig_set(tempdir_factory, store, tmpdir): test_run_a_node_hook(tempdir_factory, store) -def test_run_a_ruby_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'ruby_hooks_repo', - 'ruby_hook', [os.devnull], b'Hello world from a ruby hook\n', - ) - - -def test_run_a_ruby_hook_with_user_install_set(tempdir_factory, store, tmpdir): - gemrc = tmpdir.join('gemrc') - gemrc.write('gem: --user-install\n') - with envcontext((('GEMRC', str(gemrc)),)): - test_run_a_ruby_hook(tempdir_factory, store) - - -@xfailif_windows # pragma: win32 no cover -def test_run_versioned_ruby_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'ruby_versioned_hooks_repo', - 'ruby_hook', - [os.devnull], - b'3.2.0\nHello world from a ruby hook\n', - ) - - -@xfailif_windows # pragma: win32 no cover -def test_run_ruby_hook_with_disable_shared_gems( - tempdir_factory, - store, - tmpdir, -): - """Make sure a Gemfile in the project doesn't interfere.""" - tmpdir.join('Gemfile').write('gem "lol_hai"') - tmpdir.join('.bundle').mkdir() - tmpdir.join('.bundle', 'config').write( - 'BUNDLE_DISABLE_SHARED_GEMS: true\n' - 'BUNDLE_PATH: vendor/gem\n', - ) - with cwd(tmpdir.strpath): - _test_hook_repo( - tempdir_factory, store, 'ruby_versioned_hooks_repo', - 'ruby_hook', - [os.devnull], - b'3.2.0\nHello world from a ruby hook\n', - ) - - def test_system_hook_with_spaces(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'system_hook_with_spaces_repo', @@ -530,16 +482,6 @@ def test_repository_state_compatibility(tempdir_factory, store, v): assert _hook_installed(hook) is True -def test_additional_ruby_dependencies_installed(tempdir_factory, store): - path = make_repo(tempdir_factory, 'ruby_hooks_repo') - config = make_config_from_repo(path) - config['hooks'][0]['additional_dependencies'] = ['tins'] - hook = _get_hook(config, store, 'ruby_hook') - with ruby.in_env(hook.prefix, hook.language_version): - output = cmd_output('gem', 'list', '--local')[1] - assert 'tins' in output - - def test_additional_node_dependencies_installed(tempdir_factory, store): path = make_repo(tempdir_factory, 'node_hooks_repo') config = make_config_from_repo(path) From f54386203eebe320175638b3c89dd71fdc2e8674 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Jan 2023 23:04:25 -0500 Subject: [PATCH 117/342] upgrade asottile/workflows to get fast-checkout --- .github/actions/pre-test/action.yml | 2 +- .github/workflows/main.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml index 608c0cd11..42bbf00b5 100644 --- a/.github/actions/pre-test/action.yml +++ b/.github/actions/pre-test/action.yml @@ -36,5 +36,5 @@ runs: testing/get-coursier.sh testing/get-dart.sh testing/get-swift.sh - - uses: asottile/workflows/.github/actions/latest-git@v1.2.0 + - uses: asottile/workflows/.github/actions/latest-git@v1.4.0 if: inputs.env == 'py38' && runner.os == 'Linux' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c78d1051c..f281dcf27 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,12 +12,12 @@ concurrency: jobs: main-windows: - uses: asottile/workflows/.github/workflows/tox.yml@v1.2.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.4.0 with: env: '["py38"]' os: windows-latest main-linux: - uses: asottile/workflows/.github/workflows/tox.yml@v1.2.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.4.0 with: env: '["py38", "py39", "py310"]' os: ubuntu-latest From 2530913fad5c648d2614daf5c1a5583fb609fbd8 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 31 Jan 2023 20:40:19 -0500 Subject: [PATCH 118/342] ensure languages are healthy after creation --- testing/language_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/language_helpers.py b/testing/language_helpers.py index b20803bce..b9c538403 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -21,6 +21,8 @@ def run_language( version = version or language.get_default_version() language.install_environment(prefix, version, deps) + health_error = language.health_check(prefix, version) + assert health_error is None, health_error with language.in_env(prefix, version): ret, out = language.run_hook( prefix, From 909dd0e8a1984300b37611a79cf33ad3dd92aa98 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 31 Jan 2023 19:37:37 -0500 Subject: [PATCH 119/342] test node directly --- .../node_hooks_repo/.pre-commit-hooks.yaml | 5 --- testing/resources/node_hooks_repo/bin/main.js | 3 -- .../resources/node_hooks_repo/package.json | 5 --- .../.pre-commit-hooks.yaml | 6 --- .../node_versioned_hooks_repo/bin/main.js | 4 -- .../node_versioned_hooks_repo/package.json | 5 --- tests/languages/node_test.py | 41 +++++++++++++++++ tests/repository_test.py | 44 ------------------- 8 files changed, 41 insertions(+), 72 deletions(-) delete mode 100644 testing/resources/node_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/node_hooks_repo/bin/main.js delete mode 100644 testing/resources/node_hooks_repo/package.json delete mode 100644 testing/resources/node_versioned_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/node_versioned_hooks_repo/bin/main.js delete mode 100644 testing/resources/node_versioned_hooks_repo/package.json diff --git a/testing/resources/node_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/node_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 257698a44..000000000 --- a/testing/resources/node_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: foo - name: Foo - entry: foo - language: node - files: \.js$ diff --git a/testing/resources/node_hooks_repo/bin/main.js b/testing/resources/node_hooks_repo/bin/main.js deleted file mode 100644 index 8e0f025ab..000000000 --- a/testing/resources/node_hooks_repo/bin/main.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -console.log('Hello World'); diff --git a/testing/resources/node_hooks_repo/package.json b/testing/resources/node_hooks_repo/package.json deleted file mode 100644 index 050b6300b..000000000 --- a/testing/resources/node_hooks_repo/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "foo", - "version": "0.0.1", - "bin": {"foo": "./bin/main.js"} -} diff --git a/testing/resources/node_versioned_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/node_versioned_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index e7ad5ea7b..000000000 --- a/testing/resources/node_versioned_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,6 +0,0 @@ -- id: versioned-node-hook - name: Versioned node hook - entry: versioned-node-hook - language: node - language_version: 9.3.0 - files: \.js$ diff --git a/testing/resources/node_versioned_hooks_repo/bin/main.js b/testing/resources/node_versioned_hooks_repo/bin/main.js deleted file mode 100644 index df12cbebe..000000000 --- a/testing/resources/node_versioned_hooks_repo/bin/main.js +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env node - -console.log(process.version); -console.log('Hello World'); diff --git a/testing/resources/node_versioned_hooks_repo/package.json b/testing/resources/node_versioned_hooks_repo/package.json deleted file mode 100644 index 18c7787c7..000000000 --- a/testing/resources/node_versioned_hooks_repo/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "versioned-node-hook", - "version": "0.0.1", - "bin": {"versioned-node-hook": "./bin/main.js"} -} diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py index b69adfa67..cba0228b3 100644 --- a/tests/languages/node_test.py +++ b/tests/languages/node_test.py @@ -13,7 +13,9 @@ from pre_commit import parse_shebang from pre_commit.languages import node from pre_commit.prefix import Prefix +from pre_commit.store import _make_local_repo from pre_commit.util import cmd_output +from testing.language_helpers import run_language from testing.util import xfailif_windows @@ -109,3 +111,42 @@ def test_installs_without_links_outside_env(tmpdir): with node.in_env(prefix, 'system'): assert cmd_output('foo')[1] == 'success!\n' + + +def _make_hello_world(tmp_path): + package_json = '''\ +{"name": "t", "version": "0.0.1", "bin": {"node-hello": "./bin/main.js"}} +''' + tmp_path.joinpath('package.json').write_text(package_json) + bin_dir = tmp_path.joinpath('bin') + bin_dir.mkdir() + bin_dir.joinpath('main.js').write_text( + '#!/usr/bin/env node\n' + 'console.log("Hello World");\n', + ) + + +def test_node_hook_system(tmp_path): + _make_hello_world(tmp_path) + ret = run_language(tmp_path, node, 'node-hello') + assert ret == (0, b'Hello World\n') + + +def test_node_with_user_config_set(tmp_path): + cfg = tmp_path.joinpath('cfg') + cfg.write_text('cache=/dne\n') + with envcontext.envcontext((('NPM_CONFIG_USERCONFIG', str(cfg)),)): + test_node_hook_system(tmp_path) + + +@pytest.mark.parametrize('version', (C.DEFAULT, '18.13.0')) +def test_node_hook_versions(tmp_path, version): + _make_hello_world(tmp_path) + ret = run_language(tmp_path, node, 'node-hello', version=version) + assert ret == (0, b'Hello World\n') + + +def test_node_additional_deps(tmp_path): + _make_local_repo(str(tmp_path)) + ret, out = run_language(tmp_path, node, 'npm ls -g', deps=('lodash',)) + assert b' lodash@' in out diff --git a/tests/repository_test.py b/tests/repository_test.py index 2cd4c0fa5..b43b344c8 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -17,7 +17,6 @@ from pre_commit.hook import Hook from pre_commit.languages import golang from pre_commit.languages import helpers -from pre_commit.languages import node from pre_commit.languages import python from pre_commit.languages.all import languages from pre_commit.prefix import Prefix @@ -193,38 +192,6 @@ def test_run_a_docker_image_hook(tempdir_factory, store, hook_id): ) -def test_run_a_node_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'node_hooks_repo', - 'foo', [os.devnull], b'Hello World\n', - ) - - -def test_run_a_node_hook_default_version(tempdir_factory, store): - # make sure that this continues to work for platforms where node is not - # installed at the system - with mock.patch.object( - node, - 'get_default_version', - return_value=C.DEFAULT, - ): - test_run_a_node_hook(tempdir_factory, store) - - -def test_run_versioned_node_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'node_versioned_hooks_repo', - 'versioned-node-hook', [os.devnull], b'v9.3.0\nHello World\n', - ) - - -def test_node_hook_with_npm_userconfig_set(tempdir_factory, store, tmpdir): - cfg = tmpdir.join('cfg') - cfg.write('cache=/dne\n') - with mock.patch.dict(os.environ, NPM_CONFIG_USERCONFIG=str(cfg)): - test_run_a_node_hook(tempdir_factory, store) - - def test_system_hook_with_spaces(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'system_hook_with_spaces_repo', @@ -482,17 +449,6 @@ def test_repository_state_compatibility(tempdir_factory, store, v): assert _hook_installed(hook) is True -def test_additional_node_dependencies_installed(tempdir_factory, store): - path = make_repo(tempdir_factory, 'node_hooks_repo') - config = make_config_from_repo(path) - # Careful to choose a small package that's not depped by npm - config['hooks'][0]['additional_dependencies'] = ['lodash'] - hook = _get_hook(config, store, 'foo') - with node.in_env(hook.prefix, hook.language_version): - output = cmd_output('npm', 'ls', '-g')[1] - assert 'lodash' in output - - def test_additional_golang_dependencies_installed( tempdir_factory, store, ): From d216cdd5c1eccab623a71aa8b58813e4850f167d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 1 Feb 2023 18:16:09 -0500 Subject: [PATCH 120/342] fix golang version regex in test --- tests/languages/golang_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index 0219261fb..7c04255bc 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -1,9 +1,9 @@ from __future__ import annotations -import re from unittest import mock import pytest +import re_assert import pre_commit.constants as C from pre_commit.languages import golang @@ -40,4 +40,4 @@ def test_golang_infer_go_version_default(): version = ACTUAL_INFER_GO_VERSION(C.DEFAULT) assert version != C.DEFAULT - assert re.match(r'^\d+\.\d+\.\d+$', version) + re_assert.Matches(r'^\d+\.\d+(?:\.\d+)?$').assert_matches(version) From 7260d24d0fb0577f2111626b25d4f7bba56bfa5d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 1 Feb 2023 17:52:53 -0500 Subject: [PATCH 121/342] Revert "also ignore Gemfile in project" This reverts commit f4bd44996c888f48bc3a37d5ab19514325cb3f01. --- pre_commit/languages/ruby.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index b4d4b45af..4416f7280 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -39,7 +39,6 @@ def get_env_patch( ('GEM_HOME', os.path.join(venv, 'gems')), ('GEM_PATH', UNSET), ('BUNDLE_IGNORE_CONFIG', '1'), - ('BUNDLE_GEMFILE', os.devnull), ) if language_version == 'system': patches += ( From 1129e7d222fea31c9c536da0ae41610349854128 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 1 Feb 2023 17:58:08 -0500 Subject: [PATCH 122/342] fixup Gemfile in ruby tests --- tests/languages/ruby_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index b312c7fda..9cfaad5d0 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -123,8 +123,9 @@ def test_ruby_hook_language_version(tmp_path): def test_ruby_with_bundle_disable_shared_gems(tmp_path): workdir = tmp_path.joinpath('workdir') workdir.mkdir() - # this Gemfile is missing `source` - workdir.joinpath('Gemfile').write_text('gem "lol_hai"\n') + # this needs a `source` or there's a deprecation warning + # silencing this with `BUNDLE_GEMFILE` breaks some tools (#2739) + workdir.joinpath('Gemfile').write_text('source ""\ngem "lol_hai"\n') # this bundle config causes things to be written elsewhere bundle = workdir.joinpath('.bundle') bundle.mkdir() @@ -134,5 +135,5 @@ def test_ruby_with_bundle_disable_shared_gems(tmp_path): ) with cwd(workdir): - # `3.2.0` has new enough `gem` requiring `source` and reading `.bundle` + # `3.2.0` has new enough `gem` reading `.bundle` test_ruby_hook_language_version(tmp_path) From e846829992a84ce8066e6513a72a357709eec56c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 1 Feb 2023 18:21:18 -0500 Subject: [PATCH 123/342] v3.0.3 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0657e630..adf1e4b39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.0.3 - 2023-02-01 +================== + +### Fixes +- Revert "Prevent local `Gemfile` from interfering with hook execution.". + - #2739 issue by @Roguelazer. + - #2740 PR by @asottile. + 3.0.2 - 2023-01-29 ================== diff --git a/setup.cfg b/setup.cfg index 37511c09e..8eb9de7ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.0.2 +version = 3.0.3 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 7783a3e63a18ea3fb073eef5412b985153abdee8 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 2 Feb 2023 11:02:58 +0000 Subject: [PATCH 124/342] Add `--no-textconv` to `git diff` calls --- pre_commit/commands/run.py | 6 +++--- tests/commands/run_test.py | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index e44e70364..a7eb4f45a 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -272,7 +272,8 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]: def _get_diff() -> bytes: _, out, _ = cmd_output_b( - 'git', 'diff', '--no-ext-diff', '--ignore-submodules', check=False, + 'git', 'diff', '--no-ext-diff', '--no-textconv', '--ignore-submodules', + check=False, ) return out @@ -326,8 +327,7 @@ def _has_unmerged_paths() -> bool: def _has_unstaged_config(config_file: str) -> bool: retcode, _, _ = cmd_output_b( - 'git', 'diff', '--no-ext-diff', '--exit-code', config_file, - check=False, + 'git', 'diff', '--quiet', '--no-ext-diff', config_file, check=False, ) # be explicit, other git errors don't mean it has an unstaged config. return retcode == 1 diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 03d741e06..f1085d9bb 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -766,6 +766,47 @@ def test_lots_of_files(store, tempdir_factory): ) +def test_no_textconv(cap_out, store, repo_with_passing_hook): + # git textconv filters can hide changes from hooks + with open('.gitattributes', 'w') as fp: + fp.write('*.jpeg diff=empty\n') + + with open('.git/config', 'a') as fp: + fp.write('[diff "empty"]\n') + fp.write('textconv = "true"\n') + + config = { + 'repo': 'local', + 'hooks': [ + { + 'id': 'extend-jpeg', + 'name': 'extend-jpeg', + 'language': 'system', + 'entry': ( + f'{shlex.quote(sys.executable)} -c "import sys; ' + 'open(sys.argv[1], \'ab\').write(b\'\\x00\')"' + ), + 'types': ['jpeg'], + }, + ], + } + add_config_to_repo(repo_with_passing_hook, config) + + stage_a_file('example.jpeg') + + _test_run( + cap_out, + store, + repo_with_passing_hook, + {}, + ( + b'Failed', + ), + expected_ret=1, + stage=False, + ) + + def test_stages(cap_out, store, repo_with_passing_hook): config = { 'repo': 'local', From 0359fae2da2aadb2fbd3afae1777edd3aa856cc9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 3 Feb 2023 12:07:23 -0500 Subject: [PATCH 125/342] v3.0.4 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adf1e4b39..0998da98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.0.4 - 2023-02-03 +================== + +### Fixes +- Fix hook diff detection for files affected by `--textconv`. + - #2743 PR by @adamchainz. + - #2743 issue by @adamchainz. + 3.0.3 - 2023-02-01 ================== diff --git a/setup.cfg b/setup.cfg index 8eb9de7ae..56b856cad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.0.3 +version = 3.0.4 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 0c1267b214cee6da7337f7bcd42b89fd13015e26 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 4 Feb 2023 14:26:09 -0500 Subject: [PATCH 126/342] deprecate python_venv language --- pre_commit/commands/migrate_config.py | 9 +++++ pre_commit/repository.py | 9 +++++ .../.pre-commit-hooks.yaml | 5 --- .../resources/python_venv_hooks_repo/foo.py | 9 ----- .../resources/python_venv_hooks_repo/setup.py | 10 ------ tests/commands/migrate_config_test.py | 33 +++++++++++++++++++ tests/languages/all_test.py | 7 ++++ tests/repository_test.py | 20 ++++++++--- 8 files changed, 73 insertions(+), 29 deletions(-) delete mode 100644 testing/resources/python_venv_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/python_venv_hooks_repo/foo.py delete mode 100644 testing/resources/python_venv_hooks_repo/setup.py create mode 100644 tests/languages/all_test.py diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index 6f7af4eba..842fb3a7b 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -42,6 +42,14 @@ def _migrate_sha_to_rev(contents: str) -> str: return re.sub(r'(\n\s+)sha:', r'\1rev:', contents) +def _migrate_python_venv(contents: str) -> str: + return re.sub( + r'(\n\s+)language: python_venv\b', + r'\1language: python', + contents, + ) + + def migrate_config(config_file: str, quiet: bool = False) -> int: with open(config_file) as f: orig_contents = contents = f.read() @@ -55,6 +63,7 @@ def migrate_config(config_file: str, quiet: bool = False) -> int: contents = _migrate_map(contents) contents = _migrate_sha_to_rev(contents) + contents = _migrate_python_venv(contents) if contents != orig_contents: with open(config_file, 'w') as f: diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 616faf54c..308e80c70 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -3,6 +3,7 @@ import json import logging import os +import shlex from typing import Any from typing import Sequence @@ -68,6 +69,14 @@ def _hook_install(hook: Hook) -> None: logger.info('Once installed this environment will be reused.') logger.info('This may take a few minutes...') + if hook.language == 'python_venv': + logger.warning( + f'`repo: {hook.src}` uses deprecated `language: python_venv`. ' + f'This is an alias for `language: python`. ' + f'Often `pre-commit autoupdate --repo {shlex.quote(hook.src)}` ' + f'will fix this.', + ) + lang = languages[hook.language] assert lang.ENVIRONMENT_DIR is not None diff --git a/testing/resources/python_venv_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/python_venv_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index a666ed87a..000000000 --- a/testing/resources/python_venv_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: foo - name: Foo - entry: foo - language: python_venv - files: \.py$ diff --git a/testing/resources/python_venv_hooks_repo/foo.py b/testing/resources/python_venv_hooks_repo/foo.py deleted file mode 100644 index 40efde392..000000000 --- a/testing/resources/python_venv_hooks_repo/foo.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -import sys - - -def main(): - print(repr(sys.argv[1:])) - print('Hello World') - return 0 diff --git a/testing/resources/python_venv_hooks_repo/setup.py b/testing/resources/python_venv_hooks_repo/setup.py deleted file mode 100644 index cff6cadf3..000000000 --- a/testing/resources/python_venv_hooks_repo/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import annotations - -from setuptools import setup - -setup( - name='foo', - version='0.0.0', - py_modules=['foo'], - entry_points={'console_scripts': ['foo = foo:main']}, -) diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py index fca1ad92f..ba1846360 100644 --- a/tests/commands/migrate_config_test.py +++ b/tests/commands/migrate_config_test.py @@ -134,6 +134,39 @@ def test_migrate_config_sha_to_rev(tmpdir): ) +def test_migrate_config_language_python_venv(tmp_path): + src = '''\ +repos: +- repo: local + hooks: + - id: example + name: example + entry: example + language: python_venv + - id: example + name: example + entry: example + language: system +''' + expected = '''\ +repos: +- repo: local + hooks: + - id: example + name: example + entry: example + language: python + - id: example + name: example + entry: example + language: system +''' + cfg = tmp_path.joinpath('cfg.yaml') + cfg.write_text(src) + assert migrate_config(str(cfg)) == 0 + assert cfg.read_text() == expected + + def test_migrate_config_invalid_yaml(tmpdir): contents = '[' cfg = tmpdir.join(C.CONFIG_FILE) diff --git a/tests/languages/all_test.py b/tests/languages/all_test.py new file mode 100644 index 000000000..33b8925fb --- /dev/null +++ b/tests/languages/all_test.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from pre_commit.languages.all import languages + + +def test_python_venv_is_an_alias_to_python(): + assert languages['python_venv'] is languages['python'] diff --git a/tests/repository_test.py b/tests/repository_test.py index b43b344c8..9ec2d5493 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -129,11 +129,21 @@ def test_python_hook_weird_setup_cfg(in_git_dir, tempdir_factory, store): ) -def test_python_venv(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'python_venv_hooks_repo', - 'foo', [os.devnull], - f'[{os.devnull!r}]\nHello World\n'.encode(), +def test_python_venv_deprecation(store, caplog): + config = { + 'repo': 'local', + 'hooks': [{ + 'id': 'example', + 'name': 'example', + 'language': 'python_venv', + 'entry': 'echo hi', + }], + } + _get_hook(config, store, 'example') + assert caplog.messages[-1] == ( + '`repo: local` uses deprecated `language: python_venv`. ' + 'This is an alias for `language: python`. ' + 'Often `pre-commit autoupdate --repo local` will fix this.' ) From 0afb95ccca2f590bf45f45bcafb8ca792ce66423 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 4 Feb 2023 16:50:40 -0500 Subject: [PATCH 127/342] test docker and docker_image directly --- pre_commit/languages/docker.py | 3 +- testing/language_helpers.py | 7 ++-- .../docker_hooks_repo/.pre-commit-hooks.yaml | 17 -------- .../resources/docker_hooks_repo/Dockerfile | 3 -- .../.pre-commit-hooks.yaml | 8 ---- testing/util.py | 15 ------- tests/languages/docker_image_test.py | 27 +++++++++++++ tests/languages/docker_test.py | 14 +++++++ tests/repository_test.py | 40 ------------------- 9 files changed, 46 insertions(+), 88 deletions(-) delete mode 100644 testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/docker_hooks_repo/Dockerfile delete mode 100644 testing/resources/docker_image_hooks_repo/.pre-commit-hooks.yaml create mode 100644 tests/languages/docker_image_test.py diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index e80c95978..2212c5ccb 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -138,9 +138,8 @@ def run_hook( entry_exe, *cmd_rest = helpers.hook_cmd(entry, args) entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix)) - cmd = (*docker_cmd(), *entry_tag, *cmd_rest) return helpers.run_xargs( - cmd, + (*docker_cmd(), *entry_tag, *cmd_rest), file_args, require_serial=require_serial, color=color, diff --git a/testing/language_helpers.py b/testing/language_helpers.py index b9c538403..0964fbb44 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -20,9 +20,10 @@ def run_language( prefix = Prefix(str(path)) version = version or language.get_default_version() - language.install_environment(prefix, version, deps) - health_error = language.health_check(prefix, version) - assert health_error is None, health_error + if language.ENVIRONMENT_DIR is not None: + language.install_environment(prefix, version, deps) + health_error = language.health_check(prefix, version) + assert health_error is None, health_error with language.in_env(prefix, version): ret, out = language.run_hook( prefix, diff --git a/testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 529573965..000000000 --- a/testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,17 +0,0 @@ -- id: docker-hook - name: Docker test hook - entry: echo - language: docker - files: \.txt$ - -- id: docker-hook-arg - name: Docker test hook - entry: echo -n - language: docker - files: \.txt$ - -- id: docker-hook-failing - name: Docker test hook with nonzero exit code - entry: bork - language: docker - files: \.txt$ diff --git a/testing/resources/docker_hooks_repo/Dockerfile b/testing/resources/docker_hooks_repo/Dockerfile deleted file mode 100644 index 0bd1de0cf..000000000 --- a/testing/resources/docker_hooks_repo/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM ubuntu:focal - -CMD ["echo", "This is overwritten by the .pre-commit-hooks.yaml 'entry'"] diff --git a/testing/resources/docker_image_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/docker_image_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index e9fb24569..000000000 --- a/testing/resources/docker_image_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,8 +0,0 @@ -- id: echo-entrypoint - name: echo (via --entrypoint) - language: docker_image - entry: --entrypoint echo ubuntu:focal -- id: echo-cmd - name: echo (via cmd) - language: docker_image - entry: ubuntu:focal echo diff --git a/testing/util.py b/testing/util.py index b6c3804e6..7c68d0eee 100644 --- a/testing/util.py +++ b/testing/util.py @@ -6,24 +6,13 @@ import pytest -from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output -from pre_commit.util import cmd_output_b from testing.auto_namedtuple import auto_namedtuple TESTING_DIR = os.path.abspath(os.path.dirname(__file__)) -def docker_is_running() -> bool: # pragma: win32 no cover - try: - cmd_output_b('docker', 'ps') - except CalledProcessError: # pragma: no cover - return False - else: - return True - - def get_resource_path(path): return os.path.join(TESTING_DIR, 'resources', path) @@ -41,10 +30,6 @@ def cmd_output_mocked_pre_commit_home( return ret, out.replace('\r\n', '\n'), None -skipif_cant_run_docker = pytest.mark.skipif( - os.name == 'nt' or not docker_is_running(), - reason="Docker isn't running or can't be accessed", -) xfailif_windows = pytest.mark.xfail(os.name == 'nt', reason='windows') diff --git a/tests/languages/docker_image_test.py b/tests/languages/docker_image_test.py new file mode 100644 index 000000000..7993c11a8 --- /dev/null +++ b/tests/languages/docker_image_test.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from pre_commit.languages import docker_image +from testing.language_helpers import run_language +from testing.util import xfailif_windows + + +@xfailif_windows # pragma: win32 no cover +def test_docker_image_hook_via_entrypoint(tmp_path): + ret = run_language( + tmp_path, + docker_image, + '--entrypoint echo ubuntu:22.04', + args=('hello hello world',), + ) + assert ret == (0, b'hello hello world\n') + + +@xfailif_windows # pragma: win32 no cover +def test_docker_image_hook_via_args(tmp_path): + ret = run_language( + tmp_path, + docker_image, + 'ubuntu:22.04 echo', + args=('hello hello world',), + ) + assert ret == (0, b'hello hello world\n') diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py index 5f7c85e71..836382a8a 100644 --- a/tests/languages/docker_test.py +++ b/tests/languages/docker_test.py @@ -11,6 +11,8 @@ from pre_commit.languages import docker from pre_commit.util import CalledProcessError +from testing.language_helpers import run_language +from testing.util import xfailif_windows DOCKER_CGROUP_EXAMPLE = b'''\ 12:hugetlb:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 @@ -181,3 +183,15 @@ def test_get_docker_path_in_docker_docker_in_docker(in_docker): err = CalledProcessError(1, (), b'', b'') with mock.patch.object(docker, 'cmd_output_b', side_effect=err): assert docker._get_docker_path('/project') == '/project' + + +@xfailif_windows # pragma: win32 no cover +def test_docker_hook(tmp_path): + dockerfile = '''\ +FROM ubuntu:22.04 +CMD ["echo", "This is overwritten by the entry"'] +''' + tmp_path.joinpath('Dockerfile').write_text(dockerfile) + + ret = run_language(tmp_path, docker, 'echo hello hello world') + assert ret == (0, b'hello hello world\n') diff --git a/tests/repository_test.py b/tests/repository_test.py index 9ec2d5493..a4dcda5b3 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -30,7 +30,6 @@ from testing.fixtures import modify_manifest from testing.util import cwd from testing.util import get_resource_path -from testing.util import skipif_cant_run_docker def _norm_out(b): @@ -163,45 +162,6 @@ def test_language_versioned_python_hook(tempdir_factory, store): ) -@skipif_cant_run_docker # pragma: win32 no cover -def test_run_a_docker_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'docker_hooks_repo', - 'docker-hook', - ['Hello World from docker'], b'Hello World from docker\n', - ) - - -@skipif_cant_run_docker # pragma: win32 no cover -def test_run_a_docker_hook_with_entry_args(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'docker_hooks_repo', - 'docker-hook-arg', - ['Hello World from docker'], b'Hello World from docker', - ) - - -@skipif_cant_run_docker # pragma: win32 no cover -def test_run_a_failing_docker_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'docker_hooks_repo', - 'docker-hook-failing', - ['Hello World from docker'], - mock.ANY, # an error message about `bork` not existing - expected_return_code=127, - ) - - -@skipif_cant_run_docker # pragma: win32 no cover -@pytest.mark.parametrize('hook_id', ('echo-entrypoint', 'echo-cmd')) -def test_run_a_docker_image_hook(tempdir_factory, store, hook_id): - _test_hook_repo( - tempdir_factory, store, 'docker_image_hooks_repo', - hook_id, - ['Hello World from docker'], b'Hello World from docker\n', - ) - - def test_system_hook_with_spaces(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'system_hook_with_spaces_repo', From 6804100701a40c7defdbd5027e459385ceeba8f2 Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Mon, 6 Feb 2023 12:24:30 -0600 Subject: [PATCH 128/342] test golang directly --- .../golang_hooks_repo/.pre-commit-hooks.yaml | 5 - testing/resources/golang_hooks_repo/go.mod | 5 - testing/resources/golang_hooks_repo/go.sum | 2 - .../golang-hello-world/main.go | 23 ---- tests/languages/golang_test.py | 93 +++++++++++++ tests/repository_test.py | 126 ------------------ tests/store_test.py | 24 ++++ 7 files changed, 117 insertions(+), 161 deletions(-) delete mode 100644 testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/golang_hooks_repo/go.mod delete mode 100644 testing/resources/golang_hooks_repo/go.sum delete mode 100644 testing/resources/golang_hooks_repo/golang-hello-world/main.go diff --git a/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 206733bb6..000000000 --- a/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: golang-hook - name: golang example hook - entry: golang-hello-world - language: golang - files: '' diff --git a/testing/resources/golang_hooks_repo/go.mod b/testing/resources/golang_hooks_repo/go.mod deleted file mode 100644 index f37d4b674..000000000 --- a/testing/resources/golang_hooks_repo/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module golang-hello-world - -go 1.18 - -require github.com/BurntSushi/toml v1.1.0 diff --git a/testing/resources/golang_hooks_repo/go.sum b/testing/resources/golang_hooks_repo/go.sum deleted file mode 100644 index ec0c385a0..000000000 --- a/testing/resources/golang_hooks_repo/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= diff --git a/testing/resources/golang_hooks_repo/golang-hello-world/main.go b/testing/resources/golang_hooks_repo/golang-hello-world/main.go deleted file mode 100644 index 168574384..000000000 --- a/testing/resources/golang_hooks_repo/golang-hello-world/main.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - - -import ( - "fmt" - "runtime" - "github.com/BurntSushi/toml" - "os" -) - -type Config struct { - What string -} - -func main() { - message := runtime.Version() - if len(os.Args) > 1 { - message = os.Args[1] - } - var conf Config - toml.Decode("What = 'world'\n", &conf) - fmt.Printf("hello %v from %s\n", conf.What, message) -} diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index 7c04255bc..f5f9985b8 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -6,8 +6,11 @@ import re_assert import pre_commit.constants as C +from pre_commit.envcontext import envcontext from pre_commit.languages import golang from pre_commit.languages import helpers +from pre_commit.store import _make_local_repo +from testing.language_helpers import run_language ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__ @@ -41,3 +44,93 @@ def test_golang_infer_go_version_default(): assert version != C.DEFAULT re_assert.Matches(r'^\d+\.\d+(?:\.\d+)?$').assert_matches(version) + + +def _make_hello_world(tmp_path): + go_mod = '''\ +module golang-hello-world + +go 1.18 + +require github.com/BurntSushi/toml v1.1.0 +''' + go_sum = '''\ +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +''' # noqa: E501 + hello_world_go = '''\ +package main + + +import ( + "fmt" + "github.com/BurntSushi/toml" +) + +type Config struct { + What string +} + +func main() { + var conf Config + toml.Decode("What = 'world'\\n", &conf) + fmt.Printf("hello %v\\n", conf.What) +} +''' + tmp_path.joinpath('go.mod').write_text(go_mod) + tmp_path.joinpath('go.sum').write_text(go_sum) + mod_dir = tmp_path.joinpath('golang-hello-world') + mod_dir.mkdir() + main_file = mod_dir.joinpath('main.go') + main_file.write_text(hello_world_go) + + +def test_golang_system(tmp_path): + _make_hello_world(tmp_path) + + ret = run_language(tmp_path, golang, 'golang-hello-world') + assert ret == (0, b'hello world\n') + + +def test_golang_default_version(tmp_path): + _make_hello_world(tmp_path) + + ret = run_language( + tmp_path, + golang, + 'golang-hello-world', + version=C.DEFAULT, + ) + assert ret == (0, b'hello world\n') + + +def test_golang_versioned(tmp_path): + _make_local_repo(str(tmp_path)) + + ret, out = run_language( + tmp_path, + golang, + 'go version', + version='1.18.4', + ) + + assert ret == 0 + assert out.startswith(b'go version go1.18.4') + + +def test_local_golang_additional_deps(tmp_path): + _make_local_repo(str(tmp_path)) + + ret = run_language( + tmp_path, + golang, + 'hello', + deps=('golang.org/x/example/hello@latest',), + ) + + assert ret == (0, b'Hello, Go examples!\n') + + +def test_golang_hook_still_works_when_gobin_is_set(tmp_path): + with envcontext((('GOBIN', str(tmp_path.joinpath('gobin'))),)): + test_golang_system(tmp_path) diff --git a/tests/repository_test.py b/tests/repository_test.py index a4dcda5b3..0c9bba741 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -10,12 +10,9 @@ import re_assert import pre_commit.constants as C -from pre_commit import git from pre_commit.clientlib import CONFIG_SCHEMA from pre_commit.clientlib import load_manifest -from pre_commit.envcontext import envcontext from pre_commit.hook import Hook -from pre_commit.languages import golang from pre_commit.languages import helpers from pre_commit.languages import python from pre_commit.languages.all import languages @@ -169,92 +166,6 @@ def test_system_hook_with_spaces(tempdir_factory, store): ) -def test_golang_system_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'golang_hooks_repo', - 'golang-hook', ['system'], b'hello world from system\n', - config_kwargs={ - 'hooks': [{ - 'id': 'golang-hook', - 'language_version': 'system', - }], - }, - ) - - -def test_golang_versioned_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'golang_hooks_repo', - 'golang-hook', [], b'hello world from go1.18.4\n', - config_kwargs={ - 'hooks': [{ - 'id': 'golang-hook', - 'language_version': '1.18.4', - }], - }, - ) - - -def test_golang_hook_still_works_when_gobin_is_set(tempdir_factory, store): - gobin_dir = tempdir_factory.get() - with envcontext((('GOBIN', gobin_dir),)): - test_golang_system_hook(tempdir_factory, store) - assert os.listdir(gobin_dir) == [] - - -def test_golang_with_recursive_submodule(tmpdir, tempdir_factory, store): - sub_go = '''\ -package sub - -import "fmt" - -func Func() { - fmt.Println("hello hello world") -} -''' - sub = tmpdir.join('sub').ensure_dir() - sub.join('sub.go').write(sub_go) - cmd_output('git', '-C', str(sub), 'init', '.') - cmd_output('git', '-C', str(sub), 'add', '.') - git.commit(str(sub)) - - pre_commit_hooks = '''\ -- id: example - name: example - entry: example - language: golang - verbose: true -''' - go_mod = '''\ -module github.com/asottile/example - -go 1.14 -''' - main_go = '''\ -package main - -import "github.com/asottile/example/sub" - -func main() { - sub.Func() -} -''' - repo = tmpdir.join('repo').ensure_dir() - repo.join('.pre-commit-hooks.yaml').write(pre_commit_hooks) - repo.join('go.mod').write(go_mod) - repo.join('main.go').write(main_go) - cmd_output('git', '-C', str(repo), 'init', '.') - cmd_output('git', '-C', str(repo), 'add', '.') - cmd_output('git', '-C', str(repo), 'submodule', 'add', str(sub), 'sub') - git.commit(str(repo)) - - config = make_config_from_repo(str(repo)) - hook = _get_hook(config, store, 'example') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - assert _norm_out(out) == b'hello hello world\n' - - def test_missing_executable(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'not_found_exe', @@ -419,43 +330,6 @@ def test_repository_state_compatibility(tempdir_factory, store, v): assert _hook_installed(hook) is True -def test_additional_golang_dependencies_installed( - tempdir_factory, store, -): - path = make_repo(tempdir_factory, 'golang_hooks_repo') - config = make_config_from_repo(path) - # A small go package - deps = ['golang.org/x/example/hello@latest'] - config['hooks'][0]['additional_dependencies'] = deps - hook = _get_hook(config, store, 'golang-hook') - envdir = helpers.environment_dir( - hook.prefix, - golang.ENVIRONMENT_DIR, - golang.get_default_version(), - ) - binaries = os.listdir(os.path.join(envdir, 'bin')) - # normalize for windows - binaries = [os.path.splitext(binary)[0] for binary in binaries] - assert 'hello' in binaries - - -def test_local_golang_additional_dependencies(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'hello', - 'name': 'hello', - 'entry': 'hello', - 'language': 'golang', - 'additional_dependencies': ['golang.org/x/example/hello@latest'], - }], - } - hook = _get_hook(config, store, 'hello') - ret, out = _hook_run(hook, (), color=False) - assert ret == 0 - assert _norm_out(out) == b'Hello, Go examples!\n' - - def test_fail_hooks(store): config = { 'repo': 'local', diff --git a/tests/store_test.py b/tests/store_test.py index c42ce6537..146eac416 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -246,3 +246,27 @@ def _chmod_minus_w(p): # should be skipped due to readonly store.mark_config_used(str(cfg)) assert store.select_all_configs() == [] + + +def test_clone_with_recursive_submodules(store, tmp_path): + sub = tmp_path.joinpath('sub') + sub.mkdir() + sub.joinpath('submodule').write_text('i am a submodule') + cmd_output('git', '-C', str(sub), 'init', '.') + cmd_output('git', '-C', str(sub), 'add', '.') + git.commit(str(sub)) + + repo = tmp_path.joinpath('repo') + repo.mkdir() + repo.joinpath('repository').write_text('i am a repo') + cmd_output('git', '-C', str(repo), 'init', '.') + cmd_output('git', '-C', str(repo), 'add', '.') + cmd_output('git', '-C', str(repo), 'submodule', 'add', str(sub), 'sub') + git.commit(str(repo)) + + rev = git.head_rev(str(repo)) + ret = store.clone(str(repo), rev) + + assert os.path.exists(ret) + assert os.path.exists(os.path.join(ret, str(repo), 'repository')) + assert os.path.exists(os.path.join(ret, str(sub), 'submodule')) From 915b930a5d0c894a4b0d2a6957f833179255cd42 Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Tue, 7 Feb 2023 21:22:26 -0600 Subject: [PATCH 129/342] test dotnet directly --- pre_commit/languages/dotnet.py | 4 - .../.pre-commit-hooks.yaml | 12 -- .../dotnet_hooks_combo_repo.sln | 28 ---- .../dotnet_hooks_combo_repo/proj1/Program.cs | 12 -- .../proj1/proj1.csproj | 12 -- .../dotnet_hooks_combo_repo/proj2/Program.cs | 12 -- .../proj2/proj2.csproj | 12 -- .../.gitignore | 3 - .../.pre-commit-hooks.yaml | 5 - .../Program.cs | 12 -- .../dotnet_hooks_csproj_prefix_repo.csproj | 9 - .../dotnet_hooks_csproj_repo/.gitignore | 3 - .../.pre-commit-hooks.yaml | 5 - .../dotnet_hooks_csproj_repo/Program.cs | 12 -- .../dotnet_hooks_csproj_repo.csproj | 9 - .../dotnet_hooks_sln_repo/.gitignore | 3 - .../.pre-commit-hooks.yaml | 5 - .../dotnet_hooks_sln_repo/Program.cs | 12 -- .../dotnet_hooks_sln_repo.csproj | 9 - .../dotnet_hooks_sln_repo.sln | 34 ---- tests/languages/dotnet_test.py | 154 ++++++++++++++++++ tests/repository_test.py | 16 -- 22 files changed, 154 insertions(+), 229 deletions(-) delete mode 100644 testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln delete mode 100644 testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs delete mode 100644 testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj delete mode 100644 testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs delete mode 100644 testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj delete mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore delete mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs delete mode 100644 testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj delete mode 100644 testing/resources/dotnet_hooks_csproj_repo/.gitignore delete mode 100644 testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/dotnet_hooks_csproj_repo/Program.cs delete mode 100644 testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj delete mode 100644 testing/resources/dotnet_hooks_sln_repo/.gitignore delete mode 100644 testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/dotnet_hooks_sln_repo/Program.cs delete mode 100644 testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj delete mode 100644 testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.sln diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 4c3955e85..05d4ce32c 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -109,7 +109,3 @@ def install_environment( tool_id, ), ) - - # Clean the git dir, ignoring the environment dir - clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*') - helpers.run_setup_cmd(prefix, clean_cmd) diff --git a/testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml deleted file mode 100644 index f221854a4..000000000 --- a/testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,12 +0,0 @@ -- id: dotnet-example-hook - name: Test Project 1 - description: Test Project 1 - entry: proj1 - language: dotnet - stages: [commit] -- id: proj2 - name: Test Project 2 - description: Test Project 2 - entry: proj2 - language: dotnet - stages: [commit] diff --git a/testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln b/testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln deleted file mode 100644 index edb0fcbc5..000000000 --- a/testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj1", "proj1\proj1.csproj", "{38A939C3-DEA4-47D7-9B75-0418C4249662}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj2", "proj2\proj2.csproj", "{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.Build.0 = Debug|Any CPU - {38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.Build.0 = Release|Any CPU - {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs b/testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs deleted file mode 100644 index 03876f5cd..000000000 --- a/testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace proj1 -{ - class Program - { - static void Main(string[] args) - { - Console.Write("Hello from dotnet!\n"); - } - } -} diff --git a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj deleted file mode 100644 index 861ced6d9..000000000 --- a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - net6 - - true - proj1 - ./nupkg - - - diff --git a/testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs b/testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs deleted file mode 100644 index 47a99a358..000000000 --- a/testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace proj2 -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} diff --git a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj deleted file mode 100644 index dfce2cad1..000000000 --- a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - net6 - - true - proj2 - ./nupkg - - - diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore b/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore deleted file mode 100644 index edcd28f4a..000000000 --- a/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/ -obj/ -nupkg/ diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 6626627d7..000000000 --- a/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: dotnet-example-hook - name: dotnet example hook - entry: testeroni.tool - language: dotnet - files: '' diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs b/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs deleted file mode 100644 index 1456e8ef2..000000000 --- a/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace dotnet_hooks_repo -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello from dotnet!"); - } - } -} diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj b/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj deleted file mode 100644 index 754b76006..000000000 --- a/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - Exe - net7.0 - true - testeroni.tool - ./nupkg - - diff --git a/testing/resources/dotnet_hooks_csproj_repo/.gitignore b/testing/resources/dotnet_hooks_csproj_repo/.gitignore deleted file mode 100644 index edcd28f4a..000000000 --- a/testing/resources/dotnet_hooks_csproj_repo/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/ -obj/ -nupkg/ diff --git a/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 0f514c116..000000000 --- a/testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: dotnet-example-hook - name: dotnet example hook - entry: testeroni - language: dotnet - files: '' diff --git a/testing/resources/dotnet_hooks_csproj_repo/Program.cs b/testing/resources/dotnet_hooks_csproj_repo/Program.cs deleted file mode 100644 index 1456e8ef2..000000000 --- a/testing/resources/dotnet_hooks_csproj_repo/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace dotnet_hooks_repo -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello from dotnet!"); - } - } -} diff --git a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj b/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj deleted file mode 100644 index fa9879b0d..000000000 --- a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - Exe - net6 - true - testeroni - ./nupkg - - diff --git a/testing/resources/dotnet_hooks_sln_repo/.gitignore b/testing/resources/dotnet_hooks_sln_repo/.gitignore deleted file mode 100644 index edcd28f4a..000000000 --- a/testing/resources/dotnet_hooks_sln_repo/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/ -obj/ -nupkg/ diff --git a/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 0f514c116..000000000 --- a/testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: dotnet-example-hook - name: dotnet example hook - entry: testeroni - language: dotnet - files: '' diff --git a/testing/resources/dotnet_hooks_sln_repo/Program.cs b/testing/resources/dotnet_hooks_sln_repo/Program.cs deleted file mode 100644 index 04ad4e0cc..000000000 --- a/testing/resources/dotnet_hooks_sln_repo/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace dotnet_hooks_sln_repo -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello from dotnet!"); - } - } -} diff --git a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj b/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj deleted file mode 100644 index a4e2d0058..000000000 --- a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - Exe - net6 - true - testeroni - ./nupkg - - diff --git a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.sln b/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.sln deleted file mode 100644 index 87d2afbaf..000000000 --- a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet_hooks_sln_repo", "dotnet_hooks_sln_repo.csproj", "{6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x64.ActiveCfg = Debug|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x64.Build.0 = Debug|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x86.ActiveCfg = Debug|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x86.Build.0 = Debug|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|Any CPU.Build.0 = Release|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x64.ActiveCfg = Release|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x64.Build.0 = Release|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x86.ActiveCfg = Release|Any CPU - {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/tests/languages/dotnet_test.py b/tests/languages/dotnet_test.py index e69de29bb..470c03b22 100644 --- a/tests/languages/dotnet_test.py +++ b/tests/languages/dotnet_test.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from pre_commit.languages import dotnet +from testing.language_helpers import run_language + + +def _write_program_cs(tmp_path): + program_cs = '''\ +using System; + +namespace dotnet_tests +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello from dotnet!"); + } + } +} +''' + tmp_path.joinpath('Program.cs').write_text(program_cs) + + +def _csproj(tool_name): + return f'''\ + + + Exe + net6 + true + {tool_name} + ./nupkg + + +''' + + +def test_dotnet_csproj(tmp_path): + csproj = _csproj('testeroni') + _write_program_cs(tmp_path) + tmp_path.joinpath('dotnet_csproj.csproj').write_text(csproj) + ret = run_language(tmp_path, dotnet, 'testeroni') + assert ret == (0, b'Hello from dotnet!\n') + + +def test_dotnet_csproj_prefix(tmp_path): + csproj = _csproj('testeroni.tool') + _write_program_cs(tmp_path) + tmp_path.joinpath('dotnet_hooks_csproj_prefix.csproj').write_text(csproj) + ret = run_language(tmp_path, dotnet, 'testeroni.tool') + assert ret == (0, b'Hello from dotnet!\n') + + +def test_dotnet_sln(tmp_path): + csproj = _csproj('testeroni') + sln = '''\ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet_hooks_sln_repo", "dotnet_hooks_sln_repo.csproj", "{6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x64.ActiveCfg = Debug|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x64.Build.0 = Debug|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x86.ActiveCfg = Debug|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Debug|x86.Build.0 = Debug|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|Any CPU.Build.0 = Release|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x64.ActiveCfg = Release|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x64.Build.0 = Release|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x86.ActiveCfg = Release|Any CPU + {6568CFDB-6F6F-45A9-932C-8C7DAABC8E56}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal +''' # noqa: E501 + _write_program_cs(tmp_path) + tmp_path.joinpath('dotnet_hooks_sln_repo.csproj').write_text(csproj) + tmp_path.joinpath('dotnet_hooks_sln_repo.sln').write_text(sln) + + ret = run_language(tmp_path, dotnet, 'testeroni') + assert ret == (0, b'Hello from dotnet!\n') + + +def _setup_dotnet_combo(tmp_path): + sln = '''\ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj1", "proj1\\proj1.csproj", "{38A939C3-DEA4-47D7-9B75-0418C4249662}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proj2", "proj2\\proj2.csproj", "{4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38A939C3-DEA4-47D7-9B75-0418C4249662}.Release|Any CPU.Build.0 = Release|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C9916CB-165C-4EF5-8A57-4CB6794C1EBF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal +''' # noqa: E501 + tmp_path.joinpath('dotnet_hooks_combo_repo.sln').write_text(sln) + + csproj1 = _csproj('proj1') + proj1 = tmp_path.joinpath('proj1') + proj1.mkdir() + proj1.joinpath('proj1.csproj').write_text(csproj1) + _write_program_cs(proj1) + + csproj2 = _csproj('proj2') + proj2 = tmp_path.joinpath('proj2') + proj2.mkdir() + proj2.joinpath('proj2.csproj').write_text(csproj2) + _write_program_cs(proj2) + + +def test_dotnet_combo_proj1(tmp_path): + _setup_dotnet_combo(tmp_path) + ret = run_language(tmp_path, dotnet, 'proj1') + assert ret == (0, b'Hello from dotnet!\n') + + +def test_dotnet_combo_proj2(tmp_path): + _setup_dotnet_combo(tmp_path) + ret = run_language(tmp_path, dotnet, 'proj2') + assert ret == (0, b'Hello from dotnet!\n') diff --git a/tests/repository_test.py b/tests/repository_test.py index 0c9bba741..9e2f1e519 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -625,22 +625,6 @@ def test_manifest_hooks(tempdir_factory, store): ) -@pytest.mark.parametrize( - 'repo', - ( - 'dotnet_hooks_csproj_repo', - 'dotnet_hooks_sln_repo', - 'dotnet_hooks_combo_repo', - 'dotnet_hooks_csproj_prefix_repo', - ), -) -def test_dotnet_hook(tempdir_factory, store, repo): - _test_hook_repo( - tempdir_factory, store, repo, - 'dotnet-example-hook', [], b'Hello from dotnet!\n', - ) - - def test_non_installable_hook_error_for_language_version(store, caplog): config = { 'repo': 'local', From abbfb2e9b9195f6ae03441a0d69e4d2f8575d416 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 8 Feb 2023 06:43:04 +0000 Subject: [PATCH 130/342] List golang as first-class language --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9bcb79ed..ab3a92989 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,10 +64,10 @@ to implement. The current implemented languages are at varying levels: - 0th class - pre-commit does not require any dependencies for these languages as they're not actually languages (current examples: fail, pygrep) - 1st class - pre-commit will bootstrap a full interpreter requiring nothing to - be installed globally (current examples: node, ruby, rust) + be installed globally (current examples: go, node, ruby, rust) - 2nd class - pre-commit requires the user to install the language globally but - will install tools in an isolated fashion (current examples: python, go, - swift, docker). + will install tools in an isolated fashion (current examples: python, swift, + docker). - 3rd class - pre-commit requires the user to install both the tool and the language globally (current examples: script, system) From 563507937324d8214a82f3cfd6199ea4ace875d0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 8 Feb 2023 11:20:30 -0500 Subject: [PATCH 131/342] force the issue template more --- .../ISSUE_TEMPLATE/{bug.yaml => 00_bug.yaml} | 6 +++ .github/ISSUE_TEMPLATE/01_feature.yaml | 38 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yaml | 6 +++ 3 files changed, 50 insertions(+) rename .github/ISSUE_TEMPLATE/{bug.yaml => 00_bug.yaml} (87%) create mode 100644 .github/ISSUE_TEMPLATE/01_feature.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/00_bug.yaml similarity index 87% rename from .github/ISSUE_TEMPLATE/bug.yaml rename to .github/ISSUE_TEMPLATE/00_bug.yaml index 96cd6c75c..980f7afee 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/00_bug.yaml @@ -16,6 +16,12 @@ body: placeholder: ... validations: required: true + - type: markdown + attributes: + value: | + 95% of issues created are duplicates. + please try extra hard to find them first. + it's very unlikely your problem is unique. - type: textarea id: freeform attributes: diff --git a/.github/ISSUE_TEMPLATE/01_feature.yaml b/.github/ISSUE_TEMPLATE/01_feature.yaml new file mode 100644 index 000000000..c7ddc84cd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_feature.yaml @@ -0,0 +1,38 @@ +name: feature request +description: something new +body: + - type: markdown + attributes: + value: | + this is for issues for `pre-commit` (the framework). + if you are reporting an issue for [pre-commit.ci] please report it at [pre-commit-ci/issues] + + [pre-commit.ci]: https://pre-commit.ci + [pre-commit-ci/issues]: https://github.com/pre-commit-ci/issues + - type: input + id: search + attributes: + label: search you tried in the issue tracker + placeholder: ... + validations: + required: true + - type: markdown + attributes: + value: | + 95% of issues created are duplicates. + please try extra hard to find them first. + it's very unlikely your feature idea is a new one. + - type: textarea + id: freeform + attributes: + label: describe your actual problem + placeholder: 'I want to do ... I tried ... It does not work because ...' + validations: + required: true + - type: input + id: version + attributes: + label: pre-commit --version + placeholder: pre-commit x.x.x + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 000000000..a2d14826c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,6 @@ +blank_issues_enabled: false +contact_links: +- name: documentation + url: https://pre-commit.com +- name: pre-commit.ci issues + url: https://github.com/pre-commit-ci/issues From 16869444cae5ebea1917a2442e37eff381c44c76 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 8 Feb 2023 11:21:50 -0500 Subject: [PATCH 132/342] git mv .github/ISSUE_TEMPLATE/config.{yaml,yml} --- .github/ISSUE_TEMPLATE/{config.yaml => config.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{config.yaml => config.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yaml rename to .github/ISSUE_TEMPLATE/config.yml From 4bd1677cda652a92c38a6051e7b8a1d76e36364b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 8 Feb 2023 11:23:43 -0500 Subject: [PATCH 133/342] do template links need about? --- .github/ISSUE_TEMPLATE/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a2d14826c..4179f47f3 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,5 +2,7 @@ blank_issues_enabled: false contact_links: - name: documentation url: https://pre-commit.com + about: please check the docs first - name: pre-commit.ci issues url: https://github.com/pre-commit-ci/issues + about: please report issues about pre-commit.ci here From 4fdfb25a5245e63dd424f72ef7f66dfe49b2b53b Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Fri, 10 Feb 2023 16:18:43 -0600 Subject: [PATCH 134/342] test fail language inline --- tests/languages/fail_test.py | 14 ++++++++++++++ tests/repository_test.py | 24 ------------------------ 2 files changed, 14 insertions(+), 24 deletions(-) create mode 100644 tests/languages/fail_test.py diff --git a/tests/languages/fail_test.py b/tests/languages/fail_test.py new file mode 100644 index 000000000..7c74886fd --- /dev/null +++ b/tests/languages/fail_test.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from pre_commit.languages import fail +from testing.language_helpers import run_language + + +def test_fail_hooks(tmp_path): + ret = run_language( + tmp_path, + fail, + 'watch out for', + file_args=('bunnies',), + ) + assert ret == (1, b'watch out for\n\nbunnies\n') diff --git a/tests/repository_test.py b/tests/repository_test.py index 9e2f1e519..1a16e691f 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -330,30 +330,6 @@ def test_repository_state_compatibility(tempdir_factory, store, v): assert _hook_installed(hook) is True -def test_fail_hooks(store): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'fail', - 'name': 'fail', - 'language': 'fail', - 'entry': 'make sure to name changelogs as .rst!', - 'files': r'changelog/.*(? Date: Tue, 14 Feb 2023 02:25:01 +0000 Subject: [PATCH 135/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.991 → v1.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7d7f1f0d..023f4f683 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 + rev: v1.0.0 hooks: - id: mypy additional_dependencies: [types-all] From 8db5aaf4f32f9ac3d4407f70478d0aa15c0d4680 Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:30:46 -0600 Subject: [PATCH 136/342] future-proof dotnet build command see https://github.com/dotnet/sdk/issues/30624#issuecomment-1435457318 --- pre_commit/languages/dotnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 05d4ce32c..3db2679d3 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -61,7 +61,7 @@ def install_environment( helpers.assert_no_additional_deps('dotnet', additional_dependencies) envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) - build_dir = 'pre-commit-build' + build_dir = prefix.path('pre-commit-build') # Build & pack nupkg file helpers.run_setup_cmd( @@ -69,7 +69,7 @@ def install_environment( ( 'dotnet', 'pack', '--configuration', 'Release', - '--output', build_dir, + '--property', f'PackageOutputPath={build_dir}', ), ) From a2373d0a8198425785951cbd5f037d9815abb2ab Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Wed, 15 Feb 2023 20:50:19 -0600 Subject: [PATCH 137/342] test pygrep inline --- tests/languages/pygrep_test.py | 17 +++++++++++++ tests/repository_test.py | 46 ---------------------------------- 2 files changed, 17 insertions(+), 46 deletions(-) diff --git a/tests/languages/pygrep_test.py b/tests/languages/pygrep_test.py index 8420046c5..c6271c807 100644 --- a/tests/languages/pygrep_test.py +++ b/tests/languages/pygrep_test.py @@ -3,6 +3,7 @@ import pytest from pre_commit.languages import pygrep +from testing.language_helpers import run_language @pytest.fixture @@ -13,6 +14,9 @@ def some_files(tmpdir): tmpdir.join('f4').write_binary(b'foo\npattern\nbar\n') tmpdir.join('f5').write_binary(b'[INFO] hi\npattern\nbar') tmpdir.join('f6').write_binary(b"pattern\nbarwith'foo\n") + tmpdir.join('f7').write_binary(b"hello'hi\nworld\n") + tmpdir.join('f8').write_binary(b'foo\nbar\nbaz\n') + tmpdir.join('f9').write_binary(b'[WARN] hi\n') with tmpdir.as_cwd(): yield @@ -125,3 +129,16 @@ def test_multiline_multiline_flag_is_enabled(cap_out): out = cap_out.get() assert ret == 1 assert out == 'f1:1:foo\nbar\n' + + +def test_grep_hook_matching(some_files, tmp_path): + ret = run_language( + tmp_path, pygrep, 'ello', file_args=('f7', 'f8', 'f9'), + ) + assert ret == (1, b"f7:1:hello'hi\n") + + +@pytest.mark.parametrize('regex', ('nope', "foo'bar", r'^\[INFO\]')) +def test_grep_hook_not_matching(regex, some_files, tmp_path): + ret = run_language(tmp_path, pygrep, regex, file_args=('f7', 'f8', 'f9')) + assert ret == (0, b'') diff --git a/tests/repository_test.py b/tests/repository_test.py index 1a16e691f..332816d25 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -226,52 +226,6 @@ def test_output_isatty(tempdir_factory, store): ) -def _make_grep_repo(entry, store, args=()): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'grep-hook', - 'name': 'grep-hook', - 'language': 'pygrep', - 'entry': entry, - 'args': args, - 'types': ['text'], - }], - } - return _get_hook(config, store, 'grep-hook') - - -@pytest.fixture -def greppable_files(tmpdir): - with tmpdir.as_cwd(): - cmd_output_b('git', 'init', '.') - tmpdir.join('f1').write_binary(b"hello'hi\nworld\n") - tmpdir.join('f2').write_binary(b'foo\nbar\nbaz\n') - tmpdir.join('f3').write_binary(b'[WARN] hi\n') - yield tmpdir - - -def test_grep_hook_matching(greppable_files, store): - hook = _make_grep_repo('ello', store) - ret, out = _hook_run(hook, ('f1', 'f2', 'f3'), color=False) - assert ret == 1 - assert _norm_out(out) == b"f1:1:hello'hi\n" - - -def test_grep_hook_case_insensitive(greppable_files, store): - hook = _make_grep_repo('ELLO', store, args=['-i']) - ret, out = _hook_run(hook, ('f1', 'f2', 'f3'), color=False) - assert ret == 1 - assert _norm_out(out) == b"f1:1:hello'hi\n" - - -@pytest.mark.parametrize('regex', ('nope', "foo'bar", r'^\[INFO\]')) -def test_grep_hook_not_matching(regex, greppable_files, store): - hook = _make_grep_repo(regex, store) - ret, out = _hook_run(hook, ('f1', 'f2', 'f3'), color=False) - assert (ret, out) == (0, b'') - - def _norm_pwd(path): # Under windows bash's temp and windows temp is different. # This normalizes to the bash /tmp From d3883ce7f77f6cb88b622326de23c09cf8552cf6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Feb 2023 17:59:15 -0500 Subject: [PATCH 138/342] move languages.all and languages.helpers out of languages --- pre_commit/all_languages.py | 48 +++++++++ pre_commit/clientlib.py | 8 +- pre_commit/commands/run.py | 2 +- .../{languages/helpers.py => lang_base.py} | 45 ++++++++- pre_commit/languages/all.py | 99 ------------------- pre_commit/languages/conda.py | 14 +-- pre_commit/languages/coursier.py | 18 ++-- pre_commit/languages/dart.py | 20 ++-- pre_commit/languages/docker.py | 20 ++-- pre_commit/languages/docker_image.py | 14 +-- pre_commit/languages/dotnet.py | 20 ++-- pre_commit/languages/fail.py | 10 +- pre_commit/languages/golang.py | 16 +-- pre_commit/languages/lua.py | 18 ++-- pre_commit/languages/node.py | 18 ++-- pre_commit/languages/perl.py | 14 +-- pre_commit/languages/pygrep.py | 10 +- pre_commit/languages/python.py | 14 +-- pre_commit/languages/r.py | 12 +-- pre_commit/languages/ruby.py | 24 ++--- pre_commit/languages/rust.py | 12 +-- pre_commit/languages/script.py | 14 +-- pre_commit/languages/swift.py | 16 +-- pre_commit/languages/system.py | 12 +-- pre_commit/repository.py | 4 +- testing/language_helpers.py | 2 +- .../all_test.py => all_languages_test.py} | 2 +- .../helpers_test.py => lang_base_test.py} | 34 +++---- tests/languages/golang_test.py | 4 +- tests/repository_test.py | 12 +-- 30 files changed, 274 insertions(+), 282 deletions(-) create mode 100644 pre_commit/all_languages.py rename pre_commit/{languages/helpers.py => lang_base.py} (75%) delete mode 100644 pre_commit/languages/all.py rename tests/{languages/all_test.py => all_languages_test.py} (75%) rename tests/{languages/helpers_test.py => lang_base_test.py} (78%) diff --git a/pre_commit/all_languages.py b/pre_commit/all_languages.py new file mode 100644 index 000000000..2bed7067f --- /dev/null +++ b/pre_commit/all_languages.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from pre_commit.lang_base import Language +from pre_commit.languages import conda +from pre_commit.languages import coursier +from pre_commit.languages import dart +from pre_commit.languages import docker +from pre_commit.languages import docker_image +from pre_commit.languages import dotnet +from pre_commit.languages import fail +from pre_commit.languages import golang +from pre_commit.languages import lua +from pre_commit.languages import node +from pre_commit.languages import perl +from pre_commit.languages import pygrep +from pre_commit.languages import python +from pre_commit.languages import r +from pre_commit.languages import ruby +from pre_commit.languages import rust +from pre_commit.languages import script +from pre_commit.languages import swift +from pre_commit.languages import system + + +languages: dict[str, Language] = { + 'conda': conda, + 'coursier': coursier, + 'dart': dart, + 'docker': docker, + 'docker_image': docker_image, + 'dotnet': dotnet, + 'fail': fail, + 'golang': golang, + 'lua': lua, + 'node': node, + 'perl': perl, + 'pygrep': pygrep, + 'python': python, + 'r': r, + 'ruby': ruby, + 'rust': rust, + 'script': script, + 'swift': swift, + 'system': system, + # TODO: fully deprecate `python_venv` + 'python_venv': python, +} +language_names = sorted(languages) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index e191d3a00..9ff38c6a2 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -12,8 +12,8 @@ from identify.identify import ALL_TAGS import pre_commit.constants as C +from pre_commit.all_languages import language_names from pre_commit.errors import FatalError -from pre_commit.languages.all import all_languages from pre_commit.yaml import yaml_load logger = logging.getLogger('pre_commit') @@ -49,7 +49,7 @@ def check_min_version(version: str) -> None: cfgv.Required('id', cfgv.check_string), cfgv.Required('name', cfgv.check_string), cfgv.Required('entry', cfgv.check_string), - cfgv.Required('language', cfgv.check_one_of(all_languages)), + cfgv.Required('language', cfgv.check_one_of(language_names)), cfgv.Optional('alias', cfgv.check_string, ''), cfgv.Optional('files', check_string_regex, ''), @@ -281,8 +281,8 @@ def check(self, dct: dict[str, Any]) -> None: ) DEFAULT_LANGUAGE_VERSION = cfgv.Map( 'DefaultLanguageVersion', None, - cfgv.NoAdditionalKeys(all_languages), - *(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in all_languages), + cfgv.NoAdditionalKeys(language_names), + *(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in language_names), ) CONFIG_SCHEMA = cfgv.Map( 'Config', None, diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index a7eb4f45a..c9bc55b42 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -19,9 +19,9 @@ from pre_commit import color from pre_commit import git from pre_commit import output +from pre_commit.all_languages import languages from pre_commit.clientlib import load_config from pre_commit.hook import Hook -from pre_commit.languages.all import languages from pre_commit.repository import all_hooks from pre_commit.repository import install_hook_envs from pre_commit.staged_files_only import staged_files_only diff --git a/pre_commit/languages/helpers.py b/pre_commit/lang_base.py similarity index 75% rename from pre_commit/languages/helpers.py rename to pre_commit/lang_base.py index d1be409c8..6ba412f0e 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/lang_base.py @@ -7,8 +7,10 @@ import re import shlex from typing import Any +from typing import ContextManager from typing import Generator from typing import NoReturn +from typing import Protocol from typing import Sequence import pre_commit.constants as C @@ -22,6 +24,47 @@ SHIMS_RE = re.compile(r'[/\\]shims[/\\]') +class Language(Protocol): + # Use `None` for no installation / environment + @property + def ENVIRONMENT_DIR(self) -> str | None: ... + # return a value to replace `'default` for `language_version` + def get_default_version(self) -> str: ... + # return whether the environment is healthy (or should be rebuilt) + def health_check(self, prefix: Prefix, version: str) -> str | None: ... + + # install a repository for the given language and language_version + def install_environment( + self, + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], + ) -> None: + ... + + # modify the environment for hook execution + def in_env( + self, + prefix: Prefix, + version: str, + ) -> ContextManager[None]: + ... + + # execute a hook and return the exit code and output + def run_hook( + self, + prefix: Prefix, + entry: str, + args: Sequence[str], + file_args: Sequence[str], + *, + is_local: bool, + require_serial: bool, + color: bool, + ) -> tuple[int, bytes]: + ... + + def exe_exists(exe: str) -> bool: found = parse_shebang.find_executable(exe) if found is None: # exe exists @@ -45,7 +88,7 @@ def exe_exists(exe: str) -> bool: ) -def run_setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None: +def setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None: cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs) diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py deleted file mode 100644 index d952ae1ab..000000000 --- a/pre_commit/languages/all.py +++ /dev/null @@ -1,99 +0,0 @@ -from __future__ import annotations - -from typing import ContextManager -from typing import Protocol -from typing import Sequence - -from pre_commit.languages import conda -from pre_commit.languages import coursier -from pre_commit.languages import dart -from pre_commit.languages import docker -from pre_commit.languages import docker_image -from pre_commit.languages import dotnet -from pre_commit.languages import fail -from pre_commit.languages import golang -from pre_commit.languages import lua -from pre_commit.languages import node -from pre_commit.languages import perl -from pre_commit.languages import pygrep -from pre_commit.languages import python -from pre_commit.languages import r -from pre_commit.languages import ruby -from pre_commit.languages import rust -from pre_commit.languages import script -from pre_commit.languages import swift -from pre_commit.languages import system -from pre_commit.prefix import Prefix - - -class Language(Protocol): - # Use `None` for no installation / environment - @property - def ENVIRONMENT_DIR(self) -> str | None: ... - # return a value to replace `'default` for `language_version` - def get_default_version(self) -> str: ... - - # return whether the environment is healthy (or should be rebuilt) - def health_check( - self, - prefix: Prefix, - language_version: str, - ) -> str | None: - ... - - # install a repository for the given language and language_version - def install_environment( - self, - prefix: Prefix, - version: str, - additional_dependencies: Sequence[str], - ) -> None: - ... - - # modify the environment for hook execution - def in_env( - self, - prefix: Prefix, - version: str, - ) -> ContextManager[None]: - ... - - # execute a hook and return the exit code and output - def run_hook( - self, - prefix: Prefix, - entry: str, - args: Sequence[str], - file_args: Sequence[str], - *, - is_local: bool, - require_serial: bool, - color: bool, - ) -> tuple[int, bytes]: - ... - - -languages: dict[str, Language] = { - 'conda': conda, - 'coursier': coursier, - 'dart': dart, - 'docker': docker, - 'docker_image': docker_image, - 'dotnet': dotnet, - 'fail': fail, - 'golang': golang, - 'lua': lua, - 'node': node, - 'perl': perl, - 'pygrep': pygrep, - 'python': python, - 'r': r, - 'ruby': ruby, - 'rust': rust, - 'script': script, - 'swift': swift, - 'system': system, - # TODO: fully deprecate `python_venv` - 'python_venv': python, -} -all_languages = sorted(languages) diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index e2fb01969..05f1d2919 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -5,19 +5,19 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import SubstitutionT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'conda' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def get_env_patch(env: str) -> PatchesT: @@ -41,7 +41,7 @@ def get_env_patch(env: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -60,11 +60,11 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('conda', version) + lang_base.assert_version_default('conda', version) conda_exe = _conda_exe() - env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) cmd_output_b( conda_exe, 'env', 'create', '-p', env_dir, '--file', 'environment.yml', cwd=prefix.prefix_dir, diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 60757588d..9c5fbfe24 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -5,19 +5,19 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var from pre_commit.errors import FatalError -from pre_commit.languages import helpers from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix ENVIRONMENT_DIR = 'coursier' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def install_environment( @@ -25,7 +25,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('coursier', version) + lang_base.assert_version_default('coursier', version) # Support both possible executable names (either "cs" or "coursier") cs = find_executable('cs') or find_executable('coursier') @@ -35,12 +35,12 @@ def install_environment( 'executables in the application search path', ) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) def _install(*opts: str) -> None: assert cs is not None - helpers.run_setup_cmd(prefix, (cs, 'fetch', *opts)) - helpers.run_setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts)) + lang_base.setup_cmd(prefix, (cs, 'fetch', *opts)) + lang_base.setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts)) with in_env(prefix, version): channel = prefix.path('.pre-commit-channel') @@ -71,6 +71,6 @@ def get_env_patch(target_dir: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index e3c1c5855..e8539caa2 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -7,19 +7,19 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import win_exe from pre_commit.yaml import yaml_load ENVIRONMENT_DIR = 'dartenv' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def get_env_patch(venv: str) -> PatchesT: @@ -30,7 +30,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -40,9 +40,9 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('dart', version) + lang_base.assert_version_default('dart', version) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) bin_dir = os.path.join(envdir, 'bin') def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: @@ -51,10 +51,10 @@ def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: with open(prefix_p.path('pubspec.yaml')) as f: pubspec_contents = yaml_load(f) - helpers.run_setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env) + lang_base.setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env) for executable in pubspec_contents['executables']: - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix_p, ( 'dart', 'compile', 'exe', @@ -77,7 +77,7 @@ def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: else: dep_cmd = (dep,) - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix, ('dart', 'pub', 'cache', 'add', *dep_cmd), env={**os.environ, 'PUB_CACHE': dep_tmp}, diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 2212c5ccb..8e53ca9e3 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -5,16 +5,16 @@ import os from typing import Sequence -from pre_commit.languages import helpers +from pre_commit import lang_base from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'docker' PRE_COMMIT_LABEL = 'PRE_COMMIT' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -in_env = helpers.no_env # no special environment for docker +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +in_env = lang_base.no_env # no special environment for docker def _is_in_docker() -> bool: @@ -84,16 +84,16 @@ def build_docker_image( cmd += ('--pull',) # This must come last for old versions of docker. See #477 cmd += ('.',) - helpers.run_setup_cmd(prefix, cmd) + lang_base.setup_cmd(prefix, cmd) def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: # pragma: win32 no cover - helpers.assert_version_default('docker', version) - helpers.assert_no_additional_deps('docker', additional_dependencies) + lang_base.assert_version_default('docker', version) + lang_base.assert_no_additional_deps('docker', additional_dependencies) - directory = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + directory = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) # Docker doesn't really have relevant disk environment, but pre-commit # still needs to cleanup its state files on failure @@ -135,10 +135,10 @@ def run_hook( # automated cleanup of docker images. build_docker_image(prefix, pull=False) - entry_exe, *cmd_rest = helpers.hook_cmd(entry, args) + entry_exe, *cmd_rest = lang_base.hook_cmd(entry, args) entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix)) - return helpers.run_xargs( + return lang_base.run_xargs( (*docker_cmd(), *entry_tag, *cmd_rest), file_args, require_serial=require_serial, diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index 8e5f2c04c..26f006e4a 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -2,15 +2,15 @@ from typing import Sequence -from pre_commit.languages import helpers +from pre_commit import lang_base from pre_commit.languages.docker import docker_cmd from pre_commit.prefix import Prefix ENVIRONMENT_DIR = None -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -install_environment = helpers.no_install -in_env = helpers.no_env +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +install_environment = lang_base.no_install +in_env = lang_base.no_env def run_hook( @@ -23,8 +23,8 @@ def run_hook( require_serial: bool, color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - cmd = docker_cmd() + helpers.hook_cmd(entry, args) - return helpers.run_xargs( + cmd = docker_cmd() + lang_base.hook_cmd(entry, args) + return lang_base.run_xargs( cmd, file_args, require_serial=require_serial, diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index 3db2679d3..e9568f222 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -9,18 +9,18 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix ENVIRONMENT_DIR = 'dotnetenv' BIN_DIR = 'bin' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def get_env_patch(venv: str) -> PatchesT: @@ -31,7 +31,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -57,14 +57,14 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('dotnet', version) - helpers.assert_no_additional_deps('dotnet', additional_dependencies) + lang_base.assert_version_default('dotnet', version) + lang_base.assert_no_additional_deps('dotnet', additional_dependencies) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) build_dir = prefix.path('pre-commit-build') # Build & pack nupkg file - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix, ( 'dotnet', 'pack', @@ -99,7 +99,7 @@ def install_environment( # Install to bin dir with _nuget_config_no_sources() as nuget_config: - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix, ( 'dotnet', 'tool', 'install', diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index 33df067e4..a8ec6a53d 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -2,14 +2,14 @@ from typing import Sequence -from pre_commit.languages import helpers +from pre_commit import lang_base from pre_commit.prefix import Prefix ENVIRONMENT_DIR = None -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -install_environment = helpers.no_install -in_env = helpers.no_env +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +install_environment = lang_base.no_install +in_env = lang_base.no_env def run_hook( diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 3c4b652fa..bea91e9bd 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -19,17 +19,17 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output from pre_commit.util import rmtree ENVIRONMENT_DIR = 'golangenv' -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook _ARCH_ALIASES = { 'x86_64': 'amd64', @@ -60,7 +60,7 @@ def _open_archive(bio: IO[bytes]) -> ContextManager[ExtractAll]: @functools.lru_cache(maxsize=1) def get_default_version() -> str: - if helpers.exe_exists('go'): + if lang_base.exe_exists('go'): return 'system' else: return C.DEFAULT @@ -121,7 +121,7 @@ def _install_go(version: str, dest: str) -> None: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir, version)): yield @@ -131,7 +131,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) if version != 'system': _install_go(version, env_dir) @@ -149,9 +149,9 @@ def install_environment( os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'], )) - helpers.run_setup_cmd(prefix, ('go', 'install', './...'), env=env) + lang_base.setup_cmd(prefix, ('go', 'install', './...'), env=env) for dependency in additional_dependencies: - helpers.run_setup_cmd(prefix, ('go', 'install', dependency), env=env) + lang_base.setup_cmd(prefix, ('go', 'install', dependency), env=env) # save some disk space -- we don't need this after installation pkgdir = os.path.join(env_dir, 'pkg') diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index ffc40b505..12d066140 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -6,17 +6,17 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output ENVIRONMENT_DIR = 'lua_env' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def _get_lua_version() -> str: # pragma: win32 no cover @@ -45,7 +45,7 @@ def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -55,9 +55,9 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: # pragma: win32 no cover - helpers.assert_version_default('lua', version) + lang_base.assert_version_default('lua', version) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with in_env(prefix, version): # luarocks doesn't bootstrap a tree prior to installing # so ensure the directory exists. @@ -66,10 +66,10 @@ def install_environment( # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg for rockspec in prefix.star('.rockspec'): make_cmd = ('luarocks', '--tree', envdir, 'make', rockspec) - helpers.run_setup_cmd(prefix, make_cmd) + lang_base.setup_cmd(prefix, make_cmd) # luarocks can't install multiple packages at once # so install them individually. for dependency in additional_dependencies: cmd = ('luarocks', '--tree', envdir, 'install', dependency) - helpers.run_setup_cmd(prefix, cmd) + lang_base.setup_cmd(prefix, cmd) diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 9688da359..66d613637 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -8,11 +8,11 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.languages.python import bin_dir from pre_commit.prefix import Prefix from pre_commit.util import cmd_output @@ -20,7 +20,7 @@ from pre_commit.util import rmtree ENVIRONMENT_DIR = 'node_env' -run_hook = helpers.basic_run_hook +run_hook = lang_base.basic_run_hook @functools.lru_cache(maxsize=1) @@ -30,7 +30,7 @@ def get_default_version() -> str: return C.DEFAULT # if node is already installed, we can save a bunch of setup time by # using the installed version - elif all(helpers.exe_exists(exe) for exe in ('node', 'npm')): + elif all(lang_base.exe_exists(exe) for exe in ('node', 'npm')): return 'system' else: return C.DEFAULT @@ -60,13 +60,13 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield -def health_check(prefix: Prefix, language_version: str) -> str | None: - with in_env(prefix, language_version): +def health_check(prefix: Prefix, version: str) -> str | None: + with in_env(prefix, version): retcode, _, _ = cmd_output_b('node', '--version', check=False) if retcode != 0: # pragma: win32 no cover return f'`node --version` returned {retcode}' @@ -78,7 +78,7 @@ def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: assert prefix.exists('package.json') - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath if sys.platform == 'win32': # pragma: no cover @@ -96,13 +96,13 @@ def install_environment( 'npm', 'install', '--dev', '--prod', '--ignore-prepublish', '--no-progress', '--no-save', ) - helpers.run_setup_cmd(prefix, local_install_cmd) + lang_base.setup_cmd(prefix, local_install_cmd) _, pkg, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir) pkg = prefix.path(pkg.strip()) install = ('npm', 'install', '-g', pkg, *additional_dependencies) - helpers.run_setup_cmd(prefix, install) + lang_base.setup_cmd(prefix, install) # clean these up after installation if prefix.exists('node_modules'): # pragma: win32 no cover diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 2530c0ee1..2a7f16290 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -6,16 +6,16 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix ENVIRONMENT_DIR = 'perl_env' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def get_env_patch(venv: str) -> PatchesT: @@ -34,7 +34,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -42,9 +42,9 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: - helpers.assert_version_default('perl', version) + lang_base.assert_version_default('perl', version) with in_env(prefix, version): - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix, ('cpan', '-T', '.', *additional_dependencies), ) diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index f0eb9a959..ec55560b0 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -7,16 +7,16 @@ from typing import Pattern from typing import Sequence +from pre_commit import lang_base from pre_commit import output -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.xargs import xargs ENVIRONMENT_DIR = None -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -install_environment = helpers.no_install -in_env = helpers.no_env +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +install_environment = lang_base.no_install +in_env = lang_base.no_env def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int: diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index c373646bc..976674e2b 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -8,11 +8,11 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.parse_shebang import find_executable from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError @@ -21,7 +21,7 @@ from pre_commit.util import win_exe ENVIRONMENT_DIR = 'py_env' -run_hook = helpers.basic_run_hook +run_hook = lang_base.basic_run_hook @functools.lru_cache(maxsize=None) @@ -153,13 +153,13 @@ def norm_version(version: str) -> str | None: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield -def health_check(prefix: Prefix, language_version: str) -> str | None: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) +def health_check(prefix: Prefix, version: str) -> str | None: + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') # created with "old" virtualenv @@ -202,7 +202,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) venv_cmd = [sys.executable, '-mvirtualenv', envdir] python = norm_version(version) if python is not None: @@ -211,4 +211,4 @@ def install_environment( cmd_output_b(*venv_cmd, cwd='/') with in_env(prefix, version): - helpers.run_setup_cmd(prefix, install_cmd) + lang_base.setup_cmd(prefix, install_cmd) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index e2383658a..138a26e1e 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -7,18 +7,18 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b from pre_commit.util import win_exe ENVIRONMENT_DIR = 'renv' RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check def get_env_patch(venv: str) -> PatchesT: @@ -30,7 +30,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -93,7 +93,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) os.makedirs(env_dir, exist_ok=True) shutil.copy(prefix.path('renv.lock'), env_dir) shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) @@ -166,7 +166,7 @@ def run_hook( color: bool, ) -> tuple[int, bytes]: cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local) - return helpers.run_xargs( + return lang_base.run_xargs( cmd, file_args, require_serial=require_serial, diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 4416f7280..0ee0a857c 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -9,23 +9,23 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError from pre_commit.util import resource_bytesio ENVIRONMENT_DIR = 'rbenv' -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook @functools.lru_cache(maxsize=1) def get_default_version() -> str: - if all(helpers.exe_exists(exe) for exe in ('ruby', 'gem')): + if all(lang_base.exe_exists(exe) for exe in ('ruby', 'gem')): return 'system' else: return C.DEFAULT @@ -68,7 +68,7 @@ def get_env_patch( @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir, version)): yield @@ -83,7 +83,7 @@ def _install_rbenv( prefix: Prefix, version: str, ) -> None: # pragma: win32 no cover - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) _extract_resource('rbenv.tar.gz', prefix.path('.')) shutil.move(prefix.path('rbenv'), envdir) @@ -100,10 +100,10 @@ def _install_ruby( version: str, ) -> None: # pragma: win32 no cover try: - helpers.run_setup_cmd(prefix, ('rbenv', 'download', version)) + lang_base.setup_cmd(prefix, ('rbenv', 'download', version)) except CalledProcessError: # pragma: no cover (usually find with download) # Failed to download from mirror for some reason, build it instead - helpers.run_setup_cmd(prefix, ('rbenv', 'install', version)) + lang_base.setup_cmd(prefix, ('rbenv', 'install', version)) def install_environment( @@ -114,17 +114,17 @@ def install_environment( with in_env(prefix, version): # Need to call this before installing so rbenv's directories # are set up - helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-')) + lang_base.setup_cmd(prefix, ('rbenv', 'init', '-')) if version != C.DEFAULT: _install_ruby(prefix, version) # Need to call this after installing to set up the shims - helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) + lang_base.setup_cmd(prefix, ('rbenv', 'rehash')) with in_env(prefix, version): - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix, ('gem', 'build', *prefix.star('.gemspec')), ) - helpers.run_setup_cmd( + lang_base.setup_cmd( prefix, ( 'gem', 'install', diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 391fd8657..e98e0d02d 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -11,19 +11,19 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit import lang_base from pre_commit import parse_shebang from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b from pre_commit.util import make_executable from pre_commit.util import win_exe ENVIRONMENT_DIR = 'rustenv' -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook @functools.lru_cache(maxsize=1) @@ -63,7 +63,7 @@ def get_env_patch(target_dir: str, version: str) -> PatchesT: @contextlib.contextmanager def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir, version)): yield @@ -78,7 +78,7 @@ def _add_dependencies( crate = f'{name}@{spec or "*"}' crates.append(crate) - helpers.run_setup_cmd(prefix, ('cargo', 'add', *crates)) + lang_base.setup_cmd(prefix, ('cargo', 'add', *crates)) def install_rust_with_toolchain(toolchain: str) -> None: @@ -116,7 +116,7 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) # There are two cases where we might want to specify more dependencies: # as dependencies for the library being built, and as binary packages diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 08325f469..89a3ab2d6 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -2,14 +2,14 @@ from typing import Sequence -from pre_commit.languages import helpers +from pre_commit import lang_base from pre_commit.prefix import Prefix ENVIRONMENT_DIR = None -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -install_environment = helpers.no_install -in_env = helpers.no_env +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +install_environment = lang_base.no_install +in_env = lang_base.no_env def run_hook( @@ -22,9 +22,9 @@ def run_hook( require_serial: bool, color: bool, ) -> tuple[int, bytes]: - cmd = helpers.hook_cmd(entry, args) + cmd = lang_base.hook_cmd(entry, args) cmd = (prefix.path(cmd[0]), *cmd[1:]) - return helpers.run_xargs( + return lang_base.run_xargs( cmd, file_args, require_serial=require_serial, diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index c66ad5fb0..8250ab703 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -5,10 +5,10 @@ from typing import Generator from typing import Sequence +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b @@ -16,9 +16,9 @@ BUILD_CONFIG = 'release' ENVIRONMENT_DIR = 'swift_env' -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover @@ -28,7 +28,7 @@ def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @@ -36,9 +36,9 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: # pragma: win32 no cover - helpers.assert_version_default('swift', version) - helpers.assert_no_additional_deps('swift', additional_dependencies) - envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) + lang_base.assert_version_default('swift', version) + lang_base.assert_no_additional_deps('swift', additional_dependencies) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) # Build the swift package os.mkdir(envdir) diff --git a/pre_commit/languages/system.py b/pre_commit/languages/system.py index 204cad727..f6ad688fa 100644 --- a/pre_commit/languages/system.py +++ b/pre_commit/languages/system.py @@ -1,10 +1,10 @@ from __future__ import annotations -from pre_commit.languages import helpers +from pre_commit import lang_base ENVIRONMENT_DIR = None -get_default_version = helpers.basic_get_default_version -health_check = helpers.basic_health_check -install_environment = helpers.no_install -in_env = helpers.no_env -run_hook = helpers.basic_run_hook +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +install_environment = lang_base.no_install +in_env = lang_base.no_env +run_hook = lang_base.basic_run_hook diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 308e80c70..5183df47a 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -8,13 +8,13 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit.all_languages import languages from pre_commit.clientlib import load_manifest from pre_commit.clientlib import LOCAL from pre_commit.clientlib import META from pre_commit.clientlib import parse_version from pre_commit.hook import Hook -from pre_commit.languages.all import languages -from pre_commit.languages.helpers import environment_dir +from pre_commit.lang_base import environment_dir from pre_commit.prefix import Prefix from pre_commit.store import Store from pre_commit.util import clean_path_on_failure diff --git a/testing/language_helpers.py b/testing/language_helpers.py index 0964fbb44..5ab2af2a9 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -3,7 +3,7 @@ import os from typing import Sequence -from pre_commit.languages.all import Language +from pre_commit.lang_base import Language from pre_commit.prefix import Prefix diff --git a/tests/languages/all_test.py b/tests/all_languages_test.py similarity index 75% rename from tests/languages/all_test.py rename to tests/all_languages_test.py index 33b8925fb..98c912150 100644 --- a/tests/languages/all_test.py +++ b/tests/all_languages_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from pre_commit.languages.all import languages +from pre_commit.all_languages import languages def test_python_venv_is_an_alias_to_python(): diff --git a/tests/languages/helpers_test.py b/tests/lang_base_test.py similarity index 78% rename from tests/languages/helpers_test.py rename to tests/lang_base_test.py index c209e7e6d..89a64a1f2 100644 --- a/tests/languages/helpers_test.py +++ b/tests/lang_base_test.py @@ -8,8 +8,8 @@ import pytest import pre_commit.constants as C +from pre_commit import lang_base from pre_commit import parse_shebang -from pre_commit.languages import helpers from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError @@ -32,42 +32,42 @@ def fake_expanduser(pth): def test_exe_exists_does_not_exist(find_exe_mck, homedir_mck): find_exe_mck.return_value = None - assert helpers.exe_exists('ruby') is False + assert lang_base.exe_exists('ruby') is False def test_exe_exists_exists(find_exe_mck, homedir_mck): find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby') - assert helpers.exe_exists('ruby') is True + assert lang_base.exe_exists('ruby') is True def test_exe_exists_false_if_shim(find_exe_mck, homedir_mck): find_exe_mck.return_value = os.path.normpath('/foo/shims/ruby') - assert helpers.exe_exists('ruby') is False + assert lang_base.exe_exists('ruby') is False def test_exe_exists_false_if_homedir(find_exe_mck, homedir_mck): find_exe_mck.return_value = os.path.normpath('/home/me/somedir/ruby') - assert helpers.exe_exists('ruby') is False + assert lang_base.exe_exists('ruby') is False def test_exe_exists_commonpath_raises_ValueError(find_exe_mck, homedir_mck): find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby') with mock.patch.object(os.path, 'commonpath', side_effect=ValueError): - assert helpers.exe_exists('ruby') is True + assert lang_base.exe_exists('ruby') is True def test_exe_exists_true_when_homedir_is_slash(find_exe_mck): find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby') with mock.patch.object(os.path, 'expanduser', return_value=os.sep): - assert helpers.exe_exists('ruby') is True + assert lang_base.exe_exists('ruby') is True def test_basic_get_default_version(): - assert helpers.basic_get_default_version() == C.DEFAULT + assert lang_base.basic_get_default_version() == C.DEFAULT def test_basic_health_check(): - assert helpers.basic_health_check(Prefix('.'), 'default') is None + assert lang_base.basic_health_check(Prefix('.'), 'default') is None def test_failed_setup_command_does_not_unicode_error(): @@ -79,12 +79,12 @@ def test_failed_setup_command_does_not_unicode_error(): # an assertion that this does not raise `UnicodeError` with pytest.raises(CalledProcessError): - helpers.run_setup_cmd(Prefix('.'), (sys.executable, '-c', script)) + lang_base.setup_cmd(Prefix('.'), (sys.executable, '-c', script)) def test_assert_no_additional_deps(): with pytest.raises(AssertionError) as excinfo: - helpers.assert_no_additional_deps('lang', ['hmmm']) + lang_base.assert_no_additional_deps('lang', ['hmmm']) msg, = excinfo.value.args assert msg == ( 'for now, pre-commit does not support additional_dependencies for ' @@ -96,19 +96,19 @@ def test_assert_no_additional_deps(): def test_target_concurrency_normal(): with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): with mock.patch.dict(os.environ, {}, clear=True): - assert helpers.target_concurrency() == 123 + assert lang_base.target_concurrency() == 123 def test_target_concurrency_testing_env_var(): with mock.patch.dict( os.environ, {'PRE_COMMIT_NO_CONCURRENCY': '1'}, clear=True, ): - assert helpers.target_concurrency() == 1 + assert lang_base.target_concurrency() == 1 def test_target_concurrency_on_travis(): with mock.patch.dict(os.environ, {'TRAVIS': '1'}, clear=True): - assert helpers.target_concurrency() == 2 + assert lang_base.target_concurrency() == 2 def test_target_concurrency_cpu_count_not_implemented(): @@ -116,17 +116,17 @@ def test_target_concurrency_cpu_count_not_implemented(): multiprocessing, 'cpu_count', side_effect=NotImplementedError, ): with mock.patch.dict(os.environ, {}, clear=True): - assert helpers.target_concurrency() == 1 + assert lang_base.target_concurrency() == 1 def test_shuffled_is_deterministic(): seq = [str(i) for i in range(10)] expected = ['4', '0', '5', '1', '8', '6', '2', '3', '7', '9'] - assert helpers._shuffled(seq) == expected + assert lang_base._shuffled(seq) == expected def test_xargs_require_serial_is_not_shuffled(): - ret, out = helpers.run_xargs( + ret, out = lang_base.run_xargs( ('echo',), [str(i) for i in range(10)], require_serial=True, color=False, diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index f5f9985b8..ec5a87875 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -6,9 +6,9 @@ import re_assert import pre_commit.constants as C +from pre_commit import lang_base from pre_commit.envcontext import envcontext from pre_commit.languages import golang -from pre_commit.languages import helpers from pre_commit.store import _make_local_repo from testing.language_helpers import run_language @@ -18,7 +18,7 @@ @pytest.fixture def exe_exists_mck(): - with mock.patch.object(helpers, 'exe_exists') as mck: + with mock.patch.object(lang_base, 'exe_exists') as mck: yield mck diff --git a/tests/repository_test.py b/tests/repository_test.py index 332816d25..c04eb3796 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -10,12 +10,12 @@ import re_assert import pre_commit.constants as C +from pre_commit import lang_base +from pre_commit.all_languages import languages from pre_commit.clientlib import CONFIG_SCHEMA from pre_commit.clientlib import load_manifest from pre_commit.hook import Hook -from pre_commit.languages import helpers from pre_commit.languages import python -from pre_commit.languages.all import languages from pre_commit.prefix import Prefix from pre_commit.repository import _hook_installed from pre_commit.repository import all_hooks @@ -275,7 +275,7 @@ def test_repository_state_compatibility(tempdir_factory, store, v): config = make_config_from_repo(path) hook = _get_hook(config, store, 'foo') - envdir = helpers.environment_dir( + envdir = lang_base.environment_dir( hook.prefix, python.ENVIRONMENT_DIR, hook.language_version, @@ -327,7 +327,7 @@ class MyKeyboardInterrupt(KeyboardInterrupt): # raise as well. with pytest.raises(MyKeyboardInterrupt): with mock.patch.object( - helpers, 'run_setup_cmd', side_effect=MyKeyboardInterrupt, + lang_base, 'setup_cmd', side_effect=MyKeyboardInterrupt, ): with mock.patch.object( shutil, 'rmtree', side_effect=MyKeyboardInterrupt, @@ -336,7 +336,7 @@ class MyKeyboardInterrupt(KeyboardInterrupt): # Should have made an environment, however this environment is broken! hook, = hooks - envdir = helpers.environment_dir( + envdir = lang_base.environment_dir( hook.prefix, python.ENVIRONMENT_DIR, hook.language_version, @@ -359,7 +359,7 @@ def test_invalidated_virtualenv(tempdir_factory, store): hook = _get_hook(config, store, 'foo') # Simulate breaking of the virtualenv - envdir = helpers.environment_dir( + envdir = lang_base.environment_dir( hook.prefix, python.ENVIRONMENT_DIR, hook.language_version, From c3613b954a7155e6143b52cb3f3defcab82ba3ae Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Feb 2023 18:18:08 -0500 Subject: [PATCH 139/342] test things more directly to improve coverage --- tests/languages/python_test.py | 23 +++++++++++++++++++++++ tests/languages/script_test.py | 14 ++++++++++++++ tests/languages/system_test.py | 9 +++++++++ tests/repository_test.py | 16 ---------------- 4 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 tests/languages/script_test.py create mode 100644 tests/languages/system_test.py diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index 54fb98feb..8bb284eb9 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -12,6 +12,7 @@ from pre_commit.prefix import Prefix from pre_commit.util import make_executable from pre_commit.util import win_exe +from testing.language_helpers import run_language def test_read_pyvenv_cfg(tmpdir): @@ -210,3 +211,25 @@ def test_unhealthy_then_replaced(python_dir): os.replace(f'{py_exe}.tmp', py_exe) assert python.health_check(prefix, C.DEFAULT) is None + + +def test_language_versioned_python_hook(tmp_path): + setup_py = '''\ +from setuptools import setup +setup( + name='example', + py_modules=['mod'], + entry_points={'console_scripts': ['myexe=mod:main']}, +) +''' + tmp_path.joinpath('setup.py').write_text(setup_py) + tmp_path.joinpath('mod.py').write_text('def main(): print("ohai")') + + # we patch this to force virtualenv executing with `-p` since we can't + # reliably have multiple pythons available in CI + with mock.patch.object( + python, + '_sys_executable_matches', + return_value=False, + ): + assert run_language(tmp_path, python, 'myexe') == (0, b'ohai\n') diff --git a/tests/languages/script_test.py b/tests/languages/script_test.py new file mode 100644 index 000000000..a02f615a9 --- /dev/null +++ b/tests/languages/script_test.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from pre_commit.languages import script +from pre_commit.util import make_executable +from testing.language_helpers import run_language + + +def test_script_language(tmp_path): + exe = tmp_path.joinpath('main') + exe.write_text('#!/usr/bin/env bash\necho hello hello world\n') + make_executable(exe) + + expected = (0, b'hello hello world\n') + assert run_language(tmp_path, script, 'main') == expected diff --git a/tests/languages/system_test.py b/tests/languages/system_test.py new file mode 100644 index 000000000..dcd9cf1e0 --- /dev/null +++ b/tests/languages/system_test.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from pre_commit.languages import system +from testing.language_helpers import run_language + + +def test_system_language(tmp_path): + expected = (0, b'hello hello world\n') + assert run_language(tmp_path, system, 'echo hello hello world') == expected diff --git a/tests/repository_test.py b/tests/repository_test.py index 332816d25..e8b540708 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -143,22 +143,6 @@ def test_python_venv_deprecation(store, caplog): ) -def test_language_versioned_python_hook(tempdir_factory, store): - # we patch this force virtualenv executing with `-p` since we can't - # reliably have multiple pythons available in CI - with mock.patch.object( - python, - '_sys_executable_matches', - return_value=False, - ): - _test_hook_repo( - tempdir_factory, store, 'python3_hooks_repo', - 'python3-hook', - [os.devnull], - f'3\n[{os.devnull!r}]\nHello World\n'.encode(), - ) - - def test_system_hook_with_spaces(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'system_hook_with_spaces_repo', From 0cc2856883adc8910c522f4c8eb4ba2b397ebff0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 02:06:17 +0000 Subject: [PATCH 140/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.0.0 → v1.0.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.0...v1.0.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 023f4f683..ad8ffba73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.0 + rev: v1.0.1 hooks: - id: mypy additional_dependencies: [types-all] From 25b8ad752831dbbe9c5469760baffef16f4630f6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Feb 2023 21:32:32 -0500 Subject: [PATCH 141/342] improve unit test coverage of lang_base --- tests/lang_base_test.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/lang_base_test.py b/tests/lang_base_test.py index 89a64a1f2..a532b6a54 100644 --- a/tests/lang_base_test.py +++ b/tests/lang_base_test.py @@ -82,6 +82,21 @@ def test_failed_setup_command_does_not_unicode_error(): lang_base.setup_cmd(Prefix('.'), (sys.executable, '-c', script)) +def test_environment_dir(tmp_path): + ret = lang_base.environment_dir(Prefix(tmp_path), 'langenv', 'default') + assert ret == f'{tmp_path}{os.sep}langenv-default' + + +def test_assert_version_default(): + with pytest.raises(AssertionError) as excinfo: + lang_base.assert_version_default('lang', '1.2.3') + msg, = excinfo.value.args + assert msg == ( + 'for now, pre-commit requires system-installed lang -- ' + 'you selected `language_version: 1.2.3`' + ) + + def test_assert_no_additional_deps(): with pytest.raises(AssertionError) as excinfo: lang_base.assert_no_additional_deps('lang', ['hmmm']) @@ -93,6 +108,14 @@ def test_assert_no_additional_deps(): ) +def test_no_env_noop(tmp_path): + before = os.environ.copy() + with lang_base.no_env(Prefix(tmp_path), '1.2.3'): + inside = os.environ.copy() + after = os.environ.copy() + assert before == inside == after + + def test_target_concurrency_normal(): with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): with mock.patch.dict(os.environ, {}, clear=True): @@ -133,3 +156,18 @@ def test_xargs_require_serial_is_not_shuffled(): ) assert ret == 0 assert out.strip() == b'0 1 2 3 4 5 6 7 8 9' + + +def test_basic_run_hook(tmp_path): + ret, out = lang_base.basic_run_hook( + Prefix(tmp_path), + 'echo hi', + ['hello'], + ['file', 'file', 'file'], + is_local=False, + require_serial=False, + color=False, + ) + assert ret == 0 + out = out.replace(b'\r\n', b'\n') + assert out == b'hi hello file file file\n' From 8d84a7a2702b074a8b46f5e38af28bd576291251 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Feb 2023 21:45:04 -0500 Subject: [PATCH 142/342] resources_bytesio is only used by ruby --- pre_commit/languages/ruby.py | 9 +++++++-- pre_commit/util.py | 5 ----- tests/languages/ruby_test.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 0ee0a857c..76631f253 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -2,10 +2,12 @@ import contextlib import functools +import importlib.resources import os.path import shutil import tarfile from typing import Generator +from typing import IO from typing import Sequence import pre_commit.constants as C @@ -16,13 +18,16 @@ from pre_commit.envcontext import Var from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError -from pre_commit.util import resource_bytesio ENVIRONMENT_DIR = 'rbenv' health_check = lang_base.basic_health_check run_hook = lang_base.basic_run_hook +def _resource_bytesio(filename: str) -> IO[bytes]: + return importlib.resources.open_binary('pre_commit.resources', filename) + + @functools.lru_cache(maxsize=1) def get_default_version() -> str: if all(lang_base.exe_exists(exe) for exe in ('ruby', 'gem')): @@ -74,7 +79,7 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: def _extract_resource(filename: str, dest: str) -> None: - with resource_bytesio(filename) as bio: + with _resource_bytesio(filename) as bio: with tarfile.open(fileobj=bio) as tf: tf.extractall(dest) diff --git a/pre_commit/util.py b/pre_commit/util.py index 8ea48446a..3d448e318 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -12,7 +12,6 @@ from typing import Any from typing import Callable from typing import Generator -from typing import IO from pre_commit import parse_shebang @@ -36,10 +35,6 @@ def clean_path_on_failure(path: str) -> Generator[None, None, None]: raise -def resource_bytesio(filename: str) -> IO[bytes]: - return importlib.resources.open_binary('pre_commit.resources', filename) - - def resource_text(filename: str) -> str: return importlib.resources.read_text('pre_commit.resources', filename) diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index 9cfaad5d0..6397a4347 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -9,8 +9,8 @@ from pre_commit import parse_shebang from pre_commit.envcontext import envcontext from pre_commit.languages import ruby +from pre_commit.languages.ruby import _resource_bytesio from pre_commit.store import _make_local_repo -from pre_commit.util import resource_bytesio from testing.language_helpers import run_language from testing.util import cwd from testing.util import xfailif_windows @@ -40,7 +40,7 @@ def test_uses_system_if_both_gem_and_ruby_are_available(find_exe_mck): ('rbenv.tar.gz', 'ruby-build.tar.gz', 'ruby-download.tar.gz'), ) def test_archive_root_stat(filename): - with resource_bytesio(filename) as f: + with _resource_bytesio(filename) as f: with tarfile.open(fileobj=f) as tarf: root, _, _ = filename.partition('.') assert oct(tarf.getmember(root).mode) == '0o755' From d23990cc8b3b6040c0e5a7455ab7104cd60a5df4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Feb 2023 22:21:31 -0500 Subject: [PATCH 143/342] use run_language for repository_test --- testing/language_helpers.py | 6 ++++-- tests/repository_test.py | 31 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/testing/language_helpers.py b/testing/language_helpers.py index 5ab2af2a9..ead8dae27 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -16,6 +16,8 @@ def run_language( version: str | None = None, deps: Sequence[str] = (), is_local: bool = False, + require_serial: bool = True, + color: bool = False, ) -> tuple[int, bytes]: prefix = Prefix(str(path)) version = version or language.get_default_version() @@ -31,8 +33,8 @@ def run_language( args, file_args, is_local=is_local, - require_serial=True, - color=False, + require_serial=require_serial, + color=color, ) out = out.replace(b'\r\n', b'\n') return ret, out diff --git a/tests/repository_test.py b/tests/repository_test.py index 1af73e3aa..9e5d9d62f 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -25,25 +25,24 @@ from testing.fixtures import make_config_from_repo from testing.fixtures import make_repo from testing.fixtures import modify_manifest +from testing.language_helpers import run_language from testing.util import cwd from testing.util import get_resource_path -def _norm_out(b): - return b.replace(b'\r\n', b'\n') - - def _hook_run(hook, filenames, color): - with languages[hook.language].in_env(hook.prefix, hook.language_version): - return languages[hook.language].run_hook( - hook.prefix, - hook.entry, - hook.args, - filenames, - is_local=hook.src == 'local', - require_serial=hook.require_serial, - color=color, - ) + return run_language( + path=hook.prefix.prefix_dir, + language=languages[hook.language], + exe=hook.entry, + args=hook.args, + file_args=filenames, + version=hook.language_version, + deps=hook.additional_dependencies, + is_local=hook.src == 'local', + require_serial=hook.require_serial, + color=color, + ) def _get_hook_no_install(repo_config, store, hook_id): @@ -77,7 +76,7 @@ def _test_hook_repo( hook = _get_hook(config, store, hook_id) ret, out = _hook_run(hook, args, color=color) assert ret == expected_return_code - assert _norm_out(out) == expected + assert out == expected def test_python_hook(tempdir_factory, store): @@ -425,7 +424,7 @@ def test_local_python_repo(store, local_python_config): assert hook.language_version != C.DEFAULT ret, out = _hook_run(hook, ('filename',), color=False) assert ret == 0 - assert _norm_out(out) == b"['filename']\nHello World\n" + assert out == b"['filename']\nHello World\n" def test_default_language_version(store, local_python_config): From 9655158d938d0f49df0a0eedc5c0d166a45d591a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Feb 2023 17:00:05 -0500 Subject: [PATCH 144/342] test languages only when they are changed --- .github/actions/pre-test/action.yml | 31 ----------- .github/workflows/languages.yaml | 82 +++++++++++++++++++++++++++++ testing/languages | 79 +++++++++++++++++++++++++++ tox.ini | 4 +- 4 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/languages.yaml create mode 100755 testing/languages diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml index 42bbf00b5..9d1eb2de6 100644 --- a/.github/actions/pre-test/action.yml +++ b/.github/actions/pre-test/action.yml @@ -5,36 +5,5 @@ inputs: runs: using: composite steps: - - name: setup (windows) - shell: bash - if: runner.os == 'Windows' - run: | - set -x - - echo 'TEMP=C:\TEMP' >> "$GITHUB_ENV" - - echo "$CONDA\Scripts" >> "$GITHUB_PATH" - - echo 'C:\Strawberry\perl\bin' >> "$GITHUB_PATH" - echo 'C:\Strawberry\perl\site\bin' >> "$GITHUB_PATH" - echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" - - testing/get-coursier.sh - testing/get-dart.sh - - name: setup (linux) - shell: bash - if: runner.os == 'Linux' - run: | - set -x - - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - lua5.3 \ - liblua5.3-dev \ - luarocks - - testing/get-coursier.sh - testing/get-dart.sh - testing/get-swift.sh - uses: asottile/workflows/.github/actions/latest-git@v1.4.0 if: inputs.env == 'py38' && runner.os == 'Linux' diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml new file mode 100644 index 000000000..8bc8e712f --- /dev/null +++ b/.github/workflows/languages.yaml @@ -0,0 +1,82 @@ +name: languages + +on: + push: + branches: [main, test-me-*] + tags: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + vars: + runs-on: ubuntu-latest + outputs: + languages: ${{ steps.vars.outputs.languages }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: install deps + run: python -mpip install -e . -r requirements-dev.txt + - name: vars + run: testing/languages ${{ github.event_name == 'push' && '--all' || '' }} + id: vars + language: + needs: [vars] + runs-on: ${{ matrix.os }} + if: needs.vars.outputs.languages != '[]' + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.vars.outputs.languages) }} + steps: + - uses: asottile/workflows/.github/actions/fast-checkout@v1.4.0 + - uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - run: echo "$CONDA\Scripts" >> "$GITHUB_PATH" + shell: bash + if: matrix.os == 'windows-latest' && matrix.language == 'conda' + - run: testing/get-coursier.sh + shell: bash + if: matrix.language == 'coursier' + - run: testing/get-dart.sh + shell: bash + if: matrix.language == 'dart' + - run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + lua5.3 \ + liblua5.3-dev \ + luarocks + if: matrix.os == 'ubuntu-latest' && matrix.language == 'lua' + - run: | + echo 'C:\Strawberry\perl\bin' >> "$GITHUB_PATH" + echo 'C:\Strawberry\perl\site\bin' >> "$GITHUB_PATH" + echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" + shell: bash + if: matrix.os == 'windows-latest' && matrix.language == 'perl' + - run: testing/get-swift.sh + if: matrix.os == 'ubuntu-latest' && matrix.language == 'swift' + + - name: install deps + run: python -mpip install -e . -r requirements-dev.txt + - name: run tests + run: coverage run -m pytest tests/languages/${{ matrix.language }}_test.py + - name: check coverage + run: coverage report --include pre_commit/languages/${{ matrix.language }}.py,tests/languages/${{ matrix.language }}_test.py + collector: + needs: [language] + if: always() + runs-on: ubuntu-latest + steps: + - name: check for failures + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: echo job failed && exit 1 diff --git a/testing/languages b/testing/languages new file mode 100755 index 000000000..5e8fc9e4f --- /dev/null +++ b/testing/languages @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import concurrent.futures +import json +import os.path +import subprocess +import sys + +EXCLUDED = frozenset(( + ('windows-latest', 'docker'), + ('windows-latest', 'docker_image'), + ('windows-latest', 'lua'), + ('windows-latest', 'swift'), +)) + + +def _lang_files(lang: str) -> frozenset[str]: + prog = f'''\ +import json +import os.path +import sys + +import pre_commit.languages.{lang} +import tests.languages.{lang}_test + +modules = sorted( + os.path.relpath(v.__file__) + for k, v in sys.modules.items() + if k.startswith(('pre_commit.', 'tests.', 'testing.')) +) +print(json.dumps(modules)) +''' + out = json.loads(subprocess.check_output((sys.executable, '-c', prog))) + return frozenset(out) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument('--all', action='store_true') + args = parser.parse_args() + + langs = [ + os.path.splitext(fname)[0] + for fname in sorted(os.listdir('pre_commit/languages')) + if fname.endswith('.py') and fname != '__init__.py' + ] + + if not args.all: + with concurrent.futures.ThreadPoolExecutor(os.cpu_count()) as exe: + by_lang = { + lang: files + for lang, files in zip(langs, exe.map(_lang_files, langs)) + } + + diff_cmd = ('git', 'diff', '--name-only', 'origin/main...HEAD') + files = set(subprocess.check_output(diff_cmd).decode().splitlines()) + + langs = [ + lang + for lang, lang_files in by_lang.items() + if lang_files & files + ] + + matched = [ + {'os': os, 'language': lang} + for os in ('windows-latest', 'ubuntu-latest') + for lang in langs + if (os, lang) not in EXCLUDED + ] + + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f'languages={json.dumps(matched)}\n') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tox.ini b/tox.ini index a44f93d48..602679a60 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,8 @@ deps = -rrequirements-dev.txt passenv = * commands = coverage erase - coverage run -m pytest {posargs:tests} - coverage report + coverage run -m pytest {posargs:tests} --ignore=tests/languages + coverage report --omit=pre_commit/languages/*,tests/languages/* [testenv:pre-commit] skip_install = true From 08fa5ffc4353f0d9255281e4914cff2acc1c0859 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 21 Feb 2023 11:06:24 -0500 Subject: [PATCH 145/342] make a change to trigger the language tests --- pre_commit/lang_base.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pre_commit/lang_base.py b/pre_commit/lang_base.py index 6ba412f0e..9480c559f 100644 --- a/pre_commit/lang_base.py +++ b/pre_commit/lang_base.py @@ -43,12 +43,7 @@ def install_environment( ... # modify the environment for hook execution - def in_env( - self, - prefix: Prefix, - version: str, - ) -> ContextManager[None]: - ... + def in_env(self, prefix: Prefix, version: str) -> ContextManager[None]: ... # execute a hook and return the exit code and output def run_hook( From cddc9cff0f05a8d9e3ca126df03962574efe98e9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 21 Feb 2023 12:09:26 -0500 Subject: [PATCH 146/342] only treat exit code 1 as a successful diff --- pre_commit/staged_files_only.py | 23 ++++++++++----- tests/staged_files_only_test.py | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index 172fb20b1..881235656 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -7,6 +7,7 @@ from typing import Generator from pre_commit import git +from pre_commit.errors import FatalError from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b @@ -49,12 +50,16 @@ def _intent_to_add_cleared() -> Generator[None, None, None]: @contextlib.contextmanager def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: tree = cmd_output('git', 'write-tree')[1].strip() - retcode, diff_stdout_binary, _ = cmd_output_b( + diff_cmd = ( 'git', 'diff-index', '--ignore-submodules', '--binary', '--exit-code', '--no-color', '--no-ext-diff', tree, '--', - check=False, ) - if retcode and diff_stdout_binary.strip(): + retcode, diff_stdout, diff_stderr = cmd_output_b(*diff_cmd, check=False) + if retcode == 0: + # There weren't any staged files so we don't need to do anything + # special + yield + elif retcode == 1 and diff_stdout.strip(): patch_filename = f'patch{int(time.time())}-{os.getpid()}' patch_filename = os.path.join(patch_dir, patch_filename) logger.warning('Unstaged files detected.') @@ -62,7 +67,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: # Save the current unstaged changes as a patch os.makedirs(patch_dir, exist_ok=True) with open(patch_filename, 'wb') as patch_file: - patch_file.write(diff_stdout_binary) + patch_file.write(diff_stdout) # prevent recursive post-checkout hooks (#1418) no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1') @@ -86,10 +91,12 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: _git_apply(patch_filename) logger.info(f'Restored changes from {patch_filename}.') - else: - # There weren't any staged files so we don't need to do anything - # special - yield + else: # pragma: win32 no cover + # some error occurred while requesting the diff + e = CalledProcessError(retcode, diff_cmd, b'', diff_stderr) + raise FatalError( + f'pre-commit failed to diff -- perhaps due to permissions?\n\n{e}', + ) @contextlib.contextmanager diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py index a91f31519..50f146be4 100644 --- a/tests/staged_files_only_test.py +++ b/tests/staged_files_only_test.py @@ -1,12 +1,15 @@ from __future__ import annotations +import contextlib import itertools import os.path import shutil import pytest +import re_assert from pre_commit import git +from pre_commit.errors import FatalError from pre_commit.staged_files_only import staged_files_only from pre_commit.util import cmd_output from testing.auto_namedtuple import auto_namedtuple @@ -14,6 +17,7 @@ from testing.util import cwd from testing.util import get_resource_path from testing.util import git_commit +from testing.util import xfailif_windows FOO_CONTENTS = '\n'.join(('1', '2', '3', '4', '5', '6', '7', '8', '')) @@ -382,3 +386,51 @@ def test_intent_to_add(in_git_dir, patch_dir): with staged_files_only(patch_dir): assert_no_diff() assert git.intent_to_add_files() == ['foo'] + + +@contextlib.contextmanager +def _unreadable(f): + orig = os.stat(f).st_mode + os.chmod(f, 0o000) + try: + yield + finally: + os.chmod(f, orig) + + +@xfailif_windows # pragma: win32 no cover +def test_failed_diff_does_not_discard_changes(in_git_dir, patch_dir): + # stage 3 files + for i in range(3): + with open(str(i), 'w') as f: + f.write(str(i)) + cmd_output('git', 'add', '0', '1', '2') + + # modify all of their contents + for i in range(3): + with open(str(i), 'w') as f: + f.write('new contents') + + with _unreadable('1'): + with pytest.raises(FatalError) as excinfo: + with staged_files_only(patch_dir): + raise AssertionError('should have errored on enter') + + # the diff command failed to produce a diff of `1` + msg, = excinfo.value.args + re_assert.Matches( + r'^pre-commit failed to diff -- perhaps due to permissions\?\n\n' + r'command: .*\n' + r'return code: 128\n' + r'stdout: \(none\)\n' + r'stderr:\n' + r' error: open\("1"\): Permission denied\n' + r' fatal: cannot hash 1\n' + # TODO: not sure why there's weird whitespace here + r' $', + ).assert_matches(msg) + + # even though it errored, the unstaged changes should still be present + for i in range(3): + with open(str(i)) as f: + assert f.read() == 'new contents' From 4ded56efac790028557e8ad446937d00dff7f05d Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 21 Feb 2023 12:42:09 -0500 Subject: [PATCH 147/342] fix trailing whitespace in CalledProcessError output --- pre_commit/util.py | 2 +- tests/staged_files_only_test.py | 4 +--- tests/util_test.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pre_commit/util.py b/pre_commit/util.py index 3d448e318..ea0d4f525 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -62,7 +62,7 @@ def __init__( def __bytes__(self) -> bytes: def _indent_or_none(part: bytes | None) -> bytes: if part: - return b'\n ' + part.replace(b'\n', b'\n ') + return b'\n ' + part.replace(b'\n', b'\n ').rstrip() else: return b' (none)' diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py index 50f146be4..58dbe5ac6 100644 --- a/tests/staged_files_only_test.py +++ b/tests/staged_files_only_test.py @@ -425,9 +425,7 @@ def test_failed_diff_does_not_discard_changes(in_git_dir, patch_dir): r'stdout: \(none\)\n' r'stderr:\n' r' error: open\("1"\): Permission denied\n' - r' fatal: cannot hash 1\n' - # TODO: not sure why there's weird whitespace here - r' $', + r' fatal: cannot hash 1$', ).assert_matches(msg) # even though it errored, the unstaged changes should still be present diff --git a/tests/util_test.py b/tests/util_test.py index 310f8f58e..5b2621138 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -16,7 +16,7 @@ def test_CalledProcessError_str(): - error = CalledProcessError(1, ('exe',), b'output', b'errors') + error = CalledProcessError(1, ('exe',), b'output\n', b'errors\n') assert str(error) == ( "command: ('exe',)\n" 'return code: 1\n' From a631abdabf0fcc2bb31f85ae33dfdefb958fe03a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Feb 2023 20:31:14 -0500 Subject: [PATCH 148/342] remove sorting for repo key for additional_deps in other languages this order can matter (such as ruby) --- pre_commit/repository.py | 2 +- pre_commit/store.py | 2 +- tests/store_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 5183df47a..040f238f0 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -33,7 +33,7 @@ def _state_filename_v2(venv: str) -> str: def _state(additional_deps: Sequence[str]) -> object: - return {'additional_dependencies': sorted(additional_deps)} + return {'additional_dependencies': additional_deps} def _read_state(venv: str) -> object | None: diff --git a/pre_commit/store.py b/pre_commit/store.py index 6ddc7c481..487e3e798 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -125,7 +125,7 @@ def connect( @classmethod def db_repo_name(cls, repo: str, deps: Sequence[str]) -> str: if deps: - return f'{repo}:{",".join(sorted(deps))}' + return f'{repo}:{",".join(deps)}' else: return repo diff --git a/tests/store_test.py b/tests/store_test.py index 146eac416..eaab94000 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -180,7 +180,7 @@ def test_create_when_store_already_exists(store): def test_db_repo_name(store): assert store.db_repo_name('repo', ()) == 'repo' - assert store.db_repo_name('repo', ('b', 'a', 'c')) == 'repo:a,b,c' + assert store.db_repo_name('repo', ('b', 'a', 'c')) == 'repo:b,a,c' def test_local_resources_reflects_reality(): From 294590fd124484a786ba90423fa5d89536a6de98 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 22 Feb 2023 20:53:02 -0500 Subject: [PATCH 149/342] v3.1.0 --- CHANGELOG.md | 18 ++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0998da98b..8a4278120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +3.1.0 - 2023-02-22 +================== + +### Fixes +- Fix `dotnet` for `.sln`-based hooks for dotnet>=7.0.200. + - #2763 PR by @m-rsha. +- Prevent stashing when `diff` fails to execute. + - #2774 PR by @asottile. + - #2773 issue by @strubbly. +- Dependencies are no longer sorted in repository key. + - #2776 PR by @asottile. + +### Updating +- Deprecate `language: python_venv`. Use `language: python` instead. + - #2746 PR by @asottile. + - #2734 issue by @asottile. + + 3.0.4 - 2023-02-03 ================== diff --git a/setup.cfg b/setup.cfg index 56b856cad..d1f649fe7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.0.4 +version = 3.1.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 2700a7d62241d7bea52d5305b5bca88ad7072919 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 27 Feb 2023 20:49:22 -0500 Subject: [PATCH 150/342] set RUSTUP_HOME when using a non-system rust --- pre_commit/languages/rust.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index e98e0d02d..af5f483d3 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -142,10 +142,15 @@ def install_environment( else: packages_to_install.add((package,)) - with in_env(prefix, version): + with contextlib.ExitStack() as ctx: + ctx.enter_context(in_env(prefix, version)) + if version != 'system': install_rust_with_toolchain(_rust_toolchain(version)) + tmpdir = ctx.enter_context(tempfile.TemporaryDirectory()) + ctx.enter_context(envcontext((('RUSTUP_HOME', tmpdir),))) + if len(lib_deps) > 0: _add_dependencies(prefix, lib_deps) From 2822de9aa6284f2de1c5ff8d0884b38bc553afa5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 27 Feb 2023 21:07:23 -0500 Subject: [PATCH 151/342] v3.1.1 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4278120..cfcef4530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.1.1 - 2023-02-27 +================== + +### Fixes +- Fix `rust` with `language_version` and a non-writable host `RUSTUP_HOME`. + - pre-commit-ci/issues#173 by @Swiftb0y. + - #2788 by @asottile. + 3.1.0 - 2023-02-22 ================== diff --git a/setup.cfg b/setup.cfg index d1f649fe7..507c0ad13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.1.0 +version = 3.1.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 5ce4a549d3e0ee441698a13e431cf207bc3b611f Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Fri, 3 Mar 2023 20:16:09 -0600 Subject: [PATCH 152/342] prefer `sys.platform` over `os.name` when checking for windows OS --- pre_commit/languages/conda.py | 3 ++- pre_commit/languages/python.py | 4 ++-- pre_commit/util.py | 2 +- testing/util.py | 3 ++- tests/languages/python_test.py | 4 ++-- tests/parse_shebang_test.py | 2 +- tests/repository_test.py | 3 ++- tests/xargs_test.py | 2 +- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 05f1d2919..41c355e77 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -2,6 +2,7 @@ import contextlib import os +import sys from typing import Generator from typing import Sequence @@ -26,7 +27,7 @@ def get_env_patch(env: str) -> PatchesT: # $CONDA_PREFIX/Scripts and $CONDA_PREFIX. Whereas the latter only # seems to be used for python.exe. path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH')) - if os.name == 'nt': # pragma: no cover (platform specific) + if sys.platform == 'win32': # pragma: win32 cover path = (env, os.pathsep, *path) path = (os.path.join(env, 'Scripts'), os.pathsep, *path) path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path) diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 976674e2b..3ef343608 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -48,7 +48,7 @@ def _read_pyvenv_cfg(filename: str) -> dict[str, str]: def bin_dir(venv: str) -> str: """On windows there's a different directory for the virtualenv""" - bin_part = 'Scripts' if os.name == 'nt' else 'bin' + bin_part = 'Scripts' if sys.platform == 'win32' else 'bin' return os.path.join(venv, bin_part) @@ -137,7 +137,7 @@ def norm_version(version: str) -> str | None: elif _sys_executable_matches(version): # virtualenv defaults to our exe return None - if os.name == 'nt': # pragma: no cover (windows) + if sys.platform == 'win32': # pragma: no cover (windows) version_exec = _find_by_py_launcher(version) if version_exec: return version_exec diff --git a/pre_commit/util.py b/pre_commit/util.py index ea0d4f525..4f8e8357d 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -119,7 +119,7 @@ def cmd_output(*cmd: str, **kwargs: Any) -> tuple[int, str, str | None]: return returncode, stdout, stderr -if os.name != 'nt': # pragma: win32 no cover +if sys.platform != 'win32': # pragma: win32 no cover from os import openpty import termios diff --git a/testing/util.py b/testing/util.py index 7c68d0eee..0fee28265 100644 --- a/testing/util.py +++ b/testing/util.py @@ -3,6 +3,7 @@ import contextlib import os.path import subprocess +import sys import pytest @@ -30,7 +31,7 @@ def cmd_output_mocked_pre_commit_home( return ret, out.replace('\r\n', '\n'), None -xfailif_windows = pytest.mark.xfail(os.name == 'nt', reason='windows') +xfailif_windows = pytest.mark.xfail(sys.platform == 'win32', reason='windows') def run_opts( diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index 8bb284eb9..a4000b416 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -36,10 +36,10 @@ def test_read_pyvenv_cfg_non_utf8(tmpdir): def test_norm_version_expanduser(): home = os.path.expanduser('~') - if os.name == 'nt': # pragma: nt cover + if sys.platform == 'win32': # pragma: win32 cover path = r'~\python343' expected_path = fr'{home}\python343' - else: # pragma: nt no cover + else: # pragma: win32 no cover path = '~/.pyenv/versions/3.4.3/bin/python' expected_path = f'{home}/.pyenv/versions/3.4.3/bin/python' result = python.norm_version(path) diff --git a/tests/parse_shebang_test.py b/tests/parse_shebang_test.py index 2fcb29ee7..dd97ca5d8 100644 --- a/tests/parse_shebang_test.py +++ b/tests/parse_shebang_test.py @@ -94,7 +94,7 @@ def test_normexe_does_not_exist_sep(): assert excinfo.value.args == ('Executable `./i-dont-exist-lol` not found',) -@pytest.mark.xfail(os.name == 'nt', reason='posix only') +@pytest.mark.xfail(sys.platform == 'win32', reason='posix only') def test_normexe_not_executable(tmpdir): # pragma: win32 no cover tmpdir.join('exe').ensure() with tmpdir.as_cwd(), pytest.raises(OSError) as excinfo: diff --git a/tests/repository_test.py b/tests/repository_test.py index 9e5d9d62f..8fe6e02bb 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -2,6 +2,7 @@ import os.path import shutil +import sys from typing import Any from unittest import mock @@ -198,7 +199,7 @@ def test_intermixed_stdout_stderr(tempdir_factory, store): ) -@pytest.mark.xfail(os.name == 'nt', reason='ptys are posix-only') +@pytest.mark.xfail(sys.platform == 'win32', reason='ptys are posix-only') def test_output_isatty(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'stdout_stderr_repo', diff --git a/tests/xargs_test.py b/tests/xargs_test.py index 0530e50d1..7c41f98cd 100644 --- a/tests/xargs_test.py +++ b/tests/xargs_test.py @@ -187,7 +187,7 @@ def test_xargs_propagate_kwargs_to_cmd(): assert b'Pre commit is awesome' in stdout -@pytest.mark.xfail(os.name == 'nt', reason='posix only') +@pytest.mark.xfail(sys.platform == 'win32', reason='posix only') def test_xargs_color_true_makes_tty(): retcode, out = xargs.xargs( (sys.executable, '-c', 'import sys; print(sys.stdout.isatty())'), From 0616c0abf75d45d2bd793ced4b3bddc42b478662 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 02:52:32 +0000 Subject: [PATCH 153/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-autopep8: v2.0.1 → v2.0.2](https://github.com/pre-commit/mirrors-autopep8/compare/v2.0.1...v2.0.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad8ffba73..0aa2e9ea2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.1 + rev: v2.0.2 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From 63a180a935dc0096d23a65aa48b84498b57b8760 Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Sun, 5 Mar 2023 05:16:00 -0600 Subject: [PATCH 154/342] rewrite `args with spaces` test to not require python --- tests/repository_test.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/repository_test.py b/tests/repository_test.py index 8fe6e02bb..a6c58bc7d 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -1,6 +1,7 @@ from __future__ import annotations import os.path +import shlex import shutil import sys from typing import Any @@ -17,6 +18,7 @@ from pre_commit.clientlib import load_manifest from pre_commit.hook import Hook from pre_commit.languages import python +from pre_commit.languages import system from pre_commit.prefix import Prefix from pre_commit.repository import _hook_installed from pre_commit.repository import all_hooks @@ -99,22 +101,6 @@ def test_python_hook_default_version(tempdir_factory, store): test_python_hook(tempdir_factory, store) -def test_python_hook_args_with_spaces(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'python_hooks_repo', - 'foo', - [], - b"['i have spaces', 'and\"\\'quotes', '$and !this']\n" - b'Hello World\n', - config_kwargs={ - 'hooks': [{ - 'id': 'foo', - 'args': ['i have spaces', 'and"\'quotes', '$and !this'], - }], - }, - ) - - def test_python_hook_weird_setup_cfg(in_git_dir, tempdir_factory, store): in_git_dir.join('setup.cfg').write('[install]\ninstall_scripts=/usr/sbin') @@ -583,3 +569,14 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog): 'using language `system` which does not install an environment. ' 'Perhaps you meant to use a specific language?' ) + + +def test_args_with_spaces_and_quotes(tmp_path): + ret = run_language( + tmp_path, system, + f"{shlex.quote(sys.executable)} -c 'import sys; print(sys.argv[1:])'", + ('i have spaces', 'and"\'quotes', '$and !this'), + ) + + expected = b"['i have spaces', 'and\"\\'quotes', '$and !this']\n" + assert ret == (0, expected) From 8ab9747b339df5bfbf0b7ebb7ebd1885ad6baabd Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 9 Mar 2023 11:00:31 -0500 Subject: [PATCH 155/342] show 20 slowest durations in CI --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 602679a60..609c2fe18 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ deps = -rrequirements-dev.txt passenv = * commands = coverage erase - coverage run -m pytest {posargs:tests} --ignore=tests/languages + coverage run -m pytest {posargs:tests} --ignore=tests/languages --durations=20 coverage report --omit=pre_commit/languages/*,tests/languages/* [testenv:pre-commit] From e3e17a1617b90c081e043db32cb046ed010f2310 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 11 Mar 2023 14:15:49 -0500 Subject: [PATCH 156/342] make --hook-type and stages match --- pre_commit/clientlib.py | 67 ++++++++++++++++++++++++++++---- pre_commit/commands/hook_impl.py | 2 +- pre_commit/constants.py | 13 ------- pre_commit/main.py | 8 +++- testing/util.py | 2 +- tests/clientlib_test.py | 48 +++++++++++++++++++++++ tests/commands/hook_impl_test.py | 4 +- tests/commands/run_test.py | 14 +++---- tests/main_test.py | 6 +++ tests/repository_test.py | 25 +++++++----- 10 files changed, 147 insertions(+), 42 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 9ff38c6a2..cb7778bb2 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -6,6 +6,7 @@ import shlex import sys from typing import Any +from typing import NamedTuple from typing import Sequence import cfgv @@ -20,6 +21,20 @@ check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex) +HOOK_TYPES = ( + 'commit-msg', + 'post-checkout', + 'post-commit', + 'post-merge', + 'post-rewrite', + 'pre-commit', + 'pre-merge-commit', + 'pre-push', + 'prepare-commit-msg', +) +# `manual` is not invoked by any installed git hook. See #719 +STAGES = (*HOOK_TYPES, 'manual') + def check_type_tag(tag: str) -> None: if tag not in ALL_TAGS: @@ -43,6 +58,46 @@ def check_min_version(version: str) -> None: ) +_STAGES = { + 'commit': 'pre-commit', + 'merge-commit': 'pre-merge-commit', + 'push': 'pre-push', +} + + +def transform_stage(stage: str) -> str: + return _STAGES.get(stage, stage) + + +class StagesMigrationNoDefault(NamedTuple): + key: str + default: Sequence[str] + + def check(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + return + + val = dct[self.key] + cfgv.check_array(cfgv.check_any)(val) + + val = [transform_stage(v) for v in val] + cfgv.check_array(cfgv.check_one_of(STAGES))(val) + + def apply_default(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + return + dct[self.key] = [transform_stage(v) for v in dct[self.key]] + + def remove_default(self, dct: dict[str, Any]) -> None: + raise NotImplementedError + + +class StagesMigration(StagesMigrationNoDefault): + def apply_default(self, dct: dict[str, Any]) -> None: + dct.setdefault(self.key, self.default) + super().apply_default(dct) + + MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -70,7 +125,7 @@ def check_min_version(version: str) -> None: cfgv.Optional('log_file', cfgv.check_string, ''), cfgv.Optional('minimum_pre_commit_version', cfgv.check_string, '0'), cfgv.Optional('require_serial', cfgv.check_bool, False), - cfgv.Optional('stages', cfgv.check_array(cfgv.check_one_of(C.STAGES)), []), + StagesMigration('stages', []), cfgv.Optional('verbose', cfgv.check_bool, False), ) MANIFEST_SCHEMA = cfgv.Array(MANIFEST_HOOK_DICT) @@ -241,7 +296,9 @@ def check(self, dct: dict[str, Any]) -> None: cfgv.OptionalNoDefault(item.key, item.check_fn) for item in MANIFEST_HOOK_DICT.items if item.key != 'id' + if item.key != 'stages' ), + StagesMigrationNoDefault('stages', []), OptionalSensibleRegexAtHook('files', cfgv.check_string), OptionalSensibleRegexAtHook('exclude', cfgv.check_string), ) @@ -290,17 +347,13 @@ def check(self, dct: dict[str, Any]) -> None: cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)), cfgv.Optional( 'default_install_hook_types', - cfgv.check_array(cfgv.check_one_of(C.HOOK_TYPES)), + cfgv.check_array(cfgv.check_one_of(HOOK_TYPES)), ['pre-commit'], ), cfgv.OptionalRecurse( 'default_language_version', DEFAULT_LANGUAGE_VERSION, {}, ), - cfgv.Optional( - 'default_stages', - cfgv.check_array(cfgv.check_one_of(C.STAGES)), - C.STAGES, - ), + StagesMigration('default_stages', STAGES), cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('exclude', check_string_regex, '^$'), cfgv.Optional('fail_fast', cfgv.check_bool, False), diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index f5995e9ad..25d99c297 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -84,7 +84,7 @@ def _ns( ) -> argparse.Namespace: return argparse.Namespace( color=color, - hook_stage=hook_type.replace('pre-', ''), + hook_stage=hook_type, remote_branch=remote_branch, local_branch=local_branch, from_ref=from_ref, diff --git a/pre_commit/constants.py b/pre_commit/constants.py index 3f03ceed9..79a9bb692 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -10,17 +10,4 @@ VERSION = importlib.metadata.version('pre_commit') -# `manual` is not invoked by any installed git hook. See #719 -STAGES = ( - 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', - 'post-commit', 'manual', 'post-checkout', 'push', 'post-merge', - 'post-rewrite', -) - -HOOK_TYPES = ( - 'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg', - 'commit-msg', 'post-commit', 'post-checkout', 'post-merge', - 'post-rewrite', -) - DEFAULT = 'default' diff --git a/pre_commit/main.py b/pre_commit/main.py index 3915993ff..62d171e66 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -7,6 +7,7 @@ from typing import Sequence import pre_commit.constants as C +from pre_commit import clientlib from pre_commit import git from pre_commit.color import add_color_option from pre_commit.commands.autoupdate import autoupdate @@ -52,7 +53,7 @@ def _add_config_option(parser: argparse.ArgumentParser) -> None: def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: parser.add_argument( '-t', '--hook-type', - choices=C.HOOK_TYPES, action='append', dest='hook_types', + choices=clientlib.HOOK_TYPES, action='append', dest='hook_types', ) @@ -73,7 +74,10 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: help='When hooks fail, run `git diff` directly afterward.', ) parser.add_argument( - '--hook-stage', choices=C.STAGES, default='commit', + '--hook-stage', + choices=clientlib.STAGES, + type=clientlib.transform_stage, + default='pre-commit', help='The stage during which the hook is fired. One of %(choices)s', ) parser.add_argument( diff --git a/testing/util.py b/testing/util.py index 0fee28265..8e3934cf2 100644 --- a/testing/util.py +++ b/testing/util.py @@ -46,7 +46,7 @@ def run_opts( to_ref='', remote_name='', remote_url='', - hook_stage='commit', + hook_stage='pre-commit', show_diff_on_failure=False, commit_msg_filename='', prepare_commit_message_source='', diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index efb2aa84a..568b2e974 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -12,6 +12,7 @@ from pre_commit.clientlib import CONFIG_REPO_DICT from pre_commit.clientlib import CONFIG_SCHEMA from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION +from pre_commit.clientlib import MANIFEST_HOOK_DICT from pre_commit.clientlib import MANIFEST_SCHEMA from pre_commit.clientlib import META_HOOK_DICT from pre_commit.clientlib import OptionalSensibleRegexAtHook @@ -416,3 +417,50 @@ def test_warn_additional(schema): x for x in schema.items if isinstance(x, cfgv.WarnAdditionalKeys) ) assert allowed_keys == set(warn_additional.keys) + + +def test_stages_migration_for_default_stages(): + cfg = { + 'default_stages': ['commit-msg', 'push', 'commit', 'merge-commit'], + 'repos': [], + } + cfgv.validate(cfg, CONFIG_SCHEMA) + cfg = cfgv.apply_defaults(cfg, CONFIG_SCHEMA) + assert cfg['default_stages'] == [ + 'commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit', + ] + + +def test_manifest_stages_defaulting(): + dct = { + 'id': 'fake-hook', + 'name': 'fake-hook', + 'entry': 'fake-hook', + 'language': 'system', + 'stages': ['commit-msg', 'push', 'commit', 'merge-commit'], + } + cfgv.validate(dct, MANIFEST_HOOK_DICT) + dct = cfgv.apply_defaults(dct, MANIFEST_HOOK_DICT) + assert dct['stages'] == [ + 'commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit', + ] + + +def test_config_hook_stages_defaulting_missing(): + dct = {'id': 'fake-hook'} + cfgv.validate(dct, CONFIG_HOOK_DICT) + dct = cfgv.apply_defaults(dct, CONFIG_HOOK_DICT) + assert dct == {'id': 'fake-hook'} + + +def test_config_hook_stages_defaulting(): + dct = { + 'id': 'fake-hook', + 'stages': ['commit-msg', 'push', 'commit', 'merge-commit'], + } + cfgv.validate(dct, CONFIG_HOOK_DICT) + dct = cfgv.apply_defaults(dct, CONFIG_HOOK_DICT) + assert dct == { + 'id': 'fake-hook', + 'stages': ['commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit'], + } diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py index aa321dabc..169e1414b 100644 --- a/tests/commands/hook_impl_test.py +++ b/tests/commands/hook_impl_test.py @@ -142,7 +142,7 @@ def test_check_args_length_prepare_commit_msg_error(): def test_run_ns_pre_commit(): ns = hook_impl._run_ns('pre-commit', True, (), b'') assert ns is not None - assert ns.hook_stage == 'commit' + assert ns.hook_stage == 'pre-commit' assert ns.color is True @@ -245,7 +245,7 @@ def test_run_ns_pre_push_updating_branch(push_example): ns = hook_impl._run_ns('pre-push', False, args, stdin) assert ns is not None - assert ns.hook_stage == 'push' + assert ns.hook_stage == 'pre-push' assert ns.color is False assert ns.remote_name == 'origin' assert ns.remote_url == src diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index f1085d9bb..885b78d61 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -354,13 +354,13 @@ def test_show_diff_on_failure( ({'hook': 'bash_hook'}, (b'Bash hook', b'Passed'), 0, True), ( {'hook': 'nope'}, - (b'No hook with id `nope` in stage `commit`',), + (b'No hook with id `nope` in stage `pre-commit`',), 1, True, ), ( - {'hook': 'nope', 'hook_stage': 'push'}, - (b'No hook with id `nope` in stage `push`',), + {'hook': 'nope', 'hook_stage': 'pre-push'}, + (b'No hook with id `nope` in stage `pre-push`',), 1, True, ), @@ -818,7 +818,7 @@ def test_stages(cap_out, store, repo_with_passing_hook): 'language': 'pygrep', 'stages': [stage], } - for i, stage in enumerate(('commit', 'push', 'manual'), 1) + for i, stage in enumerate(('pre-commit', 'pre-push', 'manual'), 1) ], } add_config_to_repo(repo_with_passing_hook, config) @@ -833,8 +833,8 @@ def _run_for_stage(stage): assert printed.count(b'hook ') == 1 return printed - assert _run_for_stage('commit').startswith(b'hook 1...') - assert _run_for_stage('push').startswith(b'hook 2...') + assert _run_for_stage('pre-commit').startswith(b'hook 1...') + assert _run_for_stage('pre-push').startswith(b'hook 2...') assert _run_for_stage('manual').startswith(b'hook 3...') @@ -1173,7 +1173,7 @@ def test_args_hook_only(cap_out, store, repo_with_passing_hook): ), 'language': 'system', 'files': r'\.py$', - 'stages': ['commit'], + 'stages': ['pre-commit'], }, { 'id': 'do_not_commit', diff --git a/tests/main_test.py b/tests/main_test.py index 511592622..945349fa4 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -216,3 +216,9 @@ def test_expected_fatal_error_no_git_repo(in_tmpdir, cap_out, mock_store_dir): 'Is it installed, and are you in a Git repository directory?' ) assert cap_out_lines[-1] == f'Check the log at {log_file}' + + +def test_hook_stage_migration(mock_store_dir): + with mock.patch.object(main, 'run') as mck: + main.main(('run', '--hook-stage', 'commit')) + assert mck.call_args[0][2].hook_stage == 'pre-commit' diff --git a/tests/repository_test.py b/tests/repository_test.py index a6c58bc7d..903574ce3 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -417,7 +417,7 @@ def test_local_python_repo(store, local_python_config): def test_default_language_version(store, local_python_config): config: dict[str, Any] = { 'default_language_version': {'python': 'fake'}, - 'default_stages': ['commit'], + 'default_stages': ['pre-commit'], 'repos': [local_python_config], } @@ -434,18 +434,18 @@ def test_default_language_version(store, local_python_config): def test_default_stages(store, local_python_config): config: dict[str, Any] = { 'default_language_version': {'python': C.DEFAULT}, - 'default_stages': ['commit'], + 'default_stages': ['pre-commit'], 'repos': [local_python_config], } # `stages` was not set, should default hook, = all_hooks(config, store) - assert hook.stages == ['commit'] + assert hook.stages == ['pre-commit'] # `stages` is set, should not default - config['repos'][0]['hooks'][0]['stages'] = ['push'] + config['repos'][0]['hooks'][0]['stages'] = ['pre-push'] hook, = all_hooks(config, store) - assert hook.stages == ['push'] + assert hook.stages == ['pre-push'] def test_hook_id_not_present(tempdir_factory, store, caplog): @@ -513,11 +513,18 @@ def test_manifest_hooks(tempdir_factory, store): name='Bash hook', pass_filenames=True, require_serial=False, - stages=( - 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', - 'post-commit', 'manual', 'post-checkout', 'push', 'post-merge', + stages=[ + 'commit-msg', + 'post-checkout', + 'post-commit', + 'post-merge', 'post-rewrite', - ), + 'pre-commit', + 'pre-merge-commit', + 'pre-push', + 'prepare-commit-msg', + 'manual', + ], types=['file'], types_or=[], verbose=False, From f39154f69f864457595b21f00e81f0e989d05ddf Mon Sep 17 00:00:00 2001 From: Marcelo Galigniana Date: Fri, 27 Jan 2023 16:18:06 -0300 Subject: [PATCH 157/342] Add pre-rebase hook support --- pre_commit/clientlib.py | 1 + pre_commit/commands/hook_impl.py | 17 ++++++++++ pre_commit/commands/run.py | 5 +++ pre_commit/main.py | 11 +++++++ testing/util.py | 4 +++ tests/commands/hook_impl_test.py | 25 +++++++++++++++ tests/commands/install_uninstall_test.py | 40 ++++++++++++++++++++++++ tests/commands/run_test.py | 10 ++++++ tests/repository_test.py | 1 + 9 files changed, 114 insertions(+) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index cb7778bb2..d0651cae2 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -30,6 +30,7 @@ 'pre-commit', 'pre-merge-commit', 'pre-push', + 'pre-rebase', 'prepare-commit-msg', ) # `manual` is not invoked by any installed git hook. See #719 diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 25d99c297..dab2135d4 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -73,6 +73,8 @@ def _ns( local_branch: str | None = None, from_ref: str | None = None, to_ref: str | None = None, + pre_rebase_upstream: str | None = None, + pre_rebase_branch: str | None = None, remote_name: str | None = None, remote_url: str | None = None, commit_msg_filename: str | None = None, @@ -89,6 +91,8 @@ def _ns( local_branch=local_branch, from_ref=from_ref, to_ref=to_ref, + pre_rebase_upstream=pre_rebase_upstream, + pre_rebase_branch=pre_rebase_branch, remote_name=remote_name, remote_url=remote_url, commit_msg_filename=commit_msg_filename, @@ -185,6 +189,12 @@ def _check_args_length(hook_type: str, args: Sequence[str]) -> None: f'hook-impl for {hook_type} expected 1, 2, or 3 arguments ' f'but got {len(args)}: {args}', ) + elif hook_type == 'pre-rebase': + if len(args) < 1 or len(args) > 2: + raise SystemExit( + f'hook-impl for {hook_type} expected 1 or 2 arguments ' + f'but got {len(args)}: {args}', + ) elif hook_type in _EXPECTED_ARG_LENGTH_BY_HOOK: expected = _EXPECTED_ARG_LENGTH_BY_HOOK[hook_type] if len(args) != expected: @@ -231,6 +241,13 @@ def _run_ns( return _ns(hook_type, color, is_squash_merge=args[0]) elif hook_type == 'post-rewrite': return _ns(hook_type, color, rewrite_command=args[0]) + elif hook_type == 'pre-rebase' and len(args) == 1: + return _ns(hook_type, color, pre_rebase_upstream=args[0]) + elif hook_type == 'pre-rebase' and len(args) == 2: + return _ns( + hook_type, color, pre_rebase_upstream=args[0], + pre_rebase_branch=args[1], + ) else: raise AssertionError(f'unexpected hook type: {hook_type}') diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index c9bc55b42..c867799e8 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -254,6 +254,7 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]: # these hooks do not operate on files if args.hook_stage in { 'post-checkout', 'post-commit', 'post-merge', 'post-rewrite', + 'pre-rebase', }: return () elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}: @@ -389,6 +390,10 @@ def run( environ['PRE_COMMIT_FROM_REF'] = args.from_ref environ['PRE_COMMIT_TO_REF'] = args.to_ref + if args.pre_rebase_upstream and args.pre_rebase_branch: + environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] = args.pre_rebase_upstream + environ['PRE_COMMIT_PRE_REBASE_BRANCH'] = args.pre_rebase_branch + if ( args.remote_name and args.remote_url and args.remote_branch and args.local_branch diff --git a/pre_commit/main.py b/pre_commit/main.py index 62d171e66..9615c5e14 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -107,6 +107,17 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: 'now checked out.' ), ) + parser.add_argument( + '--pre-rebase-upstream', help=( + 'The upstream from which the series was forked.' + ), + ) + parser.add_argument( + '--pre-rebase-branch', help=( + 'The branch being rebased, and is not set when ' + 'rebasing the current branch.' + ), + ) parser.add_argument( '--commit-msg-filename', help='Filename to check when running during `commit-msg`', diff --git a/testing/util.py b/testing/util.py index 8e3934cf2..08d52cbc3 100644 --- a/testing/util.py +++ b/testing/util.py @@ -44,6 +44,8 @@ def run_opts( local_branch='', from_ref='', to_ref='', + pre_rebase_upstream='', + pre_rebase_branch='', remote_name='', remote_url='', hook_stage='pre-commit', @@ -67,6 +69,8 @@ def run_opts( local_branch=local_branch, from_ref=from_ref, to_ref=to_ref, + pre_rebase_upstream=pre_rebase_upstream, + pre_rebase_branch=pre_rebase_branch, remote_name=remote_name, remote_url=remote_url, hook_stage=hook_stage, diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py index 169e1414b..d757e85c0 100644 --- a/tests/commands/hook_impl_test.py +++ b/tests/commands/hook_impl_test.py @@ -100,6 +100,8 @@ def call(*_, **__): ('commit-msg', ['.git/COMMIT_EDITMSG']), ('post-commit', []), ('post-merge', ['1']), + ('pre-rebase', ['main', 'topic']), + ('pre-rebase', ['main']), ('post-checkout', ['old_head', 'new_head', '1']), ('post-rewrite', ['amend']), # multiple choices for commit-editmsg @@ -139,6 +141,13 @@ def test_check_args_length_prepare_commit_msg_error(): ) +def test_check_args_length_pre_rebase_error(): + with pytest.raises(SystemExit) as excinfo: + hook_impl._check_args_length('pre-rebase', []) + msg, = excinfo.value.args + assert msg == 'hook-impl for pre-rebase expected 1 or 2 arguments but got 0: []' # noqa: E501 + + def test_run_ns_pre_commit(): ns = hook_impl._run_ns('pre-commit', True, (), b'') assert ns is not None @@ -146,6 +155,22 @@ def test_run_ns_pre_commit(): assert ns.color is True +def test_run_ns_pre_rebase(): + ns = hook_impl._run_ns('pre-rebase', True, ('main', 'topic'), b'') + assert ns is not None + assert ns.hook_stage == 'pre-rebase' + assert ns.color is True + assert ns.pre_rebase_upstream == 'main' + assert ns.pre_rebase_branch == 'topic' + + ns = hook_impl._run_ns('pre-rebase', True, ('main',), b'') + assert ns is not None + assert ns.hook_stage == 'pre-rebase' + assert ns.color is True + assert ns.pre_rebase_upstream == 'main' + assert ns.pre_rebase_branch is None + + def test_run_ns_commit_msg(): ns = hook_impl._run_ns('commit-msg', False, ('.git/COMMIT_MSG',), b'') assert ns is not None diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index a1ecda867..8b0d3ece4 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -810,6 +810,46 @@ def test_post_merge_integration(tempdir_factory, store): assert os.path.exists('post-merge.tmp') +def test_pre_rebase_integration(tempdir_factory, store): + path = git_dir(tempdir_factory) + config = { + 'repos': [ + { + 'repo': 'local', + 'hooks': [{ + 'id': 'pre-rebase', + 'name': 'Pre rebase', + 'entry': 'touch pre-rebase.tmp', + 'language': 'system', + 'always_run': True, + 'verbose': True, + 'stages': ['pre-rebase'], + }], + }, + ], + } + write_config(path, config) + with cwd(path): + install(C.CONFIG_FILE, store, hook_types=['pre-rebase']) + open('foo', 'a').close() + cmd_output('git', 'add', '.') + git_commit() + + cmd_output('git', 'checkout', '-b', 'branch') + open('bar', 'a').close() + cmd_output('git', 'add', '.') + git_commit() + + cmd_output('git', 'checkout', 'master') + open('baz', 'a').close() + cmd_output('git', 'add', '.') + git_commit() + + cmd_output('git', 'checkout', 'branch') + cmd_output('git', 'rebase', 'master', 'branch') + assert os.path.exists('pre-rebase.tmp') + + def test_post_rewrite_integration(tempdir_factory, store): path = git_dir(tempdir_factory) config = { diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 885b78d61..dd15b94c5 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -563,6 +563,16 @@ def test_merge_conflict_resolved(cap_out, store, in_merge_conflict): assert msg in printed +def test_rebase(cap_out, store, repo_with_passing_hook): + args = run_opts(pre_rebase_upstream='master', pre_rebase_branch='topic') + environ: MutableMapping[str, str] = {} + ret, printed = _do_run( + cap_out, store, repo_with_passing_hook, args, environ, + ) + assert environ['PRE_COMMIT_PRE_REBASE_UPSTREAM'] == 'master' + assert environ['PRE_COMMIT_PRE_REBASE_BRANCH'] == 'topic' + + @pytest.mark.parametrize( ('hooks', 'expected'), ( diff --git a/tests/repository_test.py b/tests/repository_test.py index 903574ce3..045656689 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -522,6 +522,7 @@ def test_manifest_hooks(tempdir_factory, store): 'pre-commit', 'pre-merge-commit', 'pre-push', + 'pre-rebase', 'prepare-commit-msg', 'manual', ], From d3c0a66d23b5cebc060f48278ddb43bcc3384dfc Mon Sep 17 00:00:00 2001 From: marsha <46257533+m-rsha@users.noreply.github.com> Date: Sun, 12 Mar 2023 08:24:38 -0500 Subject: [PATCH 158/342] move slowest python-specific tests out of repository_test --- tests/languages/python_test.py | 51 ++++++++++++++++++++++++++++++++++ tests/repository_test.py | 29 ------------------- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index a4000b416..ab26e14e7 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -233,3 +233,54 @@ def test_language_versioned_python_hook(tmp_path): return_value=False, ): assert run_language(tmp_path, python, 'myexe') == (0, b'ohai\n') + + +def _make_hello_hello(tmp_path): + setup_py = '''\ +from setuptools import setup + +setup( + name='socks', + version='0.0.0', + py_modules=['socks'], + entry_points={'console_scripts': ['socks = socks:main']}, +) +''' + + main_py = '''\ +import sys + +def main(): + print(repr(sys.argv[1:])) + print('hello hello') + return 0 +''' + tmp_path.joinpath('setup.py').write_text(setup_py) + tmp_path.joinpath('socks.py').write_text(main_py) + + +def test_simple_python_hook(tmp_path): + _make_hello_hello(tmp_path) + + ret = run_language(tmp_path, python, 'socks', [os.devnull]) + assert ret == (0, f'[{os.devnull!r}]\nhello hello\n'.encode()) + + +def test_simple_python_hook_default_version(tmp_path): + # make sure that this continues to work for platforms where default + # language detection does not work + with mock.patch.object( + python, + 'get_default_version', + return_value=C.DEFAULT, + ): + test_simple_python_hook(tmp_path) + + +def test_python_hook_weird_setup_cfg(tmp_path): + _make_hello_hello(tmp_path) + setup_cfg = '[install]\ninstall_scripts=/usr/sbin' + tmp_path.joinpath('setup.cfg').write_text(setup_cfg) + + ret = run_language(tmp_path, python, 'socks', [os.devnull]) + assert ret == (0, f'[{os.devnull!r}]\nhello hello\n'.encode()) diff --git a/tests/repository_test.py b/tests/repository_test.py index 045656689..b8dde99b4 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -82,35 +82,6 @@ def _test_hook_repo( assert out == expected -def test_python_hook(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'python_hooks_repo', - 'foo', [os.devnull], - f'[{os.devnull!r}]\nHello World\n'.encode(), - ) - - -def test_python_hook_default_version(tempdir_factory, store): - # make sure that this continues to work for platforms where default - # language detection does not work - with mock.patch.object( - python, - 'get_default_version', - return_value=C.DEFAULT, - ): - test_python_hook(tempdir_factory, store) - - -def test_python_hook_weird_setup_cfg(in_git_dir, tempdir_factory, store): - in_git_dir.join('setup.cfg').write('[install]\ninstall_scripts=/usr/sbin') - - _test_hook_repo( - tempdir_factory, store, 'python_hooks_repo', - 'foo', [os.devnull], - f'[{os.devnull!r}]\nHello World\n'.encode(), - ) - - def test_python_venv_deprecation(store, caplog): config = { 'repo': 'local', From 7a7772fcdae8694107b9ab19cc93ff5fdc690755 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Mar 2023 03:19:10 +0000 Subject: [PATCH 159/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.1...v1.1.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0aa2e9ea2..cc96a7037 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.1 + rev: v1.1.1 hooks: - id: mypy additional_dependencies: [types-all] From a412e5492da8cdac6642b50cc3907db06edec109 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 17 Mar 2023 12:55:34 -0400 Subject: [PATCH 160/342] don't set CARGO_HOME in rust this adds a 270 MB registry cache in the output --- pre_commit/languages/rust.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index af5f483d3..a1f4dbe16 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -50,7 +50,6 @@ def _rust_toolchain(language_version: str) -> str: def get_env_patch(target_dir: str, version: str) -> PatchesT: return ( - ('CARGO_HOME', target_dir), ('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))), # Only set RUSTUP_TOOLCHAIN if we don't want use the system's default # toolchain From df2cada973da6ee689cbc8e323caccf5c00df92c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 17 Mar 2023 14:26:34 -0400 Subject: [PATCH 161/342] v3.2.0 --- CHANGELOG.md | 15 +++++++++++++++ setup.cfg | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfcef4530..f2466e207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +3.2.0 - 2023-03-17 +================== + +### Features +- Allow `pre-commit`, `pre-push`, and `pre-merge-commit` as `stages`. + - #2732 issue by @asottile. + - #2808 PR by @asottile. +- Add `pre-rebase` hook support. + - #2582 issue by @BrutalSimplicity. + - #2725 PR by @mgaligniana. + +### Fixes +- Remove bulky cargo cache from `language: rust` installs. + - #2820 PR by @asottile. + 3.1.1 - 2023-02-27 ================== diff --git a/setup.cfg b/setup.cfg index 507c0ad13..5b3d1560e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.1.1 +version = 3.2.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From ee71a9345ce96a78e011c9635a61abc332e38961 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 25 Mar 2023 13:06:22 -0400 Subject: [PATCH 162/342] set CARGO_HOME while executing rustup --- pre_commit/languages/rust.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index a1f4dbe16..7eec0e7d6 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -80,9 +80,9 @@ def _add_dependencies( lang_base.setup_cmd(prefix, ('cargo', 'add', *crates)) -def install_rust_with_toolchain(toolchain: str) -> None: +def install_rust_with_toolchain(toolchain: str, envdir: str) -> None: with tempfile.TemporaryDirectory() as rustup_dir: - with envcontext((('RUSTUP_HOME', rustup_dir),)): + with envcontext((('CARGO_HOME', envdir), ('RUSTUP_HOME', rustup_dir))): # acquire `rustup` if not present if parse_shebang.find_executable('rustup') is None: # We did not detect rustup and need to download it first. @@ -145,7 +145,7 @@ def install_environment( ctx.enter_context(in_env(prefix, version)) if version != 'system': - install_rust_with_toolchain(_rust_toolchain(version)) + install_rust_with_toolchain(_rust_toolchain(version), envdir) tmpdir = ctx.enter_context(tempfile.TemporaryDirectory()) ctx.enter_context(envcontext((('RUSTUP_HOME', tmpdir),))) From bb49560dc99a65608c8f9161dd71467af163c0d1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 25 Mar 2023 14:02:57 -0400 Subject: [PATCH 163/342] v3.2.1 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2466e207..dfb8f804f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.2.1 - 2023-03-25 +================== + +### Fixes +- Fix `language_version` for `language: rust` without global `rustup`. + - #2823 issue by @daschuer. + - #2827 PR by @asottile. + 3.2.0 - 2023-03-17 ================== diff --git a/setup.cfg b/setup.cfg index 5b3d1560e..350fe237a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.2.0 +version = 3.2.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 84f040f58a5710c2a9d6530f9d1e033657665f20 Mon Sep 17 00:00:00 2001 From: Eric DeLabar Date: Mon, 3 Apr 2023 15:50:55 -0400 Subject: [PATCH 164/342] fix #2235 --- pre_commit/languages/swift.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index 8250ab703..f16bb0451 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -44,7 +44,7 @@ def install_environment( os.mkdir(envdir) cmd_output_b( 'swift', 'build', - '-C', prefix.prefix_dir, + '--package-path', prefix.prefix_dir, '-c', BUILD_CONFIG, '--build-path', os.path.join(envdir, BUILD_DIR), ) From 5027592625f8df286dea831e84e7bf83021b7c1b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 3 Apr 2023 16:31:09 -0400 Subject: [PATCH 165/342] v3.2.2 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfb8f804f..efd96c796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.2.2 - 2023-04-03 +================== + +### Fixes +- Fix support for swift >= 5.8. + - #2836 PR by @edelabar. + - #2835 issue by @kgrobelny-intive. + 3.2.1 - 2023-03-25 ================== diff --git a/setup.cfg b/setup.cfg index 350fe237a..89e8e4ada 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.2.1 +version = 3.2.2 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From f5a716f1b1e2805444c199da7fbe24380100930c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 02:57:43 +0000 Subject: [PATCH 166/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.2.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.1.1...v1.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc96a7037..47d2630a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.2.0 hooks: - id: mypy additional_dependencies: [types-all] From cfcb88364e29a8855998502fed425c33d18c1252 Mon Sep 17 00:00:00 2001 From: Jamie Alessio Date: Tue, 18 Apr 2023 10:58:57 -0700 Subject: [PATCH 167/342] Upgrade to ruby-build v20230330 --- pre_commit/resources/ruby-build.tar.gz | Bin 76466 -> 75808 bytes testing/make-archives | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz index b6eacf59ba31c87032e76c2d1f39a87bb2b52489..19d467fdd2867742cdefe402a78489481ce9abba 100644 GIT binary patch literal 75808 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 aVnW&LI5^+h|Iiv<8JMAUgtgp` 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%0AtVc+vZ{$*a=M$ni`+p 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 Date: Tue, 25 Apr 2023 03:20:55 +0000 Subject: [PATCH 168/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.3.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47d2630a4..ffd305878 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade args: [--py38-plus] From 4727922b9316703d97fb12319f9f459c47f88cc5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Apr 2023 13:29:00 -0400 Subject: [PATCH 169/342] use blobless clone for faster autoupdate --- pre_commit/commands/autoupdate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 7ed6e7761..a43d7dd95 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -50,7 +50,12 @@ def update(self, tags_only: bool, freeze: bool) -> RevInfo: with tempfile.TemporaryDirectory() as tmp: git.init_repo(tmp, self.repo) cmd_output_b( - *git_cmd, 'fetch', 'origin', 'HEAD', '--tags', + *git_cmd, 'config', 'extensions.partialClone', 'true', + cwd=tmp, + ) + cmd_output_b( + *git_cmd, 'fetch', 'origin', 'HEAD', + '--quiet', '--filter=blob:none', '--tags', cwd=tmp, ) From e885f2e76ed09c178a7e16a235b76ee4f6e765f6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Apr 2023 15:11:14 -0400 Subject: [PATCH 170/342] use -C for git commands in autoupdate --- pre_commit/commands/autoupdate.py | 35 +++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index a43d7dd95..347599f63 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -34,44 +34,33 @@ def from_config(cls, config: dict[str, Any]) -> RevInfo: return cls(config['repo'], config['rev'], None) def update(self, tags_only: bool, freeze: bool) -> RevInfo: - git_cmd = ('git', *git.NO_FS_MONITOR) + with tempfile.TemporaryDirectory() as tmp: + _git = ('git', *git.NO_FS_MONITOR, '-C', tmp) - if tags_only: - tag_cmd = ( - *git_cmd, 'describe', - 'FETCH_HEAD', '--tags', '--abbrev=0', - ) - else: - tag_cmd = ( - *git_cmd, 'describe', - 'FETCH_HEAD', '--tags', '--exact', - ) + if tags_only: + tag_opt = '--abbrev=0' + else: + tag_opt = '--exact' + tag_cmd = (*_git, 'describe', 'FETCH_HEAD', '--tags', tag_opt) - with tempfile.TemporaryDirectory() as tmp: git.init_repo(tmp, self.repo) + cmd_output_b(*_git, 'config', 'extensions.partialClone', 'true') cmd_output_b( - *git_cmd, 'config', 'extensions.partialClone', 'true', - cwd=tmp, - ) - cmd_output_b( - *git_cmd, 'fetch', 'origin', 'HEAD', + *_git, 'fetch', 'origin', 'HEAD', '--quiet', '--filter=blob:none', '--tags', - cwd=tmp, ) try: - rev = cmd_output(*tag_cmd, cwd=tmp)[1].strip() + rev = cmd_output(*tag_cmd)[1].strip() except CalledProcessError: - cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD') - rev = cmd_output(*cmd, cwd=tmp)[1].strip() + rev = cmd_output(*_git, 'rev-parse', 'FETCH_HEAD')[1].strip() else: if tags_only: rev = git.get_best_candidate_tag(rev, tmp) frozen = None if freeze: - exact_rev_cmd = (*git_cmd, 'rev-parse', rev) - exact = cmd_output(*exact_rev_cmd, cwd=tmp)[1].strip() + exact = cmd_output(*_git, 'rev-parse', rev)[1].strip() if exact != rev: rev, frozen = exact, rev return self._replace(rev=rev, frozen=frozen) From 4f045cbc21fd3113c50fc9592666908692b1d24e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Apr 2023 15:19:20 -0400 Subject: [PATCH 171/342] perform autoupdate without Store contention --- pre_commit/commands/autoupdate.py | 34 ++++++----- pre_commit/main.py | 2 +- tests/commands/autoupdate_test.py | 96 +++++++++++++++---------------- tests/commands/gc_test.py | 5 +- 4 files changed, 71 insertions(+), 66 deletions(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 347599f63..71e5c99b8 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -16,7 +16,6 @@ from pre_commit.clientlib import LOCAL from pre_commit.clientlib import META from pre_commit.commands.migrate_config import migrate_config -from pre_commit.store import Store from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b @@ -27,11 +26,12 @@ class RevInfo(NamedTuple): repo: str rev: str - frozen: str | None + frozen: str | None = None + hook_ids: frozenset[str] = frozenset() @classmethod def from_config(cls, config: dict[str, Any]) -> RevInfo: - return cls(config['repo'], config['rev'], None) + return cls(config['repo'], config['rev']) def update(self, tags_only: bool, freeze: bool) -> RevInfo: with tempfile.TemporaryDirectory() as tmp: @@ -63,7 +63,19 @@ def update(self, tags_only: bool, freeze: bool) -> RevInfo: exact = cmd_output(*_git, 'rev-parse', rev)[1].strip() if exact != rev: rev, frozen = exact, rev - return self._replace(rev=rev, frozen=frozen) + + try: + cmd_output(*_git, 'checkout', rev, '--', C.MANIFEST_FILE) + except CalledProcessError: + pass # this will be caught by manifest validating code + try: + manifest = load_manifest(os.path.join(tmp, C.MANIFEST_FILE)) + except InvalidManifestError as e: + raise RepositoryCannotBeUpdatedError(str(e)) + else: + hook_ids = frozenset(hook['id'] for hook in manifest) + + return self._replace(rev=rev, frozen=frozen, hook_ids=hook_ids) class RepositoryCannotBeUpdatedError(RuntimeError): @@ -73,17 +85,10 @@ class RepositoryCannotBeUpdatedError(RuntimeError): def _check_hooks_still_exist_at_rev( repo_config: dict[str, Any], info: RevInfo, - store: Store, ) -> None: - try: - path = store.clone(repo_config['repo'], info.rev) - manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE)) - except InvalidManifestError as e: - raise RepositoryCannotBeUpdatedError(str(e)) - # See if any of our hooks were deleted with the new commits hooks = {hook['id'] for hook in repo_config['hooks']} - hooks_missing = hooks - {hook['id'] for hook in manifest} + hooks_missing = hooks - info.hook_ids if hooks_missing: raise RepositoryCannotBeUpdatedError( f'Cannot update because the update target is missing these ' @@ -139,7 +144,6 @@ def _write_new_config(path: str, rev_infos: list[RevInfo | None]) -> None: def autoupdate( config_file: str, - store: Store, tags_only: bool, freeze: bool, repos: Sequence[str] = (), @@ -161,9 +165,9 @@ def autoupdate( continue output.write(f'Updating {info.repo} ... ') - new_info = info.update(tags_only=tags_only, freeze=freeze) try: - _check_hooks_still_exist_at_rev(repo_config, new_info, store) + new_info = info.update(tags_only=tags_only, freeze=freeze) + _check_hooks_still_exist_at_rev(repo_config, new_info) except RepositoryCannotBeUpdatedError as error: output.write_line(error.args[0]) rev_infos.append(None) diff --git a/pre_commit/main.py b/pre_commit/main.py index 9615c5e14..402bc2e56 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -368,7 +368,7 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: if args.command == 'autoupdate': return autoupdate( - args.config, store, + args.config, tags_only=not args.bleeding_edge, freeze=args.freeze, repos=args.repos, diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py index 4bcb5d82a..71bd04446 100644 --- a/tests/commands/autoupdate_test.py +++ b/tests/commands/autoupdate_test.py @@ -67,7 +67,7 @@ def test_rev_info_from_config(): def test_rev_info_update_up_to_date_repo(up_to_date): config = make_config_from_repo(up_to_date) - info = RevInfo.from_config(config) + info = RevInfo.from_config(config)._replace(hook_ids=frozenset(('foo',))) new_info = info.update(tags_only=False, freeze=False) assert info == new_info @@ -139,7 +139,7 @@ def test_rev_info_update_does_not_freeze_if_already_sha(out_of_date): assert new_info.frozen is None -def test_autoupdate_up_to_date_repo(up_to_date, tmpdir, store): +def test_autoupdate_up_to_date_repo(up_to_date, tmpdir): contents = ( f'repos:\n' f'- repo: {up_to_date}\n' @@ -150,11 +150,11 @@ def test_autoupdate_up_to_date_repo(up_to_date, tmpdir, store): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write(contents) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 assert cfg.read() == contents -def test_autoupdate_old_revision_broken(tempdir_factory, in_tmpdir, store): +def test_autoupdate_old_revision_broken(tempdir_factory, in_tmpdir): """In $FUTURE_VERSION, hooks.yaml will no longer be supported. This asserts that when that day comes, pre-commit will be able to autoupdate despite not being able to read hooks.yaml in that repository. @@ -174,14 +174,14 @@ def test_autoupdate_old_revision_broken(tempdir_factory, in_tmpdir, store): write_config('.', config) with open(C.CONFIG_FILE) as f: before = f.read() - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) == 0 with open(C.CONFIG_FILE) as f: after = f.read() assert before != after assert update_rev in after -def test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store): +def test_autoupdate_out_of_date_repo(out_of_date, tmpdir): fmt = ( 'repos:\n' '- repo: {}\n' @@ -192,24 +192,24 @@ def test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write(fmt.format(out_of_date.path, out_of_date.original_rev)) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 assert cfg.read() == fmt.format(out_of_date.path, out_of_date.head_rev) -def test_autoupdate_with_core_useBuiltinFSMonitor(out_of_date, tmpdir, store): +def test_autoupdate_with_core_useBuiltinFSMonitor(out_of_date, tmpdir): # force the setting on "globally" for git home = tmpdir.join('fakehome').ensure_dir() home.join('.gitconfig').write('[core]\nuseBuiltinFSMonitor = true\n') with envcontext.envcontext((('HOME', str(home)),)): - test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store) + test_autoupdate_out_of_date_repo(out_of_date, tmpdir) -def test_autoupdate_pure_yaml(out_of_date, tmpdir, store): +def test_autoupdate_pure_yaml(out_of_date, tmpdir): with mock.patch.object(yaml, 'Dumper', yaml.yaml.SafeDumper): - test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store) + test_autoupdate_out_of_date_repo(out_of_date, tmpdir) -def test_autoupdate_only_one_to_update(up_to_date, out_of_date, tmpdir, store): +def test_autoupdate_only_one_to_update(up_to_date, out_of_date, tmpdir): fmt = ( 'repos:\n' '- repo: {}\n' @@ -228,7 +228,7 @@ def test_autoupdate_only_one_to_update(up_to_date, out_of_date, tmpdir, store): ) cfg.write(before) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 assert cfg.read() == fmt.format( up_to_date, git.head_rev(up_to_date), out_of_date.path, out_of_date.head_rev, @@ -236,7 +236,7 @@ def test_autoupdate_only_one_to_update(up_to_date, out_of_date, tmpdir, store): def test_autoupdate_out_of_date_repo_with_correct_repo_name( - out_of_date, in_tmpdir, store, + out_of_date, in_tmpdir, ): stale_config = make_config_from_repo( out_of_date.path, rev=out_of_date.original_rev, check=False, @@ -249,7 +249,7 @@ def test_autoupdate_out_of_date_repo_with_correct_repo_name( before = f.read() repo_name = f'file://{out_of_date.path}' ret = autoupdate( - C.CONFIG_FILE, store, freeze=False, tags_only=False, + C.CONFIG_FILE, freeze=False, tags_only=False, repos=(repo_name,), ) with open(C.CONFIG_FILE) as f: @@ -261,7 +261,7 @@ def test_autoupdate_out_of_date_repo_with_correct_repo_name( def test_autoupdate_out_of_date_repo_with_wrong_repo_name( - out_of_date, in_tmpdir, store, + out_of_date, in_tmpdir, ): config = make_config_from_repo( out_of_date.path, rev=out_of_date.original_rev, check=False, @@ -272,7 +272,7 @@ def test_autoupdate_out_of_date_repo_with_wrong_repo_name( before = f.read() # It will not update it, because the name doesn't match ret = autoupdate( - C.CONFIG_FILE, store, freeze=False, tags_only=False, + C.CONFIG_FILE, freeze=False, tags_only=False, repos=('dne',), ) with open(C.CONFIG_FILE) as f: @@ -281,7 +281,7 @@ def test_autoupdate_out_of_date_repo_with_wrong_repo_name( assert before == after -def test_does_not_reformat(tmpdir, out_of_date, store): +def test_does_not_reformat(tmpdir, out_of_date): fmt = ( 'repos:\n' '- repo: {}\n' @@ -294,12 +294,12 @@ def test_does_not_reformat(tmpdir, out_of_date, store): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write(fmt.format(out_of_date.path, out_of_date.original_rev)) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 expected = fmt.format(out_of_date.path, out_of_date.head_rev) assert cfg.read() == expected -def test_does_not_change_mixed_endlines_read(up_to_date, tmpdir, store): +def test_does_not_change_mixed_endlines_read(up_to_date, tmpdir): fmt = ( 'repos:\n' '- repo: {}\n' @@ -314,11 +314,11 @@ def test_does_not_change_mixed_endlines_read(up_to_date, tmpdir, store): expected = fmt.format(up_to_date, git.head_rev(up_to_date)).encode() cfg.write_binary(expected) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 assert cfg.read_binary() == expected -def test_does_not_change_mixed_endlines_write(tmpdir, out_of_date, store): +def test_does_not_change_mixed_endlines_write(tmpdir, out_of_date): fmt = ( 'repos:\n' '- repo: {}\n' @@ -333,12 +333,12 @@ def test_does_not_change_mixed_endlines_write(tmpdir, out_of_date, store): fmt.format(out_of_date.path, out_of_date.original_rev).encode(), ) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 expected = fmt.format(out_of_date.path, out_of_date.head_rev).encode() assert cfg.read_binary() == expected -def test_loses_formatting_when_not_detectable(out_of_date, store, tmpdir): +def test_loses_formatting_when_not_detectable(out_of_date, tmpdir): """A best-effort attempt is made at updating rev without rewriting formatting. When the original formatting cannot be detected, this is abandoned. @@ -359,7 +359,7 @@ def test_loses_formatting_when_not_detectable(out_of_date, store, tmpdir): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write(config) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 expected = ( f'repos:\n' f'- repo: {out_of_date.path}\n' @@ -370,43 +370,43 @@ def test_loses_formatting_when_not_detectable(out_of_date, store, tmpdir): assert cfg.read() == expected -def test_autoupdate_tagged_repo(tagged, in_tmpdir, store): +def test_autoupdate_tagged_repo(tagged, in_tmpdir): config = make_config_from_repo(tagged.path, rev=tagged.original_rev) write_config('.', config) - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) == 0 with open(C.CONFIG_FILE) as f: assert 'v1.2.3' in f.read() -def test_autoupdate_freeze(tagged, in_tmpdir, store): +def test_autoupdate_freeze(tagged, in_tmpdir): config = make_config_from_repo(tagged.path, rev=tagged.original_rev) write_config('.', config) - assert autoupdate(C.CONFIG_FILE, store, freeze=True, tags_only=False) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=True, tags_only=False) == 0 with open(C.CONFIG_FILE) as f: expected = f'rev: {tagged.head_rev} # frozen: v1.2.3' assert expected in f.read() # if we un-freeze it should remove the frozen comment - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) == 0 with open(C.CONFIG_FILE) as f: assert 'rev: v1.2.3\n' in f.read() -def test_autoupdate_tags_only(tagged, in_tmpdir, store): +def test_autoupdate_tags_only(tagged, in_tmpdir): # add some commits after the tag git_commit(cwd=tagged.path) config = make_config_from_repo(tagged.path, rev=tagged.original_rev) write_config('.', config) - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=True) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=True) == 0 with open(C.CONFIG_FILE) as f: assert 'v1.2.3' in f.read() -def test_autoupdate_latest_no_config(out_of_date, in_tmpdir, store): +def test_autoupdate_latest_no_config(out_of_date, in_tmpdir): config = make_config_from_repo( out_of_date.path, rev=out_of_date.original_rev, ) @@ -415,12 +415,12 @@ def test_autoupdate_latest_no_config(out_of_date, in_tmpdir, store): cmd_output('git', 'rm', '-r', ':/', cwd=out_of_date.path) git_commit(cwd=out_of_date.path) - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 1 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) == 1 with open(C.CONFIG_FILE) as f: assert out_of_date.original_rev in f.read() -def test_hook_disppearing_repo_raises(hook_disappearing, store): +def test_hook_disppearing_repo_raises(hook_disappearing): config = make_config_from_repo( hook_disappearing.path, rev=hook_disappearing.original_rev, @@ -428,10 +428,10 @@ def test_hook_disppearing_repo_raises(hook_disappearing, store): ) info = RevInfo.from_config(config).update(tags_only=False, freeze=False) with pytest.raises(RepositoryCannotBeUpdatedError): - _check_hooks_still_exist_at_rev(config, info, store) + _check_hooks_still_exist_at_rev(config, info) -def test_autoupdate_hook_disappearing_repo(hook_disappearing, tmpdir, store): +def test_autoupdate_hook_disappearing_repo(hook_disappearing, tmpdir): contents = ( f'repos:\n' f'- repo: {hook_disappearing.path}\n' @@ -442,21 +442,21 @@ def test_autoupdate_hook_disappearing_repo(hook_disappearing, tmpdir, store): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write(contents) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 1 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 1 assert cfg.read() == contents -def test_autoupdate_local_hooks(in_git_dir, store): +def test_autoupdate_local_hooks(in_git_dir): config = sample_local_config() add_config_to_repo('.', config) - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) == 0 new_config_written = read_config('.') assert len(new_config_written['repos']) == 1 assert new_config_written['repos'][0] == config def test_autoupdate_local_hooks_with_out_of_date_repo( - out_of_date, in_tmpdir, store, + out_of_date, in_tmpdir, ): stale_config = make_config_from_repo( out_of_date.path, rev=out_of_date.original_rev, check=False, @@ -464,13 +464,13 @@ def test_autoupdate_local_hooks_with_out_of_date_repo( local_config = sample_local_config() config = {'repos': [local_config, stale_config]} write_config('.', config) - assert autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) == 0 + assert autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) == 0 new_config_written = read_config('.') assert len(new_config_written['repos']) == 2 assert new_config_written['repos'][0] == local_config -def test_autoupdate_meta_hooks(tmpdir, store): +def test_autoupdate_meta_hooks(tmpdir): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write( 'repos:\n' @@ -478,7 +478,7 @@ def test_autoupdate_meta_hooks(tmpdir, store): ' hooks:\n' ' - id: check-useless-excludes\n', ) - assert autoupdate(str(cfg), store, freeze=False, tags_only=True) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=True) == 0 assert cfg.read() == ( 'repos:\n' '- repo: meta\n' @@ -487,7 +487,7 @@ def test_autoupdate_meta_hooks(tmpdir, store): ) -def test_updates_old_format_to_new_format(tmpdir, capsys, store): +def test_updates_old_format_to_new_format(tmpdir, capsys): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write( '- repo: local\n' @@ -497,7 +497,7 @@ def test_updates_old_format_to_new_format(tmpdir, capsys, store): ' entry: ./bin/foo.sh\n' ' language: script\n', ) - assert autoupdate(str(cfg), store, freeze=False, tags_only=True) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=True) == 0 contents = cfg.read() assert contents == ( 'repos:\n' @@ -512,7 +512,7 @@ def test_updates_old_format_to_new_format(tmpdir, capsys, store): assert out == 'Configuration has been migrated.\n' -def test_maintains_rev_quoting_style(tmpdir, out_of_date, store): +def test_maintains_rev_quoting_style(tmpdir, out_of_date): fmt = ( 'repos:\n' '- repo: {path}\n' @@ -527,6 +527,6 @@ def test_maintains_rev_quoting_style(tmpdir, out_of_date, store): cfg = tmpdir.join(C.CONFIG_FILE) cfg.write(fmt.format(path=out_of_date.path, rev=out_of_date.original_rev)) - assert autoupdate(str(cfg), store, freeze=False, tags_only=False) == 0 + assert autoupdate(str(cfg), freeze=False, tags_only=False) == 0 expected = fmt.format(path=out_of_date.path, rev=out_of_date.head_rev) assert cfg.read() == expected diff --git a/tests/commands/gc_test.py b/tests/commands/gc_test.py index c128e9393..95113ed5c 100644 --- a/tests/commands/gc_test.py +++ b/tests/commands/gc_test.py @@ -43,8 +43,9 @@ def test_gc(tempdir_factory, store, in_git_dir, cap_out): store.mark_config_used(C.CONFIG_FILE) # update will clone both the old and new repo, making the old one gc-able - install_hooks(C.CONFIG_FILE, store) - assert not autoupdate(C.CONFIG_FILE, store, freeze=False, tags_only=False) + assert not install_hooks(C.CONFIG_FILE, store) + assert not autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False) + assert not install_hooks(C.CONFIG_FILE, store) assert _config_count(store) == 1 assert _repo_count(store) == 2 From ddbee32ad0722a0bc216bc29ee29a1885454bd78 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 29 Apr 2023 15:05:17 -0400 Subject: [PATCH 172/342] add --jobs option to autoupdate --- pre_commit/commands/autoupdate.py | 92 +++++++++++++++++++------------ pre_commit/lang_base.py | 10 +--- pre_commit/main.py | 7 ++- pre_commit/xargs.py | 8 +++ 4 files changed, 73 insertions(+), 44 deletions(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 71e5c99b8..178648108 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -1,5 +1,6 @@ from __future__ import annotations +import concurrent.futures import os.path import re import tempfile @@ -10,6 +11,7 @@ import pre_commit.constants as C from pre_commit import git from pre_commit import output +from pre_commit import xargs from pre_commit.clientlib import InvalidManifestError from pre_commit.clientlib import load_config from pre_commit.clientlib import load_manifest @@ -71,7 +73,7 @@ def update(self, tags_only: bool, freeze: bool) -> RevInfo: try: manifest = load_manifest(os.path.join(tmp, C.MANIFEST_FILE)) except InvalidManifestError as e: - raise RepositoryCannotBeUpdatedError(str(e)) + raise RepositoryCannotBeUpdatedError(f'[{self.repo}] {e}') else: hook_ids = frozenset(hook['id'] for hook in manifest) @@ -91,11 +93,24 @@ def _check_hooks_still_exist_at_rev( hooks_missing = hooks - info.hook_ids if hooks_missing: raise RepositoryCannotBeUpdatedError( - f'Cannot update because the update target is missing these ' - f'hooks:\n{", ".join(sorted(hooks_missing))}', + f'[{info.repo}] Cannot update because the update target is ' + f'missing these hooks: {", ".join(sorted(hooks_missing))}', ) +def _update_one( + i: int, + repo: dict[str, Any], + *, + tags_only: bool, + freeze: bool, +) -> tuple[int, RevInfo, RevInfo]: + old = RevInfo.from_config(repo) + new = old.update(tags_only=tags_only, freeze=freeze) + _check_hooks_still_exist_at_rev(repo, new) + return i, old, new + + REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$') @@ -147,45 +162,50 @@ def autoupdate( tags_only: bool, freeze: bool, repos: Sequence[str] = (), + jobs: int = 1, ) -> int: """Auto-update the pre-commit config to the latest versions of repos.""" migrate_config(config_file, quiet=True) - retv = 0 - rev_infos: list[RevInfo | None] = [] changed = False + retv = 0 - config = load_config(config_file) - for repo_config in config['repos']: - if repo_config['repo'] in {LOCAL, META}: - continue - - info = RevInfo.from_config(repo_config) - if repos and info.repo not in repos: - rev_infos.append(None) - continue - - output.write(f'Updating {info.repo} ... ') - try: - new_info = info.update(tags_only=tags_only, freeze=freeze) - _check_hooks_still_exist_at_rev(repo_config, new_info) - except RepositoryCannotBeUpdatedError as error: - output.write_line(error.args[0]) - rev_infos.append(None) - retv = 1 - continue - - if new_info.rev != info.rev: - changed = True - if new_info.frozen: - updated_to = f'{new_info.frozen} (frozen)' + config_repos = [ + repo for repo in load_config(config_file)['repos'] + if repo['repo'] not in {LOCAL, META} + ] + + rev_infos: list[RevInfo | None] = [None] * len(config_repos) + jobs = jobs or xargs.cpu_count() # 0 => number of cpus + jobs = min(jobs, len(repos) or len(config_repos)) # max 1-per-thread + jobs = max(jobs, 1) # at least one thread + with concurrent.futures.ThreadPoolExecutor(jobs) as exe: + futures = [ + exe.submit( + _update_one, + i, repo, tags_only=tags_only, freeze=freeze, + ) + for i, repo in enumerate(config_repos) + if not repos or repo['repo'] in repos + ] + for future in concurrent.futures.as_completed(futures): + try: + i, old, new = future.result() + except RepositoryCannotBeUpdatedError as e: + output.write_line(str(e)) + retv = 1 else: - updated_to = new_info.rev - msg = f'updating {info.rev} -> {updated_to}.' - output.write_line(msg) - rev_infos.append(new_info) - else: - output.write_line('already up to date.') - rev_infos.append(None) + if new.rev != old.rev: + changed = True + if new.frozen: + new_s = f'{new.frozen} (frozen)' + else: + new_s = new.rev + msg = f'updating {old.rev} -> {new_s}' + rev_infos[i] = new + else: + msg = 'already up to date!' + + output.write_line(f'[{old.repo}] {msg}') if changed: _write_new_config(config_file, rev_infos) diff --git a/pre_commit/lang_base.py b/pre_commit/lang_base.py index 9480c559f..4a993eaa4 100644 --- a/pre_commit/lang_base.py +++ b/pre_commit/lang_base.py @@ -1,7 +1,6 @@ from __future__ import annotations import contextlib -import multiprocessing import os import random import re @@ -15,9 +14,9 @@ import pre_commit.constants as C from pre_commit import parse_shebang +from pre_commit import xargs from pre_commit.prefix import Prefix from pre_commit.util import cmd_output_b -from pre_commit.xargs import xargs FIXED_RANDOM_SEED = 1542676187 @@ -140,10 +139,7 @@ def target_concurrency() -> int: if 'TRAVIS' in os.environ: return 2 else: - try: - return multiprocessing.cpu_count() - except NotImplementedError: - return 1 + return xargs.cpu_count() def _shuffled(seq: Sequence[str]) -> list[str]: @@ -171,7 +167,7 @@ def run_xargs( # ordering. file_args = _shuffled(file_args) jobs = target_concurrency() - return xargs(cmd, file_args, target_concurrency=jobs, color=color) + return xargs.xargs(cmd, file_args, target_concurrency=jobs, color=color) def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]: diff --git a/pre_commit/main.py b/pre_commit/main.py index 402bc2e56..9dfce2c25 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -226,9 +226,13 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: help='Store "frozen" hashes in `rev` instead of tag names', ) autoupdate_parser.add_argument( - '--repo', dest='repos', action='append', metavar='REPO', + '--repo', dest='repos', action='append', metavar='REPO', default=[], help='Only update this repository -- may be specified multiple times.', ) + autoupdate_parser.add_argument( + '-j', '--jobs', type=int, default=1, + help='Number of threads to use. (default %(default)s).', + ) _add_cmd('clean', help='Clean out pre-commit files.') @@ -372,6 +376,7 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: tags_only=not args.bleeding_edge, freeze=args.freeze, repos=args.repos, + jobs=args.jobs, ) elif args.command == 'clean': return clean(store) diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index e3af90efd..31be6f323 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -3,6 +3,7 @@ import concurrent.futures import contextlib import math +import multiprocessing import os import subprocess import sys @@ -22,6 +23,13 @@ TRet = TypeVar('TRet') +def cpu_count() -> int: + try: + return multiprocessing.cpu_count() + except NotImplementedError: + return 1 + + def _environ_size(_env: MutableMapping[str, str] | None = None) -> int: environ = _env if _env is not None else getattr(os, 'environb', os.environ) size = 8 * len(environ) # number of pointers in `envp` From 4c0623963f9cd0735829fec265575fdd003a7659 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 1 May 2023 18:22:26 -0400 Subject: [PATCH 173/342] v3.3.0 --- CHANGELOG.md | 12 ++++++++++++ setup.cfg | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd96c796..57e58ff25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +3.3.0 - 2023-05-01 +================== + +### Features +- Upgrade ruby-build. + - #2846 PR by @jalessio. +- Use blobless clone for faster autoupdate. + - #2859 PR by @asottile. +- Add `-j` / `--jobs` argument to `autoupdate` for parallel execution. + - #2863 PR by @asottile. + - issue by @gaborbernat. + 3.2.2 - 2023-04-03 ================== diff --git a/setup.cfg b/setup.cfg index 89e8e4ada..8dffb6b75 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.2.2 +version = 3.3.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 420a15f87e6f0ec8f9fba0ff284b7e1bd34b9d82 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 2 May 2023 09:54:25 -0400 Subject: [PATCH 174/342] add partial clone hack to fix autoupdate for windows --- pre_commit/commands/autoupdate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 178648108..e7725fdc4 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -67,6 +67,8 @@ def update(self, tags_only: bool, freeze: bool) -> RevInfo: rev, frozen = exact, rev try: + # workaround for windows -- see #2865 + cmd_output_b(*_git, 'show', f'{rev}:{C.MANIFEST_FILE}') cmd_output(*_git, 'checkout', rev, '--', C.MANIFEST_FILE) except CalledProcessError: pass # this will be caught by manifest validating code From 51104fa94a6c3cdf603de2e187284289ea5abcf5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 2 May 2023 10:07:25 -0400 Subject: [PATCH 175/342] v3.3.1 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e58ff25..970b8be19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.3.1 - 2023-05-02 +================== + +### Fixes +- Work around `git` partial clone bug for `autoupdate` on windows. + - #2866 PR by @asottile. + - #2865 issue by @adehad. + 3.3.0 - 2023-05-01 ================== diff --git a/setup.cfg b/setup.cfg index 8dffb6b75..cdd6ec3b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.3.0 +version = 3.3.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 1dd85c904eb76543c80e6506cdfd662fcb889e3b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 04:04:18 +0000 Subject: [PATCH 176/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - https://github.com/asottile/reorder_python_imports → https://github.com/asottile/reorder-python-imports - [github.com/asottile/pyupgrade: v3.3.2 → v3.4.0](https://github.com/asottile/pyupgrade/compare/v3.3.2...v3.4.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ffd305878..d275d244e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: rev: v2.2.0 hooks: - id: setup-cfg-fmt -- repo: https://github.com/asottile/reorder_python_imports +- repo: https://github.com/asottile/reorder-python-imports rev: v3.9.0 hooks: - id: reorder-python-imports @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.3.2 + rev: v3.4.0 hooks: - id: pyupgrade args: [--py38-plus] From 8923fa368a5cb37ed7219a7ce0eafbe2351258b5 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 13 May 2023 15:46:34 -0400 Subject: [PATCH 177/342] r does not support language_version currently --- pre_commit/languages/r.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 138a26e1e..083329c0e 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -93,6 +93,8 @@ def install_environment( version: str, additional_dependencies: Sequence[str], ) -> None: + lang_base.assert_version_default('r', version) + env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) os.makedirs(env_dir, exist_ok=True) shutil.copy(prefix.path('renv.lock'), env_dir) From 926071b6a7e9f797cab6a089e32cd59065741b1e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 13 May 2023 16:03:14 -0400 Subject: [PATCH 178/342] make some files trigger all languages --- testing/languages | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/testing/languages b/testing/languages index 5e8fc9e4f..9abc185f1 100755 --- a/testing/languages +++ b/testing/languages @@ -16,6 +16,15 @@ EXCLUDED = frozenset(( )) +def _always_run() -> frozenset[str]: + ret = ['.github/workflows/languages.yml', 'testing/languages'] + ret.extend( + os.path.join('pre_commit/resources', fname) + for fname in os.listdir('pre_commit/resources') + ) + return frozenset(ret) + + def _lang_files(lang: str) -> frozenset[str]: prog = f'''\ import json @@ -47,10 +56,12 @@ def main() -> int: if fname.endswith('.py') and fname != '__init__.py' ] + triggers_all = _always_run() + if not args.all: with concurrent.futures.ThreadPoolExecutor(os.cpu_count()) as exe: by_lang = { - lang: files + lang: files | triggers_all for lang, files in zip(langs, exe.map(_lang_files, langs)) } From 9c2a01186b0b7e3395dfa2744e94a1b860ef716f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 13 May 2023 16:27:14 -0400 Subject: [PATCH 179/342] fix typo in testing/languages --- testing/languages | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testing/languages b/testing/languages index 9abc185f1..f4804c7e5 100755 --- a/testing/languages +++ b/testing/languages @@ -17,7 +17,7 @@ EXCLUDED = frozenset(( def _always_run() -> frozenset[str]: - ret = ['.github/workflows/languages.yml', 'testing/languages'] + ret = ['.github/workflows/languages.yaml', 'testing/languages'] ret.extend( os.path.join('pre_commit/resources', fname) for fname in os.listdir('pre_commit/resources') @@ -57,6 +57,8 @@ def main() -> int: ] triggers_all = _always_run() + for fname in triggers_all: + assert os.path.exists(fname), fname if not args.all: with concurrent.futures.ThreadPoolExecutor(os.cpu_count()) as exe: From 08b670ff9e32e6c0fca2c5d22180b8b686b3d985 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 13 May 2023 16:24:29 -0400 Subject: [PATCH 180/342] swift is included in github actions --- .github/workflows/languages.yaml | 2 -- testing/get-swift.sh | 29 ----------------------------- 2 files changed, 31 deletions(-) delete mode 100755 testing/get-swift.sh diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index 8bc8e712f..57a1c0c79 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -63,8 +63,6 @@ jobs: echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" shell: bash if: matrix.os == 'windows-latest' && matrix.language == 'perl' - - run: testing/get-swift.sh - if: matrix.os == 'ubuntu-latest' && matrix.language == 'swift' - name: install deps run: python -mpip install -e . -r requirements-dev.txt diff --git a/testing/get-swift.sh b/testing/get-swift.sh deleted file mode 100755 index dfe093912..000000000 --- a/testing/get-swift.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -# This is a script used in CI to install swift -set -euo pipefail - -. /etc/lsb-release -if [ "$DISTRIB_CODENAME" = "jammy" ]; then - SWIFT_URL='https://download.swift.org/swift-5.7.1-release/ubuntu2204/swift-5.7.1-RELEASE/swift-5.7.1-RELEASE-ubuntu22.04.tar.gz' - SWIFT_HASH='7f60291f5088d3e77b0c2364beaabd29616ee7b37260b7b06bdbeb891a7fe161' -else - echo "unknown dist: ${DISTRIB_CODENAME}" 1>&2 - exit 1 -fi - -check() { - echo "$SWIFT_HASH $TGZ" | sha256sum --check -} - -TGZ="$HOME/.swift/swift.tar.gz" -mkdir -p "$(dirname "$TGZ")" -if ! check >& /dev/null; then - rm -f "$TGZ" - curl --location --silent --output "$TGZ" "$SWIFT_URL" - check -fi - -mkdir -p /tmp/swift -tar -xf "$TGZ" --strip 1 --directory /tmp/swift - -echo '/tmp/swift/usr/bin' >> "$GITHUB_PATH" From 64985bd63d62126dc4efd58af63967ae80121ca2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 03:20:34 +0000 Subject: [PATCH 181/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.2.0 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.2.0...v1.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d275d244e..cb03c759d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.2.0 + rev: v1.3.0 hooks: - id: mypy additional_dependencies: [types-all] From cd09c3525e35676af9fa614c04faebe5a88fc9de Mon Sep 17 00:00:00 2001 From: Lorenz Walthert Date: Mon, 15 May 2023 09:26:55 +0200 Subject: [PATCH 182/342] avoid quoting and escaping while installing R hooks by writing code to tempfile instead of execute R code inline --- pre_commit/languages/r.py | 51 +++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 083329c0e..6feb06523 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -4,6 +4,8 @@ import os import shlex import shutil +import tempfile +import textwrap from typing import Generator from typing import Sequence @@ -21,6 +23,19 @@ health_check = lang_base.basic_health_check +@contextlib.contextmanager +def _r_code_in_tempfile(code: str) -> Generator[str, None, None]: + """ + To avoid quoting and escaping issues, avoid `Rscript [options] -e {expr}` + but use `Rscript [options] path/to/file_with_expr.R` + """ + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'script.R') + with open(fname, 'w') as f: + f.write(_inline_r_setup(textwrap.dedent(code))) + yield fname + + def get_env_patch(venv: str) -> PatchesT: return ( ('R_PROFILE_USER', os.path.join(venv, 'activate.R')), @@ -129,20 +144,19 @@ def install_environment( }} """ - cmd_output_b( - _rscript_exec(), '--vanilla', '-e', - _inline_r_setup(r_code_inst_environment), - cwd=env_dir, - ) + with _r_code_in_tempfile(r_code_inst_environment) as f: + cmd_output_b(_rscript_exec(), '--vanilla', f, cwd=env_dir) + if additional_dependencies: r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))' with in_env(prefix, version): - cmd_output_b( - _rscript_exec(), *RSCRIPT_OPTS, '-e', - _inline_r_setup(r_code_inst_add), - *additional_dependencies, - cwd=env_dir, - ) + with _r_code_in_tempfile(r_code_inst_add) as f: + cmd_output_b( + _rscript_exec(), *RSCRIPT_OPTS, + f, + *additional_dependencies, + cwd=env_dir, + ) def _inline_r_setup(code: str) -> str: @@ -150,11 +164,16 @@ def _inline_r_setup(code: str) -> str: Some behaviour of R cannot be configured via env variables, but can only be configured via R options once R has started. These are set here. """ - with_option = f"""\ - options(install.packages.compile.from.source = "never", pkgType = "binary") - {code} - """ - return with_option + with_option = [ + textwrap.dedent("""\ + options( + install.packages.compile.from.source = "never", + pkgType = "binary" + ) + """), + code, + ] + return '\n'.join(with_option) def run_hook( From a0a734750e1af5a0ec0b2579d3b05f427f53c8b6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 17 May 2023 18:36:52 -0400 Subject: [PATCH 183/342] v3.3.2 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 970b8be19..4256c6aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.3.2 - 2023-05-17 +================== + +### Fixes +- Work around `r` on windows sometimes double-un-quoting arguments. + - #2885 PR by @lorenzwalthert. + - #2870 issue by @lorenzwalthert. + 3.3.1 - 2023-05-02 ================== diff --git a/setup.cfg b/setup.cfg index cdd6ec3b2..b2c268ba8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.3.1 +version = 3.3.2 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 18348f5d0dbbfe10b139dbd8f220fe608810fc83 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 17 May 2023 18:55:11 -0400 Subject: [PATCH 184/342] use distlib inside the zipapp docker image --- testing/zipapp/Dockerfile | 4 ++-- testing/zipapp/make | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/testing/zipapp/Dockerfile b/testing/zipapp/Dockerfile index 7c74c1b2e..ea967e383 100644 --- a/testing/zipapp/Dockerfile +++ b/testing/zipapp/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:focal +FROM ubuntu:jammy RUN : \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ @@ -11,4 +11,4 @@ RUN : \ ENV LANG=C.UTF-8 PATH=/venv/bin:$PATH RUN : \ && python3 -mvenv /venv \ - && pip install --no-cache-dir pip setuptools wheel no-manylinux --upgrade + && pip install --no-cache-dir pip distlib no-manylinux --upgrade diff --git a/testing/zipapp/make b/testing/zipapp/make index 37b5c355d..165046f66 100755 --- a/testing/zipapp/make +++ b/testing/zipapp/make @@ -4,7 +4,6 @@ from __future__ import annotations import argparse import base64 import hashlib -import importlib.resources import io import os.path import shutil @@ -42,10 +41,17 @@ def _add_shim(dest: str) -> None: with zipfile.ZipFile(bio, 'w') as zipf: zipf.write(shim, arcname='__main__.py') - with open(os.path.join(dest, 'python.exe'), 'wb') as f: - f.write(importlib.resources.read_binary('distlib', 't32.exe')) - f.write(b'#!py.exe -3\n') - f.write(bio.getvalue()) + with tempfile.TemporaryDirectory() as tmpdir: + _exit_if_retv( + 'podman', 'run', '--rm', '--volume', f'{tmpdir}:/out:rw', IMG, + 'cp', '/venv/lib/python3.10/site-packages/distlib/t32.exe', '/out', + ) + + with open(os.path.join(dest, 'python.exe'), 'wb') as f: + with open(os.path.join(tmpdir, 't32.exe'), 'rb') as t32: + f.write(t32.read()) + f.write(b'#!py.exe -3\n') + f.write(bio.getvalue()) def _write_cache_key(version: str, wheeldir: str, dest: str) -> None: From f4a2d52bb46f9d030aad76790035b5a3c12cb1cb Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 1 Jun 2023 19:12:46 -0400 Subject: [PATCH 185/342] fix tags trigger for github actions the old syntax worked for azure pipelines but not GHA Committed via https://github.com/asottile/all-repos --- .github/workflows/languages.yaml | 2 +- .github/workflows/main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index 57a1c0c79..7e97158cf 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -3,7 +3,7 @@ name: languages on: push: branches: [main, test-me-*] - tags: + tags: '*' pull_request: concurrency: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f281dcf27..903d24780 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: main on: push: branches: [main, test-me-*] - tags: + tags: '*' pull_request: concurrency: From f88cc6125681378d4d2704a7b08dd595e3744180 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 03:14:12 +0000 Subject: [PATCH 186/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.2.0 → v2.3.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.2.0...v2.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb03c759d..80ee23d00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 + rev: v2.3.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports From 5d273951e00e8331b6b3b07ef6e0280080566cca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 03:14:26 +0000 Subject: [PATCH 187/342] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b2c268ba8..efbf2141d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ url = https://github.com/pre-commit/pre-commit author = Anthony Sottile author_email = asottile@umich.edu license = MIT -license_file = LICENSE +license_files = LICENSE classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 From 1fc28903ab82e4ef7f8e9c37b052e4f9a53c9967 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 03:58:21 +0000 Subject: [PATCH 188/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/add-trailing-comma: v2.4.0 → v2.5.1](https://github.com/asottile/add-trailing-comma/compare/v2.4.0...v2.5.1) - [github.com/asottile/pyupgrade: v3.4.0 → v3.6.0](https://github.com/asottile/pyupgrade/compare/v3.4.0...v3.6.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80ee23d00..7810d2294 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,12 +20,12 @@ repos: exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py38-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.4.0 + rev: v2.5.1 hooks: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.4.0 + rev: v3.6.0 hooks: - id: pyupgrade args: [--py38-plus] From 9a7ed8be09b6de99bae5d1e03defc350a132b1e5 Mon Sep 17 00:00:00 2001 From: Jay Soffian Date: Tue, 13 Jun 2023 17:47:49 -0400 Subject: [PATCH 189/342] Force gem installation into envdir RubyGems allows OS packagers to specify defaults for `--install-dir` and `--bindir` and these take precedence over `GEM_HOME`. The only way to override the defaults is to explicitly specify the options ourselves when running `gem install`. Examples of OSes where this is the case are RedHat 9.2 and Gentoo. Fixes #2799. --- pre_commit/languages/ruby.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 76631f253..a411925a2 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -114,6 +114,8 @@ def _install_ruby( def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + if version != 'system': # pragma: win32 no cover _install_rbenv(prefix, version) with in_env(prefix, version): @@ -135,6 +137,8 @@ def install_environment( 'gem', 'install', '--no-document', '--no-format-executable', '--no-user-install', + '--install-dir', os.path.join(envdir, 'gems'), + '--bindir', os.path.join(envdir, 'gems', 'bin'), *prefix.star('.gem'), *additional_dependencies, ), ) From 50b1511a5b81e5c95bcf496acc22dc9799a429b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:04:03 +0000 Subject: [PATCH 190/342] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pre_commit/languages/ruby.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index a411925a2..c88269f24 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -138,7 +138,7 @@ def install_environment( '--no-document', '--no-format-executable', '--no-user-install', '--install-dir', os.path.join(envdir, 'gems'), - '--bindir', os.path.join(envdir, 'gems', 'bin'), + '--bindir', os.path.join(envdir, 'gems', 'bin'), *prefix.star('.gem'), *additional_dependencies, ), ) From 5da4258b17dea7bd4601358de200e185699f9997 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 13 Jun 2023 19:11:02 -0400 Subject: [PATCH 191/342] v3.3.3 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4256c6aa4..722e8ffa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.3.3 - 2023-06-13 +================== + +### Fixes +- Work around OS packagers setting `--install-dir` / `--bin-dir` in gem settings. + - #2905 PR by @jaysoffian. + - #2799 issue by @lmilbaum. + 3.3.2 - 2023-05-17 ================== diff --git a/setup.cfg b/setup.cfg index efbf2141d..88302e752 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.3.2 +version = 3.3.3 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From f94744a699e7d125bcd7cabc070c3129a9079cc1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 04:33:31 +0000 Subject: [PATCH 192/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.9.0 → v3.10.0](https://github.com/asottile/reorder-python-imports/compare/v3.9.0...v3.10.0) - [github.com/asottile/pyupgrade: v3.6.0 → v3.7.0](https://github.com/asottile/pyupgrade/compare/v3.6.0...v3.7.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7810d2294..6896fb75a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.9.0 + rev: v3.10.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -25,7 +25,7 @@ repos: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.6.0 + rev: v3.7.0 hooks: - id: pyupgrade args: [--py38-plus] From 854f6985314079889586d1eee43fc185fe0fee62 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 03:51:58 +0000 Subject: [PATCH 193/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6896fb75a..bb989bb34 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.3.0 + rev: v1.4.1 hooks: - id: mypy additional_dependencies: [types-all] From e72699b9ef824d1dc2a1834ba7acbced9853235a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 1 Jul 2023 16:47:06 -0400 Subject: [PATCH 194/342] updates for add-trailing-comma 3.x Committed via https://github.com/asottile/all-repos --- .pre-commit-config.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb989bb34..2bd6cca98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.3.0 + rev: v2.4.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports @@ -20,12 +20,11 @@ repos: exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py38-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.5.1 + rev: v3.0.0 hooks: - id: add-trailing-comma - args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.7.0 + rev: v3.8.0 hooks: - id: pyupgrade args: [--py38-plus] From 1c439b5a79d1c6ff9e36327f852e58e79452124c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 1 Jul 2023 17:22:42 -0400 Subject: [PATCH 195/342] shlex.join is always available in 3.8+ --- pre_commit/commands/install_uninstall.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 5ff6cba6e..d19e0d47e 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -103,8 +103,7 @@ def _install_hook_script( hook_file.write(before + TEMPLATE_START) hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n') - # TODO: python3.8+: shlex.join - args_s = ' '.join(shlex.quote(part) for part in args) + args_s = shlex.join(args) hook_file.write(f'ARGS=({args_s})\n') hook_file.write(TEMPLATE_END + after) make_executable(hook_path) From 9bf6856db35f51be1fd131094aba142f71af3543 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 05:21:22 +0000 Subject: [PATCH 196/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2bd6cca98..2e7ff8cc9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.8.0 + rev: v3.9.0 hooks: - id: pyupgrade args: [--py38-plus] From 5e4af63e8546f9b0e3f9b4a454b09c8607d02fe8 Mon Sep 17 00:00:00 2001 From: Max R Date: Sun, 16 Jul 2023 15:00:55 -0400 Subject: [PATCH 197/342] Fix link to `language` API --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab3a92989..182e7bc10 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,7 +92,7 @@ language, for example: here are the apis that should be implemented for a language -Note that these are also documented in [`pre_commit/languages/all.py`](https://github.com/pre-commit/pre-commit/blob/main/pre_commit/languages/all.py) +Note that these are also documented in [`pre_commit/lang_base.py`](https://github.com/pre-commit/pre-commit/blob/main/pre_commit/lang_base.py) #### `ENVIRONMENT_DIR` From d537c09032e1c1ca945aec2d0abb8fe80835eb88 Mon Sep 17 00:00:00 2001 From: Max R Date: Mon, 17 Jul 2023 09:36:47 -0400 Subject: [PATCH 198/342] `s/helpers/lang_base/g` --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab3a92989..dc7a70c7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,7 +111,7 @@ one cannot be determined, return `'default'`. You generally don't need to implement this on a first pass and can just use: ```python -get_default_version = helpers.basic_default_version +get_default_version = lang_base.basic_default_version ``` `python` is currently the only language which implements this api @@ -125,7 +125,7 @@ healthy. You generally don't need to implement this on a first pass and can just use: ```python -health_check = helpers.basic_healthy_check +health_check = lang_base.basic_healthy_check ``` `python` is currently the only language which implements this api, for python @@ -137,7 +137,7 @@ this is the trickiest one to implement and where all the smart parts happen. this api should do the following things -- (0th / 3rd class): `install_environment = helpers.no_install` +- (0th / 3rd class): `install_environment = lang_base.no_install` - (1st class): install a language runtime into the hook's directory - (2nd class): install the package at `.` into the `ENVIRONMENT_DIR` - (2nd class, optional): install packages listed in `additional_dependencies` From 60273ca81ea974bd429e7ebfbcee6b8598f30040 Mon Sep 17 00:00:00 2001 From: Alex Brandt Date: Wed, 19 Jul 2023 19:26:28 +0100 Subject: [PATCH 199/342] Add haskell language support to pre-commit. --- .github/workflows/languages.yaml | 2 ++ pre_commit/all_languages.py | 2 ++ pre_commit/languages/haskell.py | 56 ++++++++++++++++++++++++++++++++ tests/languages/haskell_test.py | 50 ++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 pre_commit/languages/haskell.py create mode 100644 tests/languages/haskell_test.py diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index 7e97158cf..5a6ae9cd7 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -63,6 +63,8 @@ jobs: echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH" shell: bash if: matrix.os == 'windows-latest' && matrix.language == 'perl' + - uses: haskell/actions/setup@v2 + if: matrix.language == 'haskell' - name: install deps run: python -mpip install -e . -r requirements-dev.txt diff --git a/pre_commit/all_languages.py b/pre_commit/all_languages.py index 2bed7067f..476bad9da 100644 --- a/pre_commit/all_languages.py +++ b/pre_commit/all_languages.py @@ -9,6 +9,7 @@ from pre_commit.languages import dotnet from pre_commit.languages import fail from pre_commit.languages import golang +from pre_commit.languages import haskell from pre_commit.languages import lua from pre_commit.languages import node from pre_commit.languages import perl @@ -31,6 +32,7 @@ 'dotnet': dotnet, 'fail': fail, 'golang': golang, + 'haskell': haskell, 'lua': lua, 'node': node, 'perl': perl, diff --git a/pre_commit/languages/haskell.py b/pre_commit/languages/haskell.py new file mode 100644 index 000000000..76442eb02 --- /dev/null +++ b/pre_commit/languages/haskell.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import contextlib +import os.path +from typing import Generator +from typing import Sequence + +from pre_commit import lang_base +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import Var +from pre_commit.errors import FatalError +from pre_commit.prefix import Prefix + +ENVIRONMENT_DIR = 'hs_env' +get_default_version = lang_base.basic_get_default_version +health_check = lang_base.basic_health_check +run_hook = lang_base.basic_run_hook + + +def get_env_patch(target_dir: str) -> PatchesT: + bin_path = os.path.join(target_dir, 'bin') + return (('PATH', (bin_path, os.pathsep, Var('PATH'))),) + + +@contextlib.contextmanager +def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + with envcontext(get_env_patch(envdir)): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + lang_base.assert_version_default('haskell', version) + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + + pkgs = [*prefix.star('.cabal'), *additional_dependencies] + if not pkgs: + raise FatalError('Expected .cabal files or additional_dependencies') + + bindir = os.path.join(envdir, 'bin') + os.makedirs(bindir, exist_ok=True) + lang_base.setup_cmd(prefix, ('cabal', 'update')) + lang_base.setup_cmd( + prefix, + ( + 'cabal', 'install', + '--install-method', 'copy', + '--installdir', bindir, + *pkgs, + ), + ) diff --git a/tests/languages/haskell_test.py b/tests/languages/haskell_test.py new file mode 100644 index 000000000..f888109bd --- /dev/null +++ b/tests/languages/haskell_test.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import pytest + +from pre_commit.errors import FatalError +from pre_commit.languages import haskell +from pre_commit.util import win_exe +from testing.language_helpers import run_language + + +def test_run_example_executable(tmp_path): + example_cabal = '''\ +cabal-version: 2.4 +name: example +version: 0.1.0.0 + +executable example + main-is: Main.hs + + build-depends: base >=4 + default-language: Haskell2010 +''' + main_hs = '''\ +module Main where + +main :: IO () +main = putStrLn "Hello, Haskell!" +''' + tmp_path.joinpath('example.cabal').write_text(example_cabal) + tmp_path.joinpath('Main.hs').write_text(main_hs) + + result = run_language(tmp_path, haskell, 'example') + assert result == (0, b'Hello, Haskell!\n') + + # should not symlink things into environments + exe = tmp_path.joinpath(win_exe('hs_env-default/bin/example')) + assert exe.is_file() + assert not exe.is_symlink() + + +def test_run_dep(tmp_path): + result = run_language(tmp_path, haskell, 'hello', deps=['hello']) + assert result == (0, b'Hello, World!\n') + + +def test_run_empty(tmp_path): + with pytest.raises(FatalError) as excinfo: + run_language(tmp_path, haskell, 'example') + msg, = excinfo.value.args + assert msg == 'Expected .cabal files or additional_dependencies' From 3557077bbc9a5c7fe8f373f785ec2d2d79b6a999 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 07:08:53 +0000 Subject: [PATCH 200/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/add-trailing-comma: v3.0.0 → v3.0.1](https://github.com/asottile/add-trailing-comma/compare/v3.0.0...v3.0.1) - [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1) - [github.com/PyCQA/flake8: 6.0.0 → 6.1.0](https://github.com/PyCQA/flake8/compare/6.0.0...6.1.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e7ff8cc9..4ab4feb3e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py38-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.0.0 + rev: v3.0.1 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 + rev: v3.10.1 hooks: - id: pyupgrade args: [--py38-plus] @@ -33,7 +33,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From 8c75a26f2df489b89e808d26f0cdd83ced19d1e0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 1 Aug 2023 12:08:52 -0400 Subject: [PATCH 201/342] update hello world go test --- tests/languages/golang_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index ec5a87875..640626711 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -128,7 +128,7 @@ def test_local_golang_additional_deps(tmp_path): deps=('golang.org/x/example/hello@latest',), ) - assert ret == (0, b'Hello, Go examples!\n') + assert ret == (0, b'Hello, world!\n') def test_golang_hook_still_works_when_gobin_is_set(tmp_path): From 1803db979f86ab3e1df8194f40f0177413f0fbb3 Mon Sep 17 00:00:00 2001 From: Fufu Fang Date: Mon, 14 Aug 2023 11:00:17 +0100 Subject: [PATCH 202/342] fix typo in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9965c6ca0..da7f9432f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -125,7 +125,7 @@ healthy. You generally don't need to implement this on a first pass and can just use: ```python -health_check = lang_base.basic_healthy_check +health_check = lang_base.basic_health_check ``` `python` is currently the only language which implements this api, for python From 93b1a144023891c0083e2a18cd8d320e47e0d656 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 06:03:09 +0000 Subject: [PATCH 203/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ab4feb3e..b53a90e29 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 + rev: v1.5.0 hooks: - id: mypy additional_dependencies: [types-all] From 5a4b5b1f8ea29a9df154df504f844db186e877b0 Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Mon, 21 Aug 2023 20:02:27 -0500 Subject: [PATCH 204/342] Fix exit code for commands terminated by signals Fixes https://github.com/pre-commit/pre-commit/issues/2970 --- pre_commit/xargs.py | 3 ++- tests/xargs_test.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index 31be6f323..eff57ce7c 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -170,7 +170,8 @@ def run_cmd_partition( results = thread_map(run_cmd_partition, partitions) for proc_retcode, proc_out, _ in results: - retcode = max(retcode, proc_retcode) + if abs(proc_retcode) > abs(retcode): + retcode = proc_retcode stdout += proc_out return retcode, stdout diff --git a/tests/xargs_test.py b/tests/xargs_test.py index 7c41f98cd..b0a8e0d66 100644 --- a/tests/xargs_test.py +++ b/tests/xargs_test.py @@ -147,6 +147,15 @@ def test_xargs_retcode_normal(): assert ret == 5 +@pytest.mark.xfail(sys.platform == 'win32', reason='posix only') +def test_xargs_retcode_killed_by_signal(): + ret, _ = xargs.xargs( + parse_shebang.normalize_cmd(('bash', '-c', 'kill -9 $$', '--')), + ('foo', 'bar'), + ) + assert ret == -9 + + def test_xargs_concurrency(): bash_cmd = parse_shebang.normalize_cmd(('bash', '-c')) print_pid = ('sleep 0.5 && echo $$',) From a4ae868633ca56f37fb4264c528c2ae52f50305f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 06:16:21 +0000 Subject: [PATCH 205/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b53a90e29..54a56ef38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.0 + rev: v1.5.1 hooks: - id: mypy additional_dependencies: [types-all] From 3dd1875df85ea258c790af93ed9d4311fc87a5d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 05:38:11 +0000 Subject: [PATCH 206/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-autopep8: v2.0.2 → v2.0.4](https://github.com/pre-commit/mirrors-autopep8/compare/v2.0.2...v2.0.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54a56ef38..5c6f62b45 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.2 + rev: v2.0.4 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From ea8244b229fa3b7e0c1c26e5e824fb64dfbb4b1d Mon Sep 17 00:00:00 2001 From: Joe Bateson Date: Mon, 28 Aug 2023 19:20:23 -0700 Subject: [PATCH 207/342] Use os.sched_getaffinity for cpu counts when available --- pre_commit/xargs.py | 8 ++++++++ tests/lang_base_test.py | 27 +++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index eff57ce7c..a7493c01d 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -24,6 +24,14 @@ def cpu_count() -> int: + try: + # On systems that support it, this will return a more accurate count of + # usable CPUs for the current process, which will take into account + # cgroup limits + return len(os.sched_getaffinity(0)) + except AttributeError: + pass + try: return multiprocessing.cpu_count() except NotImplementedError: diff --git a/tests/lang_base_test.py b/tests/lang_base_test.py index a532b6a54..1cffa0e5f 100644 --- a/tests/lang_base_test.py +++ b/tests/lang_base_test.py @@ -30,6 +30,19 @@ def fake_expanduser(pth): yield +@pytest.fixture +def no_sched_getaffinity(): + # Simulates an OS without os.sched_getaffinity available (mac/windows) + # https://docs.python.org/3/library/os.html#interface-to-the-scheduler + with mock.patch.object( + os, + 'sched_getaffinity', + create=True, + side_effect=AttributeError, + ): + yield + + def test_exe_exists_does_not_exist(find_exe_mck, homedir_mck): find_exe_mck.return_value = None assert lang_base.exe_exists('ruby') is False @@ -116,7 +129,17 @@ def test_no_env_noop(tmp_path): assert before == inside == after -def test_target_concurrency_normal(): +def test_target_concurrency_sched_getaffinity(no_sched_getaffinity): + with mock.patch.object( + os, + 'sched_getaffinity', + return_value=set(range(345)), + ): + with mock.patch.dict(os.environ, clear=True): + assert lang_base.target_concurrency() == 345 + + +def test_target_concurrency_without_sched_getaffinity(no_sched_getaffinity): with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): with mock.patch.dict(os.environ, {}, clear=True): assert lang_base.target_concurrency() == 123 @@ -134,7 +157,7 @@ def test_target_concurrency_on_travis(): assert lang_base.target_concurrency() == 2 -def test_target_concurrency_cpu_count_not_implemented(): +def test_target_concurrency_cpu_count_not_implemented(no_sched_getaffinity): with mock.patch.object( multiprocessing, 'cpu_count', side_effect=NotImplementedError, ): From fe9ba6b53fd5ae112ef5a3d2ac883e2d0e5a10db Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 2 Sep 2023 13:09:13 -0400 Subject: [PATCH 208/342] v3.4.0 --- CHANGELOG.md | 15 +++++++++++++++ setup.cfg | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 722e8ffa3..9e2ef0de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +3.4.0 - 2023-09-02 +================== + +### Features +- Add `language: haskell`. + - #2932 by @alunduil. +- Improve cpu count detection when run under cgroups. + - #2979 PR by @jdb8. + - #2978 issue by @jdb8. + +### Fixes +- Handle negative exit codes from hooks receiving posix signals. + - #2971 PR by @chriskuehl. + - #2970 issue by @chriskuehl. + 3.3.3 - 2023-06-13 ================== diff --git a/setup.cfg b/setup.cfg index 88302e752..cfaa61bbb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.3.3 +version = 3.4.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 818240e42575620ba8d8d5f36c1d7b5765699c68 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 06:46:49 +0000 Subject: [PATCH 209/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/add-trailing-comma: v3.0.1 → v3.1.0](https://github.com/asottile/add-trailing-comma/compare/v3.0.1...v3.1.0) - https://github.com/pre-commit/mirrors-autopep8 → https://github.com/hhatto/autopep8 --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c6f62b45..3b98f96bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py38-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.0.1 + rev: v3.1.0 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade @@ -28,7 +28,7 @@ repos: hooks: - id: pyupgrade args: [--py38-plus] -- repo: https://github.com/pre-commit/mirrors-autopep8 +- repo: https://github.com/hhatto/autopep8 rev: v2.0.4 hooks: - id: autopep8 From 493c20ce91818493068e499216e64709b96f1230 Mon Sep 17 00:00:00 2001 From: Roel Adriaans Date: Fri, 8 Sep 2023 15:12:45 +0200 Subject: [PATCH 210/342] Use the --include command, hides warning messages Fixes #1983 --- pre_commit/languages/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 66d613637..3e22dc78e 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -93,7 +93,7 @@ def install_environment( # install as if we installed from git local_install_cmd = ( - 'npm', 'install', '--dev', '--prod', + 'npm', 'install', '--include=dev', '--include=prod', '--ignore-prepublish', '--no-progress', '--no-save', ) lang_base.setup_cmd(prefix, local_install_cmd) From 9ac229dad886ed5b133a946a524f36ce4220cbf9 Mon Sep 17 00:00:00 2001 From: Max R Date: Sat, 9 Sep 2023 21:54:47 -0400 Subject: [PATCH 211/342] Refactor `target_concurrency` tests --- tests/lang_base_test.py | 62 +++++++++++------------------------------ tests/xargs_test.py | 35 +++++++++++++++++++++++ 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/tests/lang_base_test.py b/tests/lang_base_test.py index 1cffa0e5f..da289aef8 100644 --- a/tests/lang_base_test.py +++ b/tests/lang_base_test.py @@ -1,6 +1,5 @@ from __future__ import annotations -import multiprocessing import os.path import sys from unittest import mock @@ -10,6 +9,7 @@ import pre_commit.constants as C from pre_commit import lang_base from pre_commit import parse_shebang +from pre_commit import xargs from pre_commit.prefix import Prefix from pre_commit.util import CalledProcessError @@ -30,19 +30,6 @@ def fake_expanduser(pth): yield -@pytest.fixture -def no_sched_getaffinity(): - # Simulates an OS without os.sched_getaffinity available (mac/windows) - # https://docs.python.org/3/library/os.html#interface-to-the-scheduler - with mock.patch.object( - os, - 'sched_getaffinity', - create=True, - side_effect=AttributeError, - ): - yield - - def test_exe_exists_does_not_exist(find_exe_mck, homedir_mck): find_exe_mck.return_value = None assert lang_base.exe_exists('ruby') is False @@ -129,40 +116,23 @@ def test_no_env_noop(tmp_path): assert before == inside == after -def test_target_concurrency_sched_getaffinity(no_sched_getaffinity): - with mock.patch.object( - os, - 'sched_getaffinity', - return_value=set(range(345)), - ): - with mock.patch.dict(os.environ, clear=True): - assert lang_base.target_concurrency() == 345 - - -def test_target_concurrency_without_sched_getaffinity(no_sched_getaffinity): - with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): - with mock.patch.dict(os.environ, {}, clear=True): - assert lang_base.target_concurrency() == 123 - - -def test_target_concurrency_testing_env_var(): - with mock.patch.dict( - os.environ, {'PRE_COMMIT_NO_CONCURRENCY': '1'}, clear=True, - ): - assert lang_base.target_concurrency() == 1 - - -def test_target_concurrency_on_travis(): - with mock.patch.dict(os.environ, {'TRAVIS': '1'}, clear=True): - assert lang_base.target_concurrency() == 2 +@pytest.fixture +def cpu_count_mck(): + with mock.patch.object(xargs, 'cpu_count', return_value=4): + yield -def test_target_concurrency_cpu_count_not_implemented(no_sched_getaffinity): - with mock.patch.object( - multiprocessing, 'cpu_count', side_effect=NotImplementedError, - ): - with mock.patch.dict(os.environ, {}, clear=True): - assert lang_base.target_concurrency() == 1 +@pytest.mark.parametrize( + ('var', 'expected'), + ( + ('PRE_COMMIT_NO_CONCURRENCY', 1), + ('TRAVIS', 2), + (None, 4), + ), +) +def test_target_concurrency(cpu_count_mck, var, expected): + with mock.patch.dict(os.environ, {var: '1'} if var else {}, clear=True): + assert lang_base.target_concurrency() == expected def test_shuffled_is_deterministic(): diff --git a/tests/xargs_test.py b/tests/xargs_test.py index b0a8e0d66..e8000b252 100644 --- a/tests/xargs_test.py +++ b/tests/xargs_test.py @@ -1,6 +1,7 @@ from __future__ import annotations import concurrent.futures +import multiprocessing import os import sys import time @@ -12,6 +13,40 @@ from pre_commit import xargs +def test_cpu_count_sched_getaffinity_exists(): + with mock.patch.object( + os, 'sched_getaffinity', create=True, return_value=set(range(345)), + ): + assert xargs.cpu_count() == 345 + + +@pytest.fixture +def no_sched_getaffinity(): + # Simulates an OS without os.sched_getaffinity available (mac/windows) + # https://docs.python.org/3/library/os.html#interface-to-the-scheduler + with mock.patch.object( + os, + 'sched_getaffinity', + create=True, + side_effect=AttributeError, + ): + yield + + +def test_cpu_count_multiprocessing_cpu_count_implemented(no_sched_getaffinity): + with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): + assert xargs.cpu_count() == 123 + + +def test_cpu_count_multiprocessing_cpu_count_not_implemented( + no_sched_getaffinity, +): + with mock.patch.object( + multiprocessing, 'cpu_count', side_effect=NotImplementedError, + ): + assert xargs.cpu_count() == 1 + + @pytest.mark.parametrize( ('env', 'expected'), ( From 5d692d7e06606ec34ef3a6acf4a0fa7fef158983 Mon Sep 17 00:00:00 2001 From: Max R Date: Sat, 9 Sep 2023 21:51:59 -0400 Subject: [PATCH 212/342] Short-circuit hooks --- pre_commit/commands/run.py | 50 +++++++++---------- pre_commit/meta_hooks/check_hooks_apply.py | 2 +- .../meta_hooks/check_useless_excludes.py | 14 +++--- tests/commands/run_test.py | 16 +++--- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index c867799e8..38d80db3a 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -10,7 +10,8 @@ import time import unicodedata from typing import Any -from typing import Collection +from typing import Generator +from typing import Iterable from typing import MutableMapping from typing import Sequence @@ -57,20 +58,20 @@ def _full_msg( def filter_by_include_exclude( - names: Collection[str], + names: Iterable[str], include: str, exclude: str, -) -> list[str]: +) -> Generator[str, None, None]: include_re, exclude_re = re.compile(include), re.compile(exclude) - return [ + return ( filename for filename in names if include_re.search(filename) if not exclude_re.search(filename) - ] + ) class Classifier: - def __init__(self, filenames: Collection[str]) -> None: + def __init__(self, filenames: Iterable[str]) -> None: self.filenames = [f for f in filenames if os.path.lexists(f)] @functools.lru_cache(maxsize=None) @@ -79,15 +80,14 @@ def _types_for_file(self, filename: str) -> set[str]: def by_types( self, - names: Sequence[str], - types: Collection[str], - types_or: Collection[str], - exclude_types: Collection[str], - ) -> list[str]: + names: Iterable[str], + types: Iterable[str], + types_or: Iterable[str], + exclude_types: Iterable[str], + ) -> Generator[str, None, None]: types = frozenset(types) types_or = frozenset(types_or) exclude_types = frozenset(exclude_types) - ret = [] for filename in names: tags = self._types_for_file(filename) if ( @@ -95,24 +95,24 @@ def by_types( (not types_or or tags & types_or) and not tags & exclude_types ): - ret.append(filename) - return ret - - def filenames_for_hook(self, hook: Hook) -> tuple[str, ...]: - names = self.filenames - names = filter_by_include_exclude(names, hook.files, hook.exclude) - names = self.by_types( - names, + yield filename + + def filenames_for_hook(self, hook: Hook) -> Generator[str, None, None]: + return self.by_types( + filter_by_include_exclude( + self.filenames, + hook.files, + hook.exclude, + ), hook.types, hook.types_or, hook.exclude_types, ) - return tuple(names) @classmethod def from_config( cls, - filenames: Collection[str], + filenames: Iterable[str], include: str, exclude: str, ) -> Classifier: @@ -121,7 +121,7 @@ def from_config( # this also makes improperly quoted shell-based hooks work better # see #1173 if os.altsep == '/' and os.sep == '\\': - filenames = [f.replace(os.sep, os.altsep) for f in filenames] + filenames = (f.replace(os.sep, os.altsep) for f in filenames) filenames = filter_by_include_exclude(filenames, include, exclude) return Classifier(filenames) @@ -148,7 +148,7 @@ def _run_single_hook( verbose: bool, use_color: bool, ) -> tuple[bool, bytes]: - filenames = classifier.filenames_for_hook(hook) + filenames = tuple(classifier.filenames_for_hook(hook)) if hook.id in skips or hook.alias in skips: output.write( @@ -250,7 +250,7 @@ def _compute_cols(hooks: Sequence[Hook]) -> int: return max(cols, 80) -def _all_filenames(args: argparse.Namespace) -> Collection[str]: +def _all_filenames(args: argparse.Namespace) -> Iterable[str]: # these hooks do not operate on files if args.hook_stage in { 'post-checkout', 'post-commit', 'post-merge', 'post-rewrite', diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py index b05a70500..7f491a209 100644 --- a/pre_commit/meta_hooks/check_hooks_apply.py +++ b/pre_commit/meta_hooks/check_hooks_apply.py @@ -21,7 +21,7 @@ def check_all_hooks_match_files(config_file: str) -> int: for hook in all_hooks(config, Store()): if hook.always_run or hook.language == 'fail': continue - elif not classifier.filenames_for_hook(hook): + elif not any(classifier.filenames_for_hook(hook)): print(f'{hook.id} does not apply to this repository') retv = 1 diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py index 0a8249b85..8b0c106a3 100644 --- a/pre_commit/meta_hooks/check_useless_excludes.py +++ b/pre_commit/meta_hooks/check_useless_excludes.py @@ -2,6 +2,7 @@ import argparse import re +from typing import Iterable from typing import Sequence from cfgv import apply_defaults @@ -14,7 +15,7 @@ def exclude_matches_any( - filenames: Sequence[str], + filenames: Iterable[str], include: str, exclude: str, ) -> bool: @@ -50,11 +51,12 @@ def check_useless_excludes(config_file: str) -> int: # Not actually a manifest dict, but this more accurately reflects # the defaults applied during runtime hook = apply_defaults(hook, MANIFEST_HOOK_DICT) - names = classifier.filenames - types = hook['types'] - types_or = hook['types_or'] - exclude_types = hook['exclude_types'] - names = classifier.by_types(names, types, types_or, exclude_types) + names = classifier.by_types( + classifier.filenames, + hook['types'], + hook['types_or'], + hook['exclude_types'], + ) include, exclude = hook['files'], hook['exclude'] if not exclude_matches_any(names, include, exclude): print( diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index dd15b94c5..8d89815b5 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -1127,8 +1127,8 @@ def test_classifier_empty_types_or(tmpdir): types_or=[], exclude_types=[], ) - assert for_symlink == ['foo'] - assert for_file == ['bar'] + assert tuple(for_symlink) == ('foo',) + assert tuple(for_file) == ('bar',) @pytest.fixture @@ -1142,33 +1142,33 @@ def some_filenames(): def test_include_exclude_base_case(some_filenames): ret = filter_by_include_exclude(some_filenames, '', '^$') - assert ret == [ + assert tuple(ret) == ( '.pre-commit-hooks.yaml', 'pre_commit/git.py', 'pre_commit/main.py', - ] + ) def test_matches_broken_symlink(tmpdir): with tmpdir.as_cwd(): os.symlink('does-not-exist', 'link') ret = filter_by_include_exclude({'link'}, '', '^$') - assert ret == ['link'] + assert tuple(ret) == ('link',) def test_include_exclude_total_match(some_filenames): ret = filter_by_include_exclude(some_filenames, r'^.*\.py$', '^$') - assert ret == ['pre_commit/git.py', 'pre_commit/main.py'] + assert tuple(ret) == ('pre_commit/git.py', 'pre_commit/main.py') def test_include_exclude_does_search_instead_of_match(some_filenames): ret = filter_by_include_exclude(some_filenames, r'\.yaml$', '^$') - assert ret == ['.pre-commit-hooks.yaml'] + assert tuple(ret) == ('.pre-commit-hooks.yaml',) def test_include_exclude_exclude_removes_files(some_filenames): ret = filter_by_include_exclude(some_filenames, '', r'\.py$') - assert ret == ['.pre-commit-hooks.yaml'] + assert tuple(ret) == ('.pre-commit-hooks.yaml',) def test_args_hook_only(cap_out, store, repo_with_passing_hook): From d33801e78176d91023a441ca869ecb0288f4f461 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 07:06:08 +0000 Subject: [PATCH 213/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) - [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b98f96bc..fb969280c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.11.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.11.0 hooks: - id: pyupgrade args: [--py38-plus] From 5e05b012157763a46f5f0364ce575d7b99cf5c21 Mon Sep 17 00:00:00 2001 From: Eric Long Date: Mon, 25 Sep 2023 17:00:29 +0800 Subject: [PATCH 214/342] Bump Node.js version to 18.14.0 and Go to 1.21.1 On riscv64, nodeenv will pull binary from unofficial-builds [1], and unfortunately 18.13.0 seems to be the only version above 18 that is missing riscv64 builds. Shifting the version slightly to make test work. Go's binary now ships with linux/riscv64 binary since 1.21. --- tests/languages/golang_test.py | 4 ++-- tests/languages/node_test.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index 640626711..19e9f62f6 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -111,11 +111,11 @@ def test_golang_versioned(tmp_path): tmp_path, golang, 'go version', - version='1.18.4', + version='1.21.1', ) assert ret == 0 - assert out.startswith(b'go version go1.18.4') + assert out.startswith(b'go version go1.21.1') def test_local_golang_additional_deps(tmp_path): diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py index cba0228b3..055cb1e92 100644 --- a/tests/languages/node_test.py +++ b/tests/languages/node_test.py @@ -139,7 +139,7 @@ def test_node_with_user_config_set(tmp_path): test_node_hook_system(tmp_path) -@pytest.mark.parametrize('version', (C.DEFAULT, '18.13.0')) +@pytest.mark.parametrize('version', (C.DEFAULT, '18.14.0')) def test_node_hook_versions(tmp_path, version): _make_hello_world(tmp_path) ret = run_language(tmp_path, node, 'node-hello', version=version) From c68c6b944aafed8d7b7d75c0d6a0f4109d7dde50 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 07:23:52 +0000 Subject: [PATCH 215/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb969280c..309e9de5a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.11.0 + rev: v3.13.0 hooks: - id: pyupgrade args: [--py38-plus] From a4ab977cc36e06fff8a8c69cca652162407b55cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 08:58:04 +0000 Subject: [PATCH 216/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.4.0 → v2.5.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.4.0...v2.5.0) - [github.com/asottile/reorder-python-imports: v3.11.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.11.0...v3.12.0) - [github.com/asottile/pyupgrade: v3.13.0 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.14.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 309e9de5a..cccecb8e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,11 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.4.0 + rev: v2.5.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.11.0 + rev: v3.12.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + rev: v3.14.0 hooks: - id: pyupgrade args: [--py38-plus] From 997ea0ad52074c3e6474f3d99f76f7965e2d05f0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 9 Oct 2023 16:49:30 -0400 Subject: [PATCH 217/342] use sys.executable instead of echo.exe in parse_shebang the GHA runners now have echo.exe in a path with spaces --- tests/parse_shebang_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/parse_shebang_test.py b/tests/parse_shebang_test.py index dd97ca5d8..bd4384df2 100644 --- a/tests/parse_shebang_test.py +++ b/tests/parse_shebang_test.py @@ -133,17 +133,17 @@ def test_normalize_cmd_PATH(): def test_normalize_cmd_shebang(in_tmpdir): - echo = _echo_exe().replace(os.sep, '/') - path = write_executable(echo) - assert parse_shebang.normalize_cmd((path,)) == (echo, path) + us = sys.executable.replace(os.sep, '/') + path = write_executable(us) + assert parse_shebang.normalize_cmd((path,)) == (us, path) def test_normalize_cmd_PATH_shebang_full_path(in_tmpdir): - echo = _echo_exe().replace(os.sep, '/') - path = write_executable(echo) + us = sys.executable.replace(os.sep, '/') + path = write_executable(us) with bin_on_path(): ret = parse_shebang.normalize_cmd(('run',)) - assert ret == (echo, os.path.abspath(path)) + assert ret == (us, os.path.abspath(path)) def test_normalize_cmd_PATH_shebang_PATH(in_tmpdir): From 155c52134848b05b0092a446cdd2c336a03a85c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:33:33 +0000 Subject: [PATCH 218/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/asottile/pyupgrade: v3.14.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.14.0...v3.15.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cccecb8e2..5381cd611 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.14.0 + rev: v3.15.0 hooks: - id: pyupgrade args: [--py38-plus] From d988767b414495bdab9ea24532ad337e8ee3fd1f Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 13 Oct 2023 16:01:59 +0100 Subject: [PATCH 219/342] Improve hook duration timing --- pre_commit/commands/run.py | 4 ++-- tests/commands/run_test.py | 2 +- tests/commands/try_repo_test.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index c867799e8..241f6fe16 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -187,7 +187,7 @@ def _run_single_hook( if not hook.pass_filenames: filenames = () - time_before = time.time() + time_before = time.monotonic() language = languages[hook.language] with language.in_env(hook.prefix, hook.language_version): retcode, out = language.run_hook( @@ -199,7 +199,7 @@ def _run_single_hook( require_serial=hook.require_serial, color=use_color, ) - duration = round(time.time() - time_before, 2) or 0 + duration = round(time.monotonic() - time_before, 2) or 0 diff_after = _get_diff() # if the hook makes changes, fail the commit diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index dd15b94c5..4be8f3b9e 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -293,7 +293,7 @@ def test_verbose_duration(cap_out, store, in_git_dir, t1, t2, expected): write_config('.', {'repo': 'meta', 'hooks': [{'id': 'identity'}]}) cmd_output('git', 'add', '.') opts = run_opts(verbose=True) - with mock.patch.object(time, 'time', side_effect=(t1, t2)): + with mock.patch.object(time, 'monotonic', side_effect=(t1, t2)): ret, printed = _do_run(cap_out, store, str(in_git_dir), opts) assert ret == 0 assert expected in printed diff --git a/tests/commands/try_repo_test.py b/tests/commands/try_repo_test.py index 0b2db7e5a..c5f891ea7 100644 --- a/tests/commands/try_repo_test.py +++ b/tests/commands/try_repo_test.py @@ -43,7 +43,7 @@ def _run_try_repo(tempdir_factory, **kwargs): def test_try_repo_repo_only(cap_out, tempdir_factory): - with mock.patch.object(time, 'time', return_value=0.0): + with mock.patch.object(time, 'monotonic', return_value=0.0): _run_try_repo(tempdir_factory, verbose=True) start, config, rest = _get_out(cap_out) assert start == '' From 61cc55a59cc63c7405dd3cd7c96b169fdb750333 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 13 Oct 2023 11:57:20 -0400 Subject: [PATCH 220/342] v3.5.0 --- CHANGELOG.md | 17 +++++++++++++++++ setup.cfg | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2ef0de1..7a1b61a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +3.5.0 - 2023-10-13 +================== + +### Features +- Improve performance of `check-hooks-apply` and `check-useless-excludes`. + - #2998 PR by @mxr. + - #2935 issue by @mxr. + +### Fixes +- Use `time.monotonic()` for more accurate hook timing. + - #3024 PR by @adamchainz. + +### Migrating +- Require npm 6.x+ for `language: node` hooks. + - #2996 PR by @RoelAdriaans. + - #1983 issue by @henryiii. + 3.4.0 - 2023-09-02 ================== diff --git a/setup.cfg b/setup.cfg index cfaa61bbb..7543835d7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.4.0 +version = 3.5.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 44b625ebd3c3f239737ee1ea0603daffbd61c4e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:03:36 +0000 Subject: [PATCH 221/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.6.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5381cd611..0ef18ba32 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.6.0 hooks: - id: mypy additional_dependencies: [types-all] From c69e32e925dc4ef160aa9ecde13bea73f2175803 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:28:04 +0000 Subject: [PATCH 222/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.6.0 → v1.6.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.0...v1.6.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ef18ba32..46dce4813 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.0 + rev: v1.6.1 hooks: - id: mypy additional_dependencies: [types-all] From 7f15dc75eea8ad1017c9870e1468d6a9e5339ac3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Oct 2023 14:20:37 -0400 Subject: [PATCH 223/342] python3.9+ --- .github/actions/pre-test/action.yml | 2 +- .github/workflows/languages.yaml | 4 ++-- .github/workflows/main.yml | 8 ++++---- .pre-commit-config.yaml | 4 ++-- pre_commit/clientlib.py | 2 +- pre_commit/commands/autoupdate.py | 2 +- pre_commit/commands/hook_impl.py | 2 +- pre_commit/commands/run.py | 10 +++++----- pre_commit/commands/validate_config.py | 2 +- pre_commit/commands/validate_manifest.py | 2 +- pre_commit/envcontext.py | 9 ++++----- pre_commit/error_handler.py | 2 +- pre_commit/file_lock.py | 2 +- pre_commit/git.py | 2 +- pre_commit/hook.py | 2 +- pre_commit/lang_base.py | 4 ++-- pre_commit/languages/conda.py | 4 ++-- pre_commit/languages/coursier.py | 4 ++-- pre_commit/languages/dart.py | 4 ++-- pre_commit/languages/docker.py | 2 +- pre_commit/languages/docker_image.py | 2 +- pre_commit/languages/dotnet.py | 4 ++-- pre_commit/languages/fail.py | 2 +- pre_commit/languages/golang.py | 4 ++-- pre_commit/languages/haskell.py | 4 ++-- pre_commit/languages/lua.py | 4 ++-- pre_commit/languages/node.py | 4 ++-- pre_commit/languages/perl.py | 4 ++-- pre_commit/languages/pygrep.py | 4 ++-- pre_commit/languages/python.py | 6 +++--- pre_commit/languages/r.py | 4 ++-- pre_commit/languages/ruby.py | 4 ++-- pre_commit/languages/rust.py | 4 ++-- pre_commit/languages/script.py | 2 +- pre_commit/languages/swift.py | 4 ++-- pre_commit/logging_handler.py | 2 +- pre_commit/main.py | 2 +- pre_commit/meta_hooks/check_hooks_apply.py | 2 +- pre_commit/meta_hooks/check_useless_excludes.py | 4 ++-- pre_commit/meta_hooks/identity.py | 2 +- pre_commit/parse_shebang.py | 2 +- pre_commit/repository.py | 2 +- pre_commit/staged_files_only.py | 2 +- pre_commit/store.py | 4 ++-- pre_commit/util.py | 2 +- pre_commit/xargs.py | 8 ++++---- setup.cfg | 2 +- testing/language_helpers.py | 2 +- testing/make-archives | 2 +- tests/commands/run_test.py | 2 +- 50 files changed, 84 insertions(+), 85 deletions(-) diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml index 9d1eb2de6..b70c942fe 100644 --- a/.github/actions/pre-test/action.yml +++ b/.github/actions/pre-test/action.yml @@ -6,4 +6,4 @@ runs: using: composite steps: - uses: asottile/workflows/.github/actions/latest-git@v1.4.0 - if: inputs.env == 'py38' && runner.os == 'Linux' + if: inputs.env == 'py39' && runner.os == 'Linux' diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index 5a6ae9cd7..7d50535f8 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: install deps run: python -mpip install -e . -r requirements-dev.txt - name: vars @@ -39,7 +39,7 @@ jobs: - uses: asottile/workflows/.github/actions/fast-checkout@v1.4.0 - uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - run: echo "$CONDA\Scripts" >> "$GITHUB_PATH" shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 903d24780..6e32f6c6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,12 +12,12 @@ concurrency: jobs: main-windows: - uses: asottile/workflows/.github/workflows/tox.yml@v1.4.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0 with: - env: '["py38"]' + env: '["py39"]' os: windows-latest main-linux: - uses: asottile/workflows/.github/workflows/tox.yml@v1.4.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0 with: - env: '["py38", "py39", "py310"]' + env: '["py39", "py310", "py311"]' os: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5381cd611..ca2dc42b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) - args: [--py38-plus, --add-import, 'from __future__ import annotations'] + args: [--py39-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma rev: v3.1.0 hooks: @@ -27,7 +27,7 @@ repos: rev: v3.15.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 rev: v2.0.4 hooks: diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index d0651cae2..9f41bf4b2 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -5,9 +5,9 @@ import re import shlex import sys +from collections.abc import Sequence from typing import Any from typing import NamedTuple -from typing import Sequence import cfgv from identify.identify import ALL_TAGS diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index e7725fdc4..aa0c5e25e 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -4,9 +4,9 @@ import os.path import re import tempfile +from collections.abc import Sequence from typing import Any from typing import NamedTuple -from typing import Sequence import pre_commit.constants as C from pre_commit import git diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index dab2135d4..49a80b7b3 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -4,7 +4,7 @@ import os.path import subprocess import sys -from typing import Sequence +from collections.abc import Sequence from pre_commit.commands.run import run from pre_commit.envcontext import envcontext diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 41ba4ecf0..076f16d8f 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -9,11 +9,11 @@ import subprocess import time import unicodedata +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import MutableMapping +from collections.abc import Sequence from typing import Any -from typing import Generator -from typing import Iterable -from typing import MutableMapping -from typing import Sequence from identify.identify import tags_from_path @@ -74,7 +74,7 @@ class Classifier: def __init__(self, filenames: Iterable[str]) -> None: self.filenames = [f for f in filenames if os.path.lexists(f)] - @functools.lru_cache(maxsize=None) + @functools.cache def _types_for_file(self, filename: str) -> set[str]: return tags_from_path(filename) diff --git a/pre_commit/commands/validate_config.py b/pre_commit/commands/validate_config.py index 24bd3135e..b3de635b1 100644 --- a/pre_commit/commands/validate_config.py +++ b/pre_commit/commands/validate_config.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from pre_commit import clientlib diff --git a/pre_commit/commands/validate_manifest.py b/pre_commit/commands/validate_manifest.py index 419031a9b..8493c6e1e 100644 --- a/pre_commit/commands/validate_manifest.py +++ b/pre_commit/commands/validate_manifest.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from pre_commit import clientlib diff --git a/pre_commit/envcontext.py b/pre_commit/envcontext.py index 4f5956016..1f816cea9 100644 --- a/pre_commit/envcontext.py +++ b/pre_commit/envcontext.py @@ -3,10 +3,9 @@ import contextlib import enum import os -from typing import Generator -from typing import MutableMapping +from collections.abc import Generator +from collections.abc import MutableMapping from typing import NamedTuple -from typing import Tuple from typing import Union _Unset = enum.Enum('_Unset', 'UNSET') @@ -18,9 +17,9 @@ class Var(NamedTuple): default: str = '' -SubstitutionT = Tuple[Union[str, Var], ...] +SubstitutionT = tuple[Union[str, Var], ...] ValueT = Union[str, _Unset, SubstitutionT] -PatchesT = Tuple[Tuple[str, ValueT], ...] +PatchesT = tuple[tuple[str, ValueT], ...] def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str: diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py index d740ee3e4..73e608b71 100644 --- a/pre_commit/error_handler.py +++ b/pre_commit/error_handler.py @@ -5,7 +5,7 @@ import os.path import sys import traceback -from typing import Generator +from collections.abc import Generator from typing import IO import pre_commit.constants as C diff --git a/pre_commit/file_lock.py b/pre_commit/file_lock.py index f67a58644..d3dafb4da 100644 --- a/pre_commit/file_lock.py +++ b/pre_commit/file_lock.py @@ -3,8 +3,8 @@ import contextlib import errno import sys +from collections.abc import Generator from typing import Callable -from typing import Generator if sys.platform == 'win32': # pragma: no cover (windows) diff --git a/pre_commit/git.py b/pre_commit/git.py index 333dc7ba3..19aac3872 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -3,7 +3,7 @@ import logging import os.path import sys -from typing import Mapping +from collections.abc import Mapping from pre_commit.errors import FatalError from pre_commit.util import CalledProcessError diff --git a/pre_commit/hook.py b/pre_commit/hook.py index 6d436ca30..309cd5be3 100644 --- a/pre_commit/hook.py +++ b/pre_commit/hook.py @@ -1,9 +1,9 @@ from __future__ import annotations import logging +from collections.abc import Sequence from typing import Any from typing import NamedTuple -from typing import Sequence from pre_commit.prefix import Prefix diff --git a/pre_commit/lang_base.py b/pre_commit/lang_base.py index 4a993eaa4..5303948b5 100644 --- a/pre_commit/lang_base.py +++ b/pre_commit/lang_base.py @@ -5,12 +5,12 @@ import random import re import shlex +from collections.abc import Generator +from collections.abc import Sequence from typing import Any from typing import ContextManager -from typing import Generator from typing import NoReturn from typing import Protocol -from typing import Sequence import pre_commit.constants as C from pre_commit import parse_shebang diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 41c355e77..80b3e1507 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -3,8 +3,8 @@ import contextlib import os import sys -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 9c5fbfe24..6558bf6b8 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -2,8 +2,8 @@ import contextlib import os.path -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index e8539caa2..129ac5918 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -4,8 +4,8 @@ import os.path import shutil import tempfile -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 8e53ca9e3..26328515e 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -3,7 +3,7 @@ import hashlib import json import os -from typing import Sequence +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.prefix import Prefix diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index 26f006e4a..a1a2c169a 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.languages.docker import docker_cmd diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index e9568f222..e1202c4f2 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -6,8 +6,8 @@ import tempfile import xml.etree.ElementTree import zipfile -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index a8ec6a53d..6ac4d7675 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.prefix import Prefix diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index bea91e9bd..4c13d8f9c 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -12,11 +12,11 @@ import urllib.error import urllib.request import zipfile +from collections.abc import Generator +from collections.abc import Sequence from typing import ContextManager -from typing import Generator from typing import IO from typing import Protocol -from typing import Sequence import pre_commit.constants as C from pre_commit import lang_base diff --git a/pre_commit/languages/haskell.py b/pre_commit/languages/haskell.py index 76442eb02..c6945c822 100644 --- a/pre_commit/languages/haskell.py +++ b/pre_commit/languages/haskell.py @@ -2,8 +2,8 @@ import contextlib import os.path -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index 12d066140..a475ec99c 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -3,8 +3,8 @@ import contextlib import os import sys -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 3e22dc78e..d49c0e326 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -4,8 +4,8 @@ import functools import os import sys -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence import pre_commit.constants as C from pre_commit import lang_base diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 2a7f16290..61b1d114b 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -3,8 +3,8 @@ import contextlib import os import shlex -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py index ec55560b0..72a9345fa 100644 --- a/pre_commit/languages/pygrep.py +++ b/pre_commit/languages/pygrep.py @@ -3,9 +3,9 @@ import argparse import re import sys +from collections.abc import Sequence +from re import Pattern from typing import NamedTuple -from typing import Pattern -from typing import Sequence from pre_commit import lang_base from pre_commit import output diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 3ef343608..e5bac9fa8 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -4,8 +4,8 @@ import functools import os import sys -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence import pre_commit.constants as C from pre_commit import lang_base @@ -24,7 +24,7 @@ run_hook = lang_base.basic_run_hook -@functools.lru_cache(maxsize=None) +@functools.cache def _version_info(exe: str) -> str: prog = 'import sys;print(".".join(str(p) for p in sys.version_info))' try: diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 6feb06523..93b62bd53 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -6,8 +6,8 @@ import shutil import tempfile import textwrap -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index c88269f24..3ed15cfcc 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -6,9 +6,9 @@ import os.path import shutil import tarfile -from typing import Generator +from collections.abc import Generator +from collections.abc import Sequence from typing import IO -from typing import Sequence import pre_commit.constants as C from pre_commit import lang_base diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 7eec0e7d6..241146c57 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -7,8 +7,8 @@ import sys import tempfile import urllib.request -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence import pre_commit.constants as C from pre_commit import lang_base diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 89a3ab2d6..1eaa1e270 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Sequence +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.prefix import Prefix diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index f16bb0451..f7bfe84c5 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -2,8 +2,8 @@ import contextlib import os -from typing import Generator -from typing import Sequence +from collections.abc import Generator +from collections.abc import Sequence from pre_commit import lang_base from pre_commit.envcontext import envcontext diff --git a/pre_commit/logging_handler.py b/pre_commit/logging_handler.py index 1b68fc7d6..cd33953d7 100644 --- a/pre_commit/logging_handler.py +++ b/pre_commit/logging_handler.py @@ -2,7 +2,7 @@ import contextlib import logging -from typing import Generator +from collections.abc import Generator from pre_commit import color from pre_commit import output diff --git a/pre_commit/main.py b/pre_commit/main.py index 9dfce2c25..18c978a84 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -4,7 +4,7 @@ import logging import os import sys -from typing import Sequence +from collections.abc import Sequence import pre_commit.constants as C from pre_commit import clientlib diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py index 7f491a209..84c142b45 100644 --- a/pre_commit/meta_hooks/check_hooks_apply.py +++ b/pre_commit/meta_hooks/check_hooks_apply.py @@ -1,7 +1,7 @@ from __future__ import annotations import argparse -from typing import Sequence +from collections.abc import Sequence import pre_commit.constants as C from pre_commit import git diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py index 8b0c106a3..664251a44 100644 --- a/pre_commit/meta_hooks/check_useless_excludes.py +++ b/pre_commit/meta_hooks/check_useless_excludes.py @@ -2,8 +2,8 @@ import argparse import re -from typing import Iterable -from typing import Sequence +from collections.abc import Iterable +from collections.abc import Sequence from cfgv import apply_defaults diff --git a/pre_commit/meta_hooks/identity.py b/pre_commit/meta_hooks/identity.py index 72ee440bc..3e20bbc68 100644 --- a/pre_commit/meta_hooks/identity.py +++ b/pre_commit/meta_hooks/identity.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from typing import Sequence +from collections.abc import Sequence from pre_commit import output diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py index 3ee04e8d7..043a9b5d7 100644 --- a/pre_commit/parse_shebang.py +++ b/pre_commit/parse_shebang.py @@ -1,7 +1,7 @@ from __future__ import annotations import os.path -from typing import Mapping +from collections.abc import Mapping from typing import NoReturn from identify.identify import parse_shebang_from_file diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 040f238f0..439a09b4f 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -4,8 +4,8 @@ import logging import os import shlex +from collections.abc import Sequence from typing import Any -from typing import Sequence import pre_commit.constants as C from pre_commit.all_languages import languages diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index 881235656..fd28e1c22 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -4,7 +4,7 @@ import logging import os.path import time -from typing import Generator +from collections.abc import Generator from pre_commit import git from pre_commit.errors import FatalError diff --git a/pre_commit/store.py b/pre_commit/store.py index 487e3e798..84bc09a4c 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -5,9 +5,9 @@ import os.path import sqlite3 import tempfile +from collections.abc import Generator +from collections.abc import Sequence from typing import Callable -from typing import Generator -from typing import Sequence import pre_commit.constants as C from pre_commit import file_lock diff --git a/pre_commit/util.py b/pre_commit/util.py index 4f8e8357d..1e3112693 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -8,10 +8,10 @@ import stat import subprocess import sys +from collections.abc import Generator from types import TracebackType from typing import Any from typing import Callable -from typing import Generator from pre_commit import parse_shebang diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index a7493c01d..22580f595 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -7,12 +7,12 @@ import os import subprocess import sys +from collections.abc import Generator +from collections.abc import Iterable +from collections.abc import MutableMapping +from collections.abc import Sequence from typing import Any from typing import Callable -from typing import Generator -from typing import Iterable -from typing import MutableMapping -from typing import Sequence from typing import TypeVar from pre_commit import parse_shebang diff --git a/setup.cfg b/setup.cfg index 7543835d7..3110881fc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ install_requires = nodeenv>=0.11.1 pyyaml>=5.1 virtualenv>=20.10.0 -python_requires = >=3.8 +python_requires = >=3.9 [options.packages.find] exclude = diff --git a/testing/language_helpers.py b/testing/language_helpers.py index ead8dae27..05c94ebca 100644 --- a/testing/language_helpers.py +++ b/testing/language_helpers.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import Sequence +from collections.abc import Sequence from pre_commit.lang_base import Language from pre_commit.prefix import Prefix diff --git a/testing/make-archives b/testing/make-archives index 8ec05e2de..3c7ab9dd0 100755 --- a/testing/make-archives +++ b/testing/make-archives @@ -8,7 +8,7 @@ import shutil import subprocess import tarfile import tempfile -from typing import Sequence +from collections.abc import Sequence # This is a script for generating the tarred resources for git repo diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 6a0cd8556..e36a3ca9c 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -4,7 +4,7 @@ import shlex import sys import time -from typing import MutableMapping +from collections.abc import MutableMapping from unittest import mock import pytest From 75f2710bd4ffdce232fd1a37e9accbcac3ade14a Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Oct 2023 14:39:49 -0400 Subject: [PATCH 224/342] 3.13 removed the simpler importlib.resources api --- pre_commit/languages/ruby.py | 3 ++- pre_commit/util.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 3ed15cfcc..0438ae095 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -25,7 +25,8 @@ def _resource_bytesio(filename: str) -> IO[bytes]: - return importlib.resources.open_binary('pre_commit.resources', filename) + files = importlib.resources.files('pre_commit.resources') + return files.joinpath(filename).open('rb') @functools.lru_cache(maxsize=1) diff --git a/pre_commit/util.py b/pre_commit/util.py index 1e3112693..8f5958414 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -36,7 +36,8 @@ def clean_path_on_failure(path: str) -> Generator[None, None, None]: def resource_text(filename: str) -> str: - return importlib.resources.read_text('pre_commit.resources', filename) + files = importlib.resources.files('pre_commit.resources') + return files.joinpath(filename).read_text() def make_executable(filename: str) -> None: From 1d474994e0d4276c98f5ab22f7f84e5570318f8d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:35:35 +0000 Subject: [PATCH 225/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.7.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 858be1bac..5547ec1f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.1 + rev: v1.7.0 hooks: - id: mypy additional_dependencies: [types-all] From e36cefc8bd43aaee1686d16e31ecb98f576fe121 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:01:19 +0000 Subject: [PATCH 226/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.7.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.7.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5547ec1f0..4433e4e2f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.0 + rev: v1.7.1 hooks: - id: mypy additional_dependencies: [types-all] From cffabe54be63f0fd05b42ae73842387d07110feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 1 Dec 2023 17:02:12 -0600 Subject: [PATCH 227/342] Address deprecation warning in `shutil.rmtree(onerror=...)` --- .github/workflows/main.yml | 2 +- pre_commit/util.py | 46 ++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6e32f6c6b..2355b6620 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,5 +19,5 @@ jobs: main-linux: uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0 with: - env: '["py39", "py310", "py311"]' + env: '["py39", "py310", "py311", "py312"]' os: ubuntu-latest diff --git a/pre_commit/util.py b/pre_commit/util.py index 8f5958414..b3682d4f7 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -202,24 +202,36 @@ def cmd_output_p( cmd_output_p = cmd_output_b -def rmtree(path: str) -> None: - """On windows, rmtree fails for readonly dirs.""" - def handle_remove_readonly( - func: Callable[..., Any], - path: str, - exc: tuple[type[OSError], OSError, TracebackType], +def _handle_readonly( + func: Callable[[str], object], + path: str, + exc: OSError, +) -> None: + if ( + func in (os.rmdir, os.remove, os.unlink) and + exc.errno in {errno.EACCES, errno.EPERM} + ): + for p in (path, os.path.dirname(path)): + os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR) + func(path) + else: + raise + + +if sys.version_info < (3, 12): # pragma: <3.12 cover + def _handle_readonly_old( + func: Callable[[str], object], + path: str, + excinfo: tuple[type[OSError], OSError, TracebackType], ) -> None: - excvalue = exc[1] - if ( - func in (os.rmdir, os.remove, os.unlink) and - excvalue.errno in {errno.EACCES, errno.EPERM} - ): - for p in (path, os.path.dirname(path)): - os.chmod(p, os.stat(p).st_mode | stat.S_IWUSR) - func(path) - else: - raise - shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly) + return _handle_readonly(func, path, excinfo[1]) + + def rmtree(path: str) -> None: + shutil.rmtree(path, ignore_errors=False, onerror=_handle_readonly_old) +else: # pragma: >=3.12 cover + def rmtree(path: str) -> None: + """On windows, rmtree fails for readonly dirs.""" + shutil.rmtree(path, ignore_errors=False, onexc=_handle_readonly) def win_exe(s: str) -> str: From 047439abffb164edd5b49e50439fd63a625be3da Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 9 Dec 2023 15:34:16 -0500 Subject: [PATCH 228/342] attempt minimum_pre_commit_version first when parsing configs --- pre_commit/clientlib.py | 20 ++-- pre_commit/repository.py | 10 -- tests/clientlib_test.py | 195 +++++++++++++++++++++------------------ tests/repository_test.py | 28 ------ 4 files changed, 119 insertions(+), 134 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 9f41bf4b2..a49465e89 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -102,6 +102,13 @@ def apply_default(self, dct: dict[str, Any]) -> None: MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', + # check first in case it uses some newer, incompatible feature + cfgv.Optional( + 'minimum_pre_commit_version', + cfgv.check_and(cfgv.check_string, check_min_version), + '0', + ), + cfgv.Required('id', cfgv.check_string), cfgv.Required('name', cfgv.check_string), cfgv.Required('entry', cfgv.check_string), @@ -124,7 +131,6 @@ def apply_default(self, dct: dict[str, Any]) -> None: cfgv.Optional('description', cfgv.check_string, ''), cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT), cfgv.Optional('log_file', cfgv.check_string, ''), - cfgv.Optional('minimum_pre_commit_version', cfgv.check_string, '0'), cfgv.Optional('require_serial', cfgv.check_bool, False), StagesMigration('stages', []), cfgv.Optional('verbose', cfgv.check_bool, False), @@ -345,6 +351,13 @@ def check(self, dct: dict[str, Any]) -> None: CONFIG_SCHEMA = cfgv.Map( 'Config', None, + # check first in case it uses some newer, incompatible feature + cfgv.Optional( + 'minimum_pre_commit_version', + cfgv.check_and(cfgv.check_string, check_min_version), + '0', + ), + cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)), cfgv.Optional( 'default_install_hook_types', @@ -358,11 +371,6 @@ def check(self, dct: dict[str, Any]) -> None: cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('exclude', check_string_regex, '^$'), cfgv.Optional('fail_fast', cfgv.check_bool, False), - cfgv.Optional( - 'minimum_pre_commit_version', - cfgv.check_and(cfgv.check_string, check_min_version), - '0', - ), cfgv.WarnAdditionalKeys( ( 'repos', diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 439a09b4f..aa8418563 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -12,7 +12,6 @@ from pre_commit.clientlib import load_manifest from pre_commit.clientlib import LOCAL from pre_commit.clientlib import META -from pre_commit.clientlib import parse_version from pre_commit.hook import Hook from pre_commit.lang_base import environment_dir from pre_commit.prefix import Prefix @@ -124,15 +123,6 @@ def _hook( for dct in rest: ret.update(dct) - version = ret['minimum_pre_commit_version'] - if parse_version(version) > parse_version(C.VERSION): - logger.error( - f'The hook `{ret["id"]}` requires pre-commit version {version} ' - f'but version {C.VERSION} is installed. ' - f'Perhaps run `pip install --upgrade pre-commit`.', - ) - exit(1) - lang = ret['language'] if ret['language_version'] == C.DEFAULT: ret['language_version'] = root_config['default_language_version'][lang] diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 568b2e974..eaa8a044c 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -40,56 +40,51 @@ def test_check_type_tag_success(): @pytest.mark.parametrize( - ('config_obj', 'expected'), ( - ( - { - 'repos': [{ - 'repo': 'git@github.com:pre-commit/pre-commit-hooks', - 'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37', - 'hooks': [{'id': 'pyflakes', 'files': '\\.py$'}], - }], - }, - True, - ), - ( - { - 'repos': [{ - 'repo': 'git@github.com:pre-commit/pre-commit-hooks', - 'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37', - 'hooks': [ - { - 'id': 'pyflakes', - 'files': '\\.py$', - 'args': ['foo', 'bar', 'baz'], - }, - ], - }], - }, - True, - ), - ( - { - 'repos': [{ - 'repo': 'git@github.com:pre-commit/pre-commit-hooks', - 'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37', - 'hooks': [ - { - 'id': 'pyflakes', - 'files': '\\.py$', - # Exclude pattern must be a string - 'exclude': 0, - 'args': ['foo', 'bar', 'baz'], - }, - ], - }], - }, - False, - ), + 'cfg', + ( + { + 'repos': [{ + 'repo': 'git@github.com:pre-commit/pre-commit-hooks', + 'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37', + 'hooks': [{'id': 'pyflakes', 'files': '\\.py$'}], + }], + }, + { + 'repos': [{ + 'repo': 'git@github.com:pre-commit/pre-commit-hooks', + 'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37', + 'hooks': [ + { + 'id': 'pyflakes', + 'files': '\\.py$', + 'args': ['foo', 'bar', 'baz'], + }, + ], + }], + }, ), ) -def test_config_valid(config_obj, expected): - ret = is_valid_according_to_schema(config_obj, CONFIG_SCHEMA) - assert ret is expected +def test_config_valid(cfg): + assert is_valid_according_to_schema(cfg, CONFIG_SCHEMA) + + +def test_invalid_config_wrong_type(): + cfg = { + 'repos': [{ + 'repo': 'git@github.com:pre-commit/pre-commit-hooks', + 'rev': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37', + 'hooks': [ + { + 'id': 'pyflakes', + 'files': '\\.py$', + # Exclude pattern must be a string + 'exclude': 0, + 'args': ['foo', 'bar', 'baz'], + }, + ], + }], + } + assert not is_valid_according_to_schema(cfg, CONFIG_SCHEMA) def test_local_hooks_with_rev_fails(): @@ -198,14 +193,13 @@ def test_warn_mutable_rev_conditional(): ), ) def test_sensible_regex_validators_dont_pass_none(validator_cls): - key = 'files' + validator = validator_cls('files', cfgv.check_string) with pytest.raises(cfgv.ValidationError) as excinfo: - validator = validator_cls(key, cfgv.check_string) - validator.check({key: None}) + validator.check({'files': None}) assert str(excinfo.value) == ( '\n' - f'==> At key: {key}' + '==> At key: files' '\n' '=====> Expected string got NoneType' ) @@ -298,46 +292,36 @@ def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning): @pytest.mark.parametrize( - ('manifest_obj', 'expected'), + 'manifest_obj', ( - ( - [{ - 'id': 'a', - 'name': 'b', - 'entry': 'c', - 'language': 'python', - 'files': r'\.py$', - }], - True, - ), - ( - [{ - 'id': 'a', - 'name': 'b', - 'entry': 'c', - 'language': 'python', - 'language_version': 'python3.4', - 'files': r'\.py$', - }], - True, - ), - ( - # A regression in 0.13.5: always_run and files are permissible - [{ - 'id': 'a', - 'name': 'b', - 'entry': 'c', - 'language': 'python', - 'files': '', - 'always_run': True, - }], - True, - ), + [{ + 'id': 'a', + 'name': 'b', + 'entry': 'c', + 'language': 'python', + 'files': r'\.py$', + }], + [{ + 'id': 'a', + 'name': 'b', + 'entry': 'c', + 'language': 'python', + 'language_version': 'python3.4', + 'files': r'\.py$', + }], + # A regression in 0.13.5: always_run and files are permissible + [{ + 'id': 'a', + 'name': 'b', + 'entry': 'c', + 'language': 'python', + 'files': '', + 'always_run': True, + }], ), ) -def test_valid_manifests(manifest_obj, expected): - ret = is_valid_according_to_schema(manifest_obj, MANIFEST_SCHEMA) - assert ret is expected +def test_valid_manifests(manifest_obj): + assert is_valid_according_to_schema(manifest_obj, MANIFEST_SCHEMA) @pytest.mark.parametrize( @@ -393,8 +377,39 @@ def test_parse_version(): def test_minimum_pre_commit_version_failing(): + cfg = {'repos': [], 'minimum_pre_commit_version': '999'} + with pytest.raises(cfgv.ValidationError) as excinfo: + cfgv.validate(cfg, CONFIG_SCHEMA) + assert str(excinfo.value) == ( + f'\n' + f'==> At Config()\n' + f'==> At key: minimum_pre_commit_version\n' + f'=====> pre-commit version 999 is required but version {C.VERSION} ' + f'is installed. Perhaps run `pip install --upgrade pre-commit`.' + ) + + +def test_minimum_pre_commit_version_failing_in_config(): + cfg = {'repos': [sample_local_config()]} + cfg['repos'][0]['hooks'][0]['minimum_pre_commit_version'] = '999' + with pytest.raises(cfgv.ValidationError) as excinfo: + cfgv.validate(cfg, CONFIG_SCHEMA) + assert str(excinfo.value) == ( + f'\n' + f'==> At Config()\n' + f'==> At key: repos\n' + f"==> At Repository(repo='local')\n" + f'==> At key: hooks\n' + f"==> At Hook(id='do_not_commit')\n" + f'==> At key: minimum_pre_commit_version\n' + f'=====> pre-commit version 999 is required but version {C.VERSION} ' + f'is installed. Perhaps run `pip install --upgrade pre-commit`.' + ) + + +def test_minimum_pre_commit_version_failing_before_other_error(): + cfg = {'repos': 5, 'minimum_pre_commit_version': '999'} with pytest.raises(cfgv.ValidationError) as excinfo: - cfg = {'repos': [], 'minimum_pre_commit_version': '999'} cfgv.validate(cfg, CONFIG_SCHEMA) assert str(excinfo.value) == ( f'\n' diff --git a/tests/repository_test.py b/tests/repository_test.py index b8dde99b4..ac065ec40 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -9,7 +9,6 @@ import cfgv import pytest -import re_assert import pre_commit.constants as C from pre_commit import lang_base @@ -27,7 +26,6 @@ from pre_commit.util import cmd_output_b from testing.fixtures import make_config_from_repo from testing.fixtures import make_repo -from testing.fixtures import modify_manifest from testing.language_helpers import run_language from testing.util import cwd from testing.util import get_resource_path @@ -433,32 +431,6 @@ def test_hook_id_not_present(tempdir_factory, store, caplog): ) -def test_too_new_version(tempdir_factory, store, caplog): - path = make_repo(tempdir_factory, 'script_hooks_repo') - with modify_manifest(path) as manifest: - manifest[0]['minimum_pre_commit_version'] = '999.0.0' - config = make_config_from_repo(path) - with pytest.raises(SystemExit): - _get_hook(config, store, 'bash_hook') - _, msg = caplog.messages - pattern = re_assert.Matches( - r'^The hook `bash_hook` requires pre-commit version 999\.0\.0 but ' - r'version \d+\.\d+\.\d+ is installed. ' - r'Perhaps run `pip install --upgrade pre-commit`\.$', - ) - pattern.assert_matches(msg) - - -@pytest.mark.parametrize('version', ('0.1.0', C.VERSION)) -def test_versions_ok(tempdir_factory, store, version): - path = make_repo(tempdir_factory, 'script_hooks_repo') - with modify_manifest(path) as manifest: - manifest[0]['minimum_pre_commit_version'] = version - config = make_config_from_repo(path) - # Should succeed - _get_hook(config, store, 'bash_hook') - - def test_manifest_hooks(tempdir_factory, store): path = make_repo(tempdir_factory, 'script_hooks_repo') config = make_config_from_repo(path) From 08478ec176b705d17e3f7b0608d155e9dadff9bf Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 9 Dec 2023 16:04:25 -0500 Subject: [PATCH 229/342] python 3.9+: use removeprefix --- pre_commit/languages/python.py | 4 ++-- pre_commit/languages/rust.py | 2 +- tests/commands/install_uninstall_test.py | 10 ++++++---- tests/store_test.py | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index e5bac9fa8..9f4bf69a2 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -65,7 +65,7 @@ def _find_by_py_launcher( version: str, ) -> str | None: # pragma: no cover (windows only) if version.startswith('python'): - num = version[len('python'):] + num = version.removeprefix('python') cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)') env = dict(os.environ, PYTHONIOENCODING='UTF-8') try: @@ -124,7 +124,7 @@ def _sys_executable_matches(version: str) -> bool: return False try: - info = tuple(int(p) for p in version[len('python'):].split('.')) + info = tuple(int(p) for p in version.removeprefix('python').split('.')) except ValueError: return False diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 241146c57..7b04d6c25 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -134,7 +134,7 @@ def install_environment( packages_to_install: set[tuple[str, ...]] = {('--path', '.')} for cli_dep in cli_deps: - cli_dep = cli_dep[len('cli:'):] + cli_dep = cli_dep.removeprefix('cli:') package, _, crate_version = cli_dep.partition(':') if crate_version != '': packages_to_install.add((package, '--version', crate_version)) diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index 8b0d3ece4..9eb0e741a 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -349,8 +349,9 @@ def test_install_existing_hooks_no_overwrite(tempdir_factory, store): # We should run both the legacy and pre-commit hooks ret, output = _get_commit_output(tempdir_factory) assert ret == 0 - assert output.startswith('legacy hook\n') - NORMAL_PRE_COMMIT_RUN.assert_matches(output[len('legacy hook\n'):]) + legacy = 'legacy hook\n' + assert output.startswith(legacy) + NORMAL_PRE_COMMIT_RUN.assert_matches(output.removeprefix(legacy)) def test_legacy_overwriting_legacy_hook(tempdir_factory, store): @@ -375,8 +376,9 @@ def test_install_existing_hook_no_overwrite_idempotent(tempdir_factory, store): # We should run both the legacy and pre-commit hooks ret, output = _get_commit_output(tempdir_factory) assert ret == 0 - assert output.startswith('legacy hook\n') - NORMAL_PRE_COMMIT_RUN.assert_matches(output[len('legacy hook\n'):]) + legacy = 'legacy hook\n' + assert output.startswith(legacy) + NORMAL_PRE_COMMIT_RUN.assert_matches(output.removeprefix(legacy)) def test_install_with_existing_non_utf8_script(tmpdir, store): diff --git a/tests/store_test.py b/tests/store_test.py index eaab94000..45ec73272 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -185,7 +185,7 @@ def test_db_repo_name(store): def test_local_resources_reflects_reality(): on_disk = { - res[len('empty_template_'):] + res.removeprefix('empty_template_') for res in os.listdir('pre_commit/resources') if res.startswith('empty_template_') } From 9c9983dba00bf67d1b2625f1f0e9112afc063849 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 9 Dec 2023 16:24:52 -0500 Subject: [PATCH 230/342] v3.6.0 --- CHANGELOG.md | 18 ++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a1b61a49..340ac476d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +3.6.0 - 2023-12-09 +================== + +### Features +- Check `minimum_pre_commit_version` first when parsing configs. + - #3092 PR by @asottile. + +### Fixes +- Fix deprecation warnings for `importlib.resources`. + - #3043 PR by @asottile. +- Fix deprecation warnings for rmtree. + - #3079 PR by @edgarrmondragon. + +### Updating +- Drop support for python<3.9. + - #3042 PR by @asottile. + - #3093 PR by @asottile. + 3.5.0 - 2023-10-13 ================== diff --git a/setup.cfg b/setup.cfg index 3110881fc..24b94e2eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.5.0 +version = 3.6.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 9cce2834221364d4287a38469632c835142dbd62 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:20:03 +0000 Subject: [PATCH 231/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4433e4e2f..2245fea10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.1 + rev: v1.8.0 hooks: - id: mypy additional_dependencies: [types-all] From 9682f93e317639846cdae13b828b3d07d35e3eed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:21:06 +0000 Subject: [PATCH 232/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 6.1.0 → 7.0.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2245fea10..9cbda1019 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From 3388e2dbdf8f95d280b837db8cb9e4f7e7680bd0 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 12 Jan 2024 17:30:01 +0100 Subject: [PATCH 233/342] Pop PYTHONEXECUTABLE --- pre_commit/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pre_commit/main.py b/pre_commit/main.py index 18c978a84..50a2e5196 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -37,6 +37,9 @@ # pyvenv os.environ.pop('__PYVENV_LAUNCHER__', None) +# https://github.com/getsentry/snuba/pull/5388 +os.environ.pop("PYTHONEXECUTABLE", None) + COMMANDS_NO_GIT = { 'clean', 'gc', 'init-templatedir', 'sample-config', 'validate-config', 'validate-manifest', From 96e0712f432ebf118a8f2963570586590d832e85 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 16:32:43 +0000 Subject: [PATCH 234/342] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pre_commit/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/main.py b/pre_commit/main.py index 50a2e5196..559c927c9 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -38,7 +38,7 @@ os.environ.pop('__PYVENV_LAUNCHER__', None) # https://github.com/getsentry/snuba/pull/5388 -os.environ.pop("PYTHONEXECUTABLE", None) +os.environ.pop('PYTHONEXECUTABLE', None) COMMANDS_NO_GIT = { 'clean', 'gc', 'init-templatedir', 'sample-config', From 032d8e2704c9e77c04083cbcca92623a2f1e084f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 10 Feb 2024 14:01:09 -0500 Subject: [PATCH 235/342] staged_files_only can handle a crlf-only diff --- pre_commit/staged_files_only.py | 5 +++++ tests/staged_files_only_test.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index fd28e1c22..e1f81ba96 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -59,6 +59,11 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: # There weren't any staged files so we don't need to do anything # special yield + elif retcode == 1 and not diff_stdout.strip(): + # due to behaviour (probably a bug?) in git with crlf endings and + # autocrlf set to either `true` or `input` sometimes git will refuse + # to show a crlf-only diff to us :( + yield elif retcode == 1 and diff_stdout.strip(): patch_filename = f'patch{int(time.time())}-{os.getpid()}' patch_filename = os.path.join(patch_dir, patch_filename) diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py index 58dbe5ac6..cd2f63870 100644 --- a/tests/staged_files_only_test.py +++ b/tests/staged_files_only_test.py @@ -358,6 +358,21 @@ def test_crlf(in_git_dir, patch_dir, crlf_before, crlf_after, autocrlf): assert_no_diff() +@pytest.mark.parametrize('autocrlf', ('true', 'input')) +def test_crlf_diff_only(in_git_dir, patch_dir, autocrlf): + # due to a quirk (?) in git -- a diff only in crlf does not show but + # still results in an exit code of `1` + # we treat this as "no diff" -- though ideally it would discard the diff + # while committing + cmd_output('git', 'config', '--local', 'core.autocrlf', autocrlf) + + _write(b'1\r\n2\r\n3\r\n') + cmd_output('git', 'add', 'foo') + _write(b'1\n2\n3\n') + with staged_files_only(patch_dir): + pass + + def test_whitespace_errors(in_git_dir, patch_dir): cmd_output('git', 'config', '--local', 'apply.whitespace', 'error') test_crlf(in_git_dir, patch_dir, True, True, 'true') From 15bd0c7993587dc7d739ac6b1ab939eb9639bc1e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 10 Feb 2024 14:45:43 -0500 Subject: [PATCH 236/342] v3.6.1 --- CHANGELOG.md | 10 ++++++++++ setup.cfg | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 340ac476d..be2fee601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +3.6.1 - 2024-02-10 +================== + +### Fixes +- Remove `PYTHONEXECUTABLE` from environment when running. + - #3110 PR by @untitaker. +- Handle staged-files-only with only a crlf diff. + - #3126 PR by @asottile. + - issue by @tyyrok. + 3.6.0 - 2023-12-09 ================== diff --git a/setup.cfg b/setup.cfg index 24b94e2eb..2002a6816 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.6.0 +version = 3.6.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 61d9c95cc17cb391855d17cf382feb079372644e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 18 Feb 2024 13:03:44 -0500 Subject: [PATCH 237/342] fix building golang hooks during `commit --all` --- pre_commit/languages/golang.py | 3 ++- tests/languages/golang_test.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 4c13d8f9c..66e07cf71 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -23,6 +23,7 @@ from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT from pre_commit.envcontext import Var +from pre_commit.git import no_git_env from pre_commit.prefix import Prefix from pre_commit.util import cmd_output from pre_commit.util import rmtree @@ -141,7 +142,7 @@ def install_environment( else: gopath = env_dir - env = dict(os.environ, GOPATH=gopath) + env = no_git_env(dict(os.environ, GOPATH=gopath)) env.pop('GOBIN', None) if version != 'system': env['GOROOT'] = os.path.join(env_dir, '.go') diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index 19e9f62f6..02e35d710 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -7,10 +7,16 @@ import pre_commit.constants as C from pre_commit import lang_base +from pre_commit.commands.install_uninstall import install from pre_commit.envcontext import envcontext from pre_commit.languages import golang from pre_commit.store import _make_local_repo +from pre_commit.util import cmd_output +from testing.fixtures import add_config_to_repo +from testing.fixtures import make_config_from_repo from testing.language_helpers import run_language +from testing.util import cmd_output_mocked_pre_commit_home +from testing.util import git_commit ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__ @@ -134,3 +140,28 @@ def test_local_golang_additional_deps(tmp_path): def test_golang_hook_still_works_when_gobin_is_set(tmp_path): with envcontext((('GOBIN', str(tmp_path.joinpath('gobin'))),)): test_golang_system(tmp_path) + + +def test_during_commit_all(tmp_path, tempdir_factory, store, in_git_dir): + hook_dir = tmp_path.joinpath('hook') + hook_dir.mkdir() + _make_hello_world(hook_dir) + hook_dir.joinpath('.pre-commit-hooks.yaml').write_text( + '- id: hello-world\n' + ' name: hello world\n' + ' entry: golang-hello-world\n' + ' language: golang\n' + ' always_run: true\n', + ) + cmd_output('git', 'init', hook_dir) + cmd_output('git', 'add', '.', cwd=hook_dir) + git_commit(cwd=hook_dir) + + add_config_to_repo(in_git_dir, make_config_from_repo(hook_dir)) + + assert not install(C.CONFIG_FILE, store, hook_types=['pre-commit']) + + git_commit( + fn=cmd_output_mocked_pre_commit_home, + tempdir_factory=tempdir_factory, + ) From e5257268558a1e83731232b1ec4276a24ba870dc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 18 Feb 2024 13:19:11 -0500 Subject: [PATCH 238/342] v3.6.2 --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be2fee601..6c2ee9493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +3.6.2 - 2024-02-18 +================== + +### Fixes +- Fix building golang hooks during `git commit --all`. + - #3130 PR by @asottile. + - #2722 issue by @pestanko and @matthewhughes934. + 3.6.1 - 2024-02-10 ================== diff --git a/setup.cfg b/setup.cfg index 2002a6816..a447bbb9f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.6.1 +version = 3.6.2 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From a768c038e3ac1a6bdf04f7f2c38e7e87bf6a57ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 00:02:29 +0000 Subject: [PATCH 239/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.1](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cbda1019..c428788e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.15.1 hooks: - id: pyupgrade args: [--py39-plus] From e58009684cfc4842028e99d34837e2722af39b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Lehmann?= Date: Wed, 7 Feb 2024 11:18:24 +0100 Subject: [PATCH 240/342] give docker a tty output when expecting color this makes the behavior more consistent with the system language and would help the executable run in a docker container to produce a colored output. --- pre_commit/languages/docker.py | 9 +++++++-- pre_commit/languages/docker_image.py | 2 +- tests/languages/docker_image_test.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 26328515e..4de1d5824 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -108,10 +108,15 @@ def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover return () -def docker_cmd() -> tuple[str, ...]: # pragma: win32 no cover +def get_docker_tty(*, color: bool) -> tuple[str, ...]: # pragma: win32 no cover # noqa: E501 + return (('--tty',) if color else ()) + + +def docker_cmd(*, color: bool) -> tuple[str, ...]: # pragma: win32 no cover return ( 'docker', 'run', '--rm', + *get_docker_tty(color=color), *get_docker_user(), # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from # The `Z` option tells Docker to label the content with a private @@ -139,7 +144,7 @@ def run_hook( entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix)) return lang_base.run_xargs( - (*docker_cmd(), *entry_tag, *cmd_rest), + (*docker_cmd(color=color), *entry_tag, *cmd_rest), file_args, require_serial=require_serial, color=color, diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py index a1a2c169a..60caa101d 100644 --- a/pre_commit/languages/docker_image.py +++ b/pre_commit/languages/docker_image.py @@ -23,7 +23,7 @@ def run_hook( require_serial: bool, color: bool, ) -> tuple[int, bytes]: # pragma: win32 no cover - cmd = docker_cmd() + lang_base.hook_cmd(entry, args) + cmd = docker_cmd(color=color) + lang_base.hook_cmd(entry, args) return lang_base.run_xargs( cmd, file_args, diff --git a/tests/languages/docker_image_test.py b/tests/languages/docker_image_test.py index 7993c11a8..4e3a8789a 100644 --- a/tests/languages/docker_image_test.py +++ b/tests/languages/docker_image_test.py @@ -25,3 +25,27 @@ def test_docker_image_hook_via_args(tmp_path): args=('hello hello world',), ) assert ret == (0, b'hello hello world\n') + + +@xfailif_windows # pragma: win32 no cover +def test_docker_image_color_tty(tmp_path): + ret = run_language( + tmp_path, + docker_image, + 'ubuntu:22.04', + args=('grep', '--color', 'root', '/etc/group'), + color=True, + ) + assert ret == (0, b'\x1b[01;31m\x1b[Kroot\x1b[m\x1b[K:x:0:\n') + + +@xfailif_windows # pragma: win32 no cover +def test_docker_image_no_color_no_tty(tmp_path): + ret = run_language( + tmp_path, + docker_image, + 'ubuntu:22.04', + args=('grep', '--color', 'root', '/etc/group'), + color=False, + ) + assert ret == (0, b'root:x:0:\n') From 75b3e52e57b5d6fc7bef10c131204edf196ae17a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 00:16:12 +0000 Subject: [PATCH 241/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.8.0 → v1.9.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.8.0...v1.9.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c428788e9..229c0a8a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.9.0 hooks: - id: mypy additional_dependencies: [types-all] From 0939c11b4f0488ae3bff9b67aed67ea744189412 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 21:47:27 +0000 Subject: [PATCH 242/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/hhatto/autopep8: v2.0.4 → v2.1.0](https://github.com/hhatto/autopep8/compare/v2.0.4...v2.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 229c0a8a7..8a0ad8d7c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 - rev: v2.0.4 + rev: v2.1.0 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From fc622159a6c5cd31919ed2a22fa1c11d8ca56dbf Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 24 Mar 2024 13:17:00 -0400 Subject: [PATCH 243/342] fix per-hook fail_fast to not fail on previous failures --- pre_commit/commands/run.py | 2 +- tests/commands/run_test.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 076f16d8f..2a08dff0d 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -298,7 +298,7 @@ def _run_hooks( verbose=args.verbose, use_color=args.color, ) retval |= current_retval - if retval and (config['fail_fast'] or hook.fail_fast): + if current_retval and (config['fail_fast'] or hook.fail_fast): break if retval and args.show_diff_on_failure and prior_diff: if args.all_files: diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index e36a3ca9c..50a20f377 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -1088,6 +1088,22 @@ def test_fail_fast_per_hook(cap_out, store, repo_with_failing_hook): assert printed.count(b'Failing hook') == 1 +def test_fail_fast_not_prev_failures(cap_out, store, repo_with_failing_hook): + with modify_config() as config: + config['repos'].append({ + 'repo': 'meta', + 'hooks': [ + {'id': 'identity', 'fail_fast': True}, + {'id': 'identity', 'name': 'run me!'}, + ], + }) + stage_a_file() + + ret, printed = _do_run(cap_out, store, repo_with_failing_hook, run_opts()) + # should still run the last hook since the `fail_fast` one didn't fail + assert printed.count(b'run me!') == 1 + + def test_classifier_removes_dne(): classifier = Classifier(('this_file_does_not_exist',)) assert classifier.filenames == [] From 7b4667e9e6e05e31707c404c95115b151745866c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 24 Mar 2024 13:37:19 -0400 Subject: [PATCH 244/342] v3.7.0 --- CHANGELOG.md | 17 +++++++++++++++++ setup.cfg | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2ee9493..076e16315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +3.7.0 - 2024-03-24 +================== + +### Features +- Use a tty for `docker` and `docker_image` hooks when `--color` is specified. + - #3122 PR by @glehmann. + +### Fixes +- Fix `fail_fast` for individual hooks stopping when previous hooks had failed. + - #3167 issue by @tp832944. + - #3168 PR by @asottile. + +### Updating +- The per-hook behaviour of `fail_fast` was fixed. If you want the pre-3.7.0 + behaviour, add `fail_fast: true` to all hooks before the last `fail_fast` + hook. + 3.6.2 - 2024-02-18 ================== diff --git a/setup.cfg b/setup.cfg index a447bbb9f..0e155601b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.6.2 +version = 3.7.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 4e121ef25c21a8caaca8304cc683e382cacd48f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:31:39 +0000 Subject: [PATCH 245/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.1 → v3.15.2](https://github.com/asottile/pyupgrade/compare/v3.15.1...v3.15.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a0ad8d7c..9cd3b47bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 + rev: v3.15.2 hooks: - id: pyupgrade args: [--py39-plus] From 74d05b444de75367eaf630e099f15aa51e060dc1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 22:08:29 +0000 Subject: [PATCH 246/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cd3b47bb..93f70f871 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer From 0d4c6da36e96443f05ae2d1f6c4e63d1a5d2b652 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 29 Apr 2024 21:05:41 -0400 Subject: [PATCH 247/342] adjust _handle_readonly for typeshed updates --- pre_commit/util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pre_commit/util.py b/pre_commit/util.py index b3682d4f7..b75c84a2d 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -205,10 +205,11 @@ def cmd_output_p( def _handle_readonly( func: Callable[[str], object], path: str, - exc: OSError, + exc: Exception, ) -> None: if ( func in (os.rmdir, os.remove, os.unlink) and + isinstance(exc, OSError) and exc.errno in {errno.EACCES, errno.EPERM} ): for p in (path, os.path.dirname(path)): @@ -222,7 +223,7 @@ def _handle_readonly( def _handle_readonly_old( func: Callable[[str], object], path: str, - excinfo: tuple[type[OSError], OSError, TracebackType], + excinfo: tuple[type[Exception], Exception, TracebackType], ) -> None: return _handle_readonly(func, path, excinfo[1]) From 5c3d006443d616f5b9a717a43a6f3bce60381ddf Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 29 Apr 2024 21:28:16 -0400 Subject: [PATCH 248/342] use a simpler gem for testing additional_dependencies tins required building bigdecimal, whereas jmespath is self-contained --- tests/languages/ruby_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index 6397a4347..5d767b25d 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -91,8 +91,8 @@ def test_ruby_additional_deps(tmp_path): tmp_path, ruby, 'ruby -e', - args=('require "tins"',), - deps=('tins',), + args=('require "jmespath"',), + deps=('jmespath',), ) assert ret == (0, b'') From 0142f453224801138448584a8517927194865330 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:03:55 +0000 Subject: [PATCH 249/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.9.0 → v1.10.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.9.0...v1.10.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93f70f871..6caee40d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.10.0 hooks: - id: mypy additional_dependencies: [types-all] From 296f59266ec656fe46bf0d1b2bce6aac89476476 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 10 May 2024 17:06:29 -0400 Subject: [PATCH 250/342] determine rust default language version independent of rust-toolchain.toml --- pre_commit/languages/rust.py | 2 +- tests/languages/rust_test.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 7b04d6c25..5f9db8fb7 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -34,7 +34,7 @@ def get_default_version() -> str: # Just detecting the executable does not suffice, because if rustup is # installed but no toolchain is available, then `cargo` exists but # cannot be used without installing a toolchain first. - if cmd_output_b('cargo', '--version', check=False)[0] == 0: + if cmd_output_b('cargo', '--version', check=False, cwd='/')[0] == 0: return 'system' else: return C.DEFAULT diff --git a/tests/languages/rust_test.py b/tests/languages/rust_test.py index 5c17f5b69..52e356134 100644 --- a/tests/languages/rust_test.py +++ b/tests/languages/rust_test.py @@ -9,6 +9,7 @@ from pre_commit.languages import rust from pre_commit.store import _make_local_repo from testing.language_helpers import run_language +from testing.util import cwd ACTUAL_GET_DEFAULT_VERSION = rust.get_default_version.__wrapped__ @@ -29,6 +30,14 @@ def test_uses_default_when_rust_is_not_available(cmd_output_b_mck): assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT +def test_selects_system_even_if_rust_toolchain_toml(tmp_path): + toolchain_toml = '[toolchain]\nchannel = "wtf"\n' + tmp_path.joinpath('rust-toolchain.toml').write_text(toolchain_toml) + + with cwd(tmp_path): + assert ACTUAL_GET_DEFAULT_VERSION() == 'system' + + def _make_hello_world(tmp_path): src_dir = tmp_path.joinpath('src') src_dir.mkdir() From 9ee076835365c0b3aa700de8f574def623826385 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 10 May 2024 21:24:51 -0400 Subject: [PATCH 251/342] v3.7.1 --- CHANGELOG.md | 9 +++++++++ setup.cfg | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 076e16315..81d5b33e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +3.7.1 - 2024-05-10 +================== + +### Fixes +- Fix `language: rust` default language version check when `rust-toolchain.toml` + is present. + - issue by @gaborbernat. + - #3201 PR by @asottile. + 3.7.0 - 2024-03-24 ================== diff --git a/setup.cfg b/setup.cfg index 0e155601b..83c09acde 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.7.0 +version = 3.7.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 5526bb21377dc3e4a59451a55d0d729644eac462 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 21:34:15 +0000 Subject: [PATCH 252/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/hhatto/autopep8: v2.1.0 → v2.1.1](https://github.com/hhatto/autopep8/compare/v2.1.0...v2.1.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6caee40d7..eebeea992 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 - rev: v2.1.0 + rev: v2.1.1 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From 1f128556e4ac2fae84133b9a4f085a8044a44382 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 21:47:18 +0000 Subject: [PATCH 253/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0) - [github.com/hhatto/autopep8: v2.1.1 → v2.2.0](https://github.com/hhatto/autopep8/compare/v2.1.1...v2.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eebeea992..0467fa394 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 + rev: v3.13.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -29,7 +29,7 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 - rev: v2.1.1 + rev: v2.2.0 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From 9dd247898c86405b68705595d8a3c8911be39d57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:56:51 +0000 Subject: [PATCH 254/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0467fa394..6282056f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.16.0 hooks: - id: pyupgrade args: [--py39-plus] From 49a9664cd0e393fb3bc5e1023bee801cc3e6fc6a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:57:20 +0000 Subject: [PATCH 255/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/hhatto/autopep8: v2.2.0 → v2.3.0](https://github.com/hhatto/autopep8/compare/v2.2.0...v2.3.0) - [github.com/PyCQA/flake8: 7.0.0 → 7.1.0](https://github.com/PyCQA/flake8/compare/7.0.0...7.1.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6282056f9..b11a1dce2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,11 +29,11 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 - rev: v2.2.0 + rev: v2.3.0 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From 69b5dce12ab0674cd7a622ca8b55f1afa720211b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 21:49:02 +0000 Subject: [PATCH 256/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/hhatto/autopep8: v2.3.0 → v2.3.1](https://github.com/hhatto/autopep8/compare/v2.3.0...v2.3.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b11a1dce2..1f734f8cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 - rev: v2.3.0 + rev: v2.3.1 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From f632459bc67834a200aac26f1129fc16f82fb625 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 23:34:14 +0000 Subject: [PATCH 257/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1f734f8cf..f987dfe89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.10.1 hooks: - id: mypy additional_dependencies: [types-all] From 88317ddb34ac8c60b4be7e22198fb550dcae995e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 22:04:19 +0000 Subject: [PATCH 258/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f987dfe89..a628f4f47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.0 hooks: - id: mypy additional_dependencies: [types-all] From a68a19d217d0d1067828622fde9044d9502693b3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 28 Jul 2024 14:50:24 -0400 Subject: [PATCH 259/342] fixes for mypy 1.11 --- .pre-commit-config.yaml | 2 +- pre_commit/util.py | 4 ++-- tests/conftest.py | 25 +++++++------------------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a628f4f47..1a9a8fef9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,5 +40,5 @@ repos: rev: v1.11.0 hooks: - id: mypy - additional_dependencies: [types-all] + additional_dependencies: [types-pyyaml] exclude: ^testing/resources/ diff --git a/pre_commit/util.py b/pre_commit/util.py index b75c84a2d..12aa3c0e1 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -205,7 +205,7 @@ def cmd_output_p( def _handle_readonly( func: Callable[[str], object], path: str, - exc: Exception, + exc: BaseException, ) -> None: if ( func in (os.rmdir, os.remove, os.unlink) and @@ -223,7 +223,7 @@ def _handle_readonly( def _handle_readonly_old( func: Callable[[str], object], path: str, - excinfo: tuple[type[Exception], Exception, TracebackType], + excinfo: tuple[type[BaseException], BaseException, TracebackType], ) -> None: return _handle_readonly(func, path, excinfo[1]) diff --git a/tests/conftest.py b/tests/conftest.py index 30761715b..bd4af9a52 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -209,36 +209,25 @@ def log_info_mock(): yield mck -class FakeStream: - def __init__(self): - self.data = io.BytesIO() - - def write(self, s): - self.data.write(s) - - def flush(self): - pass - - class Fixture: - def __init__(self, stream): + def __init__(self, stream: io.BytesIO) -> None: self._stream = stream - def get_bytes(self): + def get_bytes(self) -> bytes: """Get the output as-if no encoding occurred""" - data = self._stream.data.getvalue() - self._stream.data.seek(0) - self._stream.data.truncate() + data = self._stream.getvalue() + self._stream.seek(0) + self._stream.truncate() return data.replace(b'\r\n', b'\n') - def get(self): + def get(self) -> str: """Get the output assuming it was written as UTF-8 bytes""" return self.get_bytes().decode() @pytest.fixture def cap_out(): - stream = FakeStream() + stream = io.BytesIO() write = functools.partial(output.write, stream=stream) write_line_b = functools.partial(output.write_line_b, stream=stream) with mock.patch.multiple(output, write=write, write_line_b=write_line_b): From da0c1d0cfa19f6dc0d6ed97820c7cc93fe7e7c58 Mon Sep 17 00:00:00 2001 From: Lorenz Walthert Date: Mon, 22 Jul 2024 20:52:43 +0200 Subject: [PATCH 260/342] implement health check for language:r --- pre_commit/languages/r.py | 77 +++++++++++++++++++++++++---- tests/languages/r_test.py | 100 +++++++++++++++++++++++++++++++++----- 2 files changed, 155 insertions(+), 22 deletions(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 93b62bd53..5d18bf1cb 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -14,13 +14,74 @@ from pre_commit.envcontext import PatchesT from pre_commit.envcontext import UNSET from pre_commit.prefix import Prefix +from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b from pre_commit.util import win_exe ENVIRONMENT_DIR = 'renv' RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') get_default_version = lang_base.basic_get_default_version -health_check = lang_base.basic_health_check + + +def _execute_vanilla_r_code_as_script( + code: str, *, + prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str, +) -> str: + with in_env(prefix, version), _r_code_in_tempfile(code) as f: + _, out, _ = cmd_output( + _rscript_exec(), *RSCRIPT_OPTS, f, *args, cwd=cwd, + ) + return out.rstrip('\n') + + +def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str: + return _execute_vanilla_r_code_as_script( + 'cat(renv::settings$r.version())', + prefix=prefix, version=version, + cwd=envdir, + ) + + +def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str: + return _execute_vanilla_r_code_as_script( + 'cat(as.character(getRversion()))', + prefix=prefix, version=version, + cwd=envdir, + ) + + +def _write_current_r_version( + envdir: str, prefix: Prefix, version: str, +) -> None: + _execute_vanilla_r_code_as_script( + 'renv::settings$r.version(as.character(getRversion()))', + prefix=prefix, version=version, + cwd=envdir, + ) + + +def health_check(prefix: Prefix, version: str) -> str | None: + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + + r_version_installation = _read_installed_version( + envdir=envdir, prefix=prefix, version=version, + ) + r_version_current_executable = _read_executable_version( + envdir=envdir, prefix=prefix, version=version, + ) + if r_version_installation in {'NULL', ''}: + return ( + f'Hooks were installed with an unknown R version. R version for ' + f'hook repo now set to {r_version_current_executable}' + ) + elif r_version_installation != r_version_current_executable: + return ( + f'Hooks were installed for R version {r_version_installation}, ' + f'but current R executable has version ' + f'{r_version_current_executable}' + ) + + return None @contextlib.contextmanager @@ -147,16 +208,14 @@ def install_environment( with _r_code_in_tempfile(r_code_inst_environment) as f: cmd_output_b(_rscript_exec(), '--vanilla', f, cwd=env_dir) + _write_current_r_version(envdir=env_dir, prefix=prefix, version=version) if additional_dependencies: r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))' - with in_env(prefix, version): - with _r_code_in_tempfile(r_code_inst_add) as f: - cmd_output_b( - _rscript_exec(), *RSCRIPT_OPTS, - f, - *additional_dependencies, - cwd=env_dir, - ) + _execute_vanilla_r_code_as_script( + code=r_code_inst_add, prefix=prefix, version=version, + args=additional_dependencies, + cwd=env_dir, + ) def _inline_r_setup(code: str) -> str: diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index 02c559cb4..10919e4a7 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -1,14 +1,17 @@ from __future__ import annotations import os.path -import shutil +from unittest import mock import pytest +import pre_commit.constants as C from pre_commit import envcontext +from pre_commit import lang_base from pre_commit.languages import r from pre_commit.prefix import Prefix from pre_commit.store import _make_local_repo +from pre_commit.util import resource_text from pre_commit.util import win_exe from testing.language_helpers import run_language @@ -127,7 +130,8 @@ def test_path_rscript_exec_no_r_home_set(): assert r._rscript_exec() == 'Rscript' -def test_r_hook(tmp_path): +@pytest.fixture +def renv_lock_file(tmp_path): renv_lock = '''\ { "R": { @@ -157,6 +161,12 @@ def test_r_hook(tmp_path): } } ''' + tmp_path.joinpath('renv.lock').write_text(renv_lock) + yield + + +@pytest.fixture +def description_file(tmp_path): description = '''\ Package: gli.clu Title: What the Package Does (One Line, Title Case) @@ -178,27 +188,39 @@ def test_r_hook(tmp_path): Imports: rprojroot ''' - hello_world_r = '''\ + tmp_path.joinpath('DESCRIPTION').write_text(description) + yield + + +@pytest.fixture +def hello_world_file(tmp_path): + hello_world = '''\ stopifnot( packageVersion('rprojroot') == '1.0', packageVersion('gli.clu') == '0.0.0.9000' ) cat("Hello, World, from R!\n") ''' + tmp_path.joinpath('hello-world.R').write_text(hello_world) + yield - tmp_path.joinpath('renv.lock').write_text(renv_lock) - tmp_path.joinpath('DESCRIPTION').write_text(description) - tmp_path.joinpath('hello-world.R').write_text(hello_world_r) + +@pytest.fixture +def renv_folder(tmp_path): renv_dir = tmp_path.joinpath('renv') renv_dir.mkdir() - shutil.copy( - os.path.join( - os.path.dirname(__file__), - '../../pre_commit/resources/empty_template_activate.R', - ), - renv_dir.joinpath('activate.R'), - ) + activate_r = resource_text('empty_template_activate.R') + renv_dir.joinpath('activate.R').write_text(activate_r) + yield + +def test_r_hook( + tmp_path, + renv_lock_file, + description_file, + hello_world_file, + renv_folder, +): expected = (0, b'Hello, World, from R!\n') assert run_language(tmp_path, r, 'Rscript hello-world.R') == expected @@ -221,3 +243,55 @@ def test_r_inline(tmp_path): args=('hi', 'hello'), ) assert ret == (0, b'hi, hello, from R!\n') + + +@pytest.fixture +def prefix(tmpdir): + yield Prefix(str(tmpdir)) + + +@pytest.fixture +def installed_environment( + renv_lock_file, + hello_world_file, + renv_folder, + prefix, +): + env_dir = lang_base.environment_dir( + prefix, r.ENVIRONMENT_DIR, r.get_default_version(), + ) + r.install_environment(prefix, C.DEFAULT, ()) + yield prefix, env_dir + + +def test_health_check_healthy(installed_environment): + # should be healthy right after creation + prefix, _ = installed_environment + assert r.health_check(prefix, C.DEFAULT) is None + + +def test_health_check_after_downgrade(installed_environment): + prefix, _ = installed_environment + + # pretend the saved installed version is old + with mock.patch.object(r, '_read_installed_version', return_value='1.0.0'): + output = r.health_check(prefix, C.DEFAULT) + + assert output is not None + assert output.startswith('Hooks were installed for R version') + + +@pytest.mark.parametrize('version', ('NULL', 'NA', "''")) +def test_health_check_without_version(prefix, installed_environment, version): + prefix, env_dir = installed_environment + + # simulate old pre-commit install by unsetting the installed version + r._execute_vanilla_r_code_as_script( + f'renv::settings$r.version({version})', + prefix=prefix, version=C.DEFAULT, cwd=env_dir, + ) + + # no R version specified fails as unhealty + msg = 'Hooks were installed with an unknown R version' + check_output = r.health_check(prefix, C.DEFAULT) + assert check_output is not None and check_output.startswith(msg) From d46423ffe14a37a06a0bcb6fe1b8294a27b6c289 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 28 Jul 2024 15:58:29 -0400 Subject: [PATCH 261/342] v3.8.0 --- CHANGELOG.md | 9 +++++++++ setup.cfg | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d5b33e8..49094bbb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +3.8.0 - 2024-07-28 +================== + +### Features +- Implement health checks for `language: r` so environments are recreated if + the system version of R changes. + - #3206 issue by @lorenzwalthert. + - #3265 PR by @lorenzwalthert. + 3.7.1 - 2024-05-10 ================== diff --git a/setup.cfg b/setup.cfg index 83c09acde..52b7681ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.7.1 +version = 3.8.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 9d4ab670d18f3c32ee204dbb50af74884d832ce4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:59:01 +0000 Subject: [PATCH 262/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a9a8fef9..16cec4cde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.16.0 + rev: v3.17.0 hooks: - id: pyupgrade args: [--py39-plus] From 917e2102be90a6384cf514ddc0edefbc563b49fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:59:19 +0000 Subject: [PATCH 263/342] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pre_commit/commands/run.py | 6 +++--- pre_commit/envcontext.py | 2 +- pre_commit/error_handler.py | 2 +- pre_commit/file_lock.py | 6 +++--- pre_commit/lang_base.py | 2 +- pre_commit/languages/conda.py | 2 +- pre_commit/languages/coursier.py | 2 +- pre_commit/languages/dart.py | 2 +- pre_commit/languages/dotnet.py | 4 ++-- pre_commit/languages/golang.py | 2 +- pre_commit/languages/haskell.py | 2 +- pre_commit/languages/lua.py | 2 +- pre_commit/languages/node.py | 2 +- pre_commit/languages/perl.py | 2 +- pre_commit/languages/python.py | 2 +- pre_commit/languages/r.py | 4 ++-- pre_commit/languages/ruby.py | 2 +- pre_commit/languages/rust.py | 2 +- pre_commit/languages/swift.py | 2 +- pre_commit/logging_handler.py | 2 +- pre_commit/staged_files_only.py | 6 +++--- pre_commit/store.py | 4 ++-- pre_commit/util.py | 2 +- pre_commit/xargs.py | 1 - 24 files changed, 32 insertions(+), 33 deletions(-) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 2a08dff0d..793adbdb2 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -61,7 +61,7 @@ def filter_by_include_exclude( names: Iterable[str], include: str, exclude: str, -) -> Generator[str, None, None]: +) -> Generator[str]: include_re, exclude_re = re.compile(include), re.compile(exclude) return ( filename for filename in names @@ -84,7 +84,7 @@ def by_types( types: Iterable[str], types_or: Iterable[str], exclude_types: Iterable[str], - ) -> Generator[str, None, None]: + ) -> Generator[str]: types = frozenset(types) types_or = frozenset(types_or) exclude_types = frozenset(exclude_types) @@ -97,7 +97,7 @@ def by_types( ): yield filename - def filenames_for_hook(self, hook: Hook) -> Generator[str, None, None]: + def filenames_for_hook(self, hook: Hook) -> Generator[str]: return self.by_types( filter_by_include_exclude( self.filenames, diff --git a/pre_commit/envcontext.py b/pre_commit/envcontext.py index 1f816cea9..d4d241184 100644 --- a/pre_commit/envcontext.py +++ b/pre_commit/envcontext.py @@ -33,7 +33,7 @@ def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str: def envcontext( patch: PatchesT, _env: MutableMapping[str, str] | None = None, -) -> Generator[None, None, None]: +) -> Generator[None]: """In this context, `os.environ` is modified according to `patch`. `patch` is an iterable of 2-tuples (key, value): diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py index 73e608b71..4f0e05733 100644 --- a/pre_commit/error_handler.py +++ b/pre_commit/error_handler.py @@ -68,7 +68,7 @@ def _log_and_exit( @contextlib.contextmanager -def error_handler() -> Generator[None, None, None]: +def error_handler() -> Generator[None]: try: yield except (Exception, KeyboardInterrupt) as e: diff --git a/pre_commit/file_lock.py b/pre_commit/file_lock.py index d3dafb4da..c840ad8b5 100644 --- a/pre_commit/file_lock.py +++ b/pre_commit/file_lock.py @@ -20,7 +20,7 @@ def _locked( fileno: int, blocked_cb: Callable[[], None], - ) -> Generator[None, None, None]: + ) -> Generator[None]: try: msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region) except OSError: @@ -53,7 +53,7 @@ def _locked( def _locked( fileno: int, blocked_cb: Callable[[], None], - ) -> Generator[None, None, None]: + ) -> Generator[None]: try: fcntl.flock(fileno, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: # pragma: no cover (tests are single-threaded) @@ -69,7 +69,7 @@ def _locked( def lock( path: str, blocked_cb: Callable[[], None], -) -> Generator[None, None, None]: +) -> Generator[None]: with open(path, 'a+') as f: with _locked(f.fileno(), blocked_cb): yield diff --git a/pre_commit/lang_base.py b/pre_commit/lang_base.py index 5303948b5..95be7b9b3 100644 --- a/pre_commit/lang_base.py +++ b/pre_commit/lang_base.py @@ -127,7 +127,7 @@ def no_install( @contextlib.contextmanager -def no_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def no_env(prefix: Prefix, version: str) -> Generator[None]: yield diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 80b3e1507..d397ebeb7 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -41,7 +41,7 @@ def get_env_patch(env: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py index 6558bf6b8..08f9a958f 100644 --- a/pre_commit/languages/coursier.py +++ b/pre_commit/languages/coursier.py @@ -70,7 +70,7 @@ def get_env_patch(target_dir: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py index 129ac5918..52a229eef 100644 --- a/pre_commit/languages/dart.py +++ b/pre_commit/languages/dart.py @@ -29,7 +29,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py index e1202c4f2..ffc65d1e8 100644 --- a/pre_commit/languages/dotnet.py +++ b/pre_commit/languages/dotnet.py @@ -30,14 +30,14 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield @contextlib.contextmanager -def _nuget_config_no_sources() -> Generator[str, None, None]: +def _nuget_config_no_sources() -> Generator[str]: with tempfile.TemporaryDirectory() as tmpdir: nuget_config = os.path.join(tmpdir, 'nuget.config') with open(nuget_config, 'w') as f: diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 66e07cf71..609087962 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -121,7 +121,7 @@ def _install_go(version: str, dest: str) -> None: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir, version)): yield diff --git a/pre_commit/languages/haskell.py b/pre_commit/languages/haskell.py index c6945c822..28bca08cc 100644 --- a/pre_commit/languages/haskell.py +++ b/pre_commit/languages/haskell.py @@ -24,7 +24,7 @@ def get_env_patch(target_dir: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py index a475ec99c..15ac1a2ec 100644 --- a/pre_commit/languages/lua.py +++ b/pre_commit/languages/lua.py @@ -44,7 +44,7 @@ def get_env_patch(d: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index d49c0e326..af7dc6f87 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -59,7 +59,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py index 61b1d114b..a07d442ac 100644 --- a/pre_commit/languages/perl.py +++ b/pre_commit/languages/perl.py @@ -33,7 +33,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 9f4bf69a2..0c4bb62db 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -152,7 +152,7 @@ def norm_version(version: str) -> str | None: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index 5d18bf1cb..c75a30893 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -85,7 +85,7 @@ def health_check(prefix: Prefix, version: str) -> str | None: @contextlib.contextmanager -def _r_code_in_tempfile(code: str) -> Generator[str, None, None]: +def _r_code_in_tempfile(code: str) -> Generator[str]: """ To avoid quoting and escaping issues, avoid `Rscript [options] -e {expr}` but use `Rscript [options] path/to/file_with_expr.R` @@ -105,7 +105,7 @@ def get_env_patch(venv: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 0438ae095..f32fea3fa 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -73,7 +73,7 @@ def get_env_patch( @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir, version)): yield diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 5f9db8fb7..fd77a9d29 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -61,7 +61,7 @@ def get_env_patch(target_dir: str, version: str) -> PatchesT: @contextlib.contextmanager -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir, version)): yield diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py index f7bfe84c5..08a9c39a8 100644 --- a/pre_commit/languages/swift.py +++ b/pre_commit/languages/swift.py @@ -27,7 +27,7 @@ def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover @contextlib.contextmanager # pragma: win32 no cover -def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: +def in_env(prefix: Prefix, version: str) -> Generator[None]: envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) with envcontext(get_env_patch(envdir)): yield diff --git a/pre_commit/logging_handler.py b/pre_commit/logging_handler.py index cd33953d7..74772beee 100644 --- a/pre_commit/logging_handler.py +++ b/pre_commit/logging_handler.py @@ -32,7 +32,7 @@ def emit(self, record: logging.LogRecord) -> None: @contextlib.contextmanager -def logging_handler(use_color: bool) -> Generator[None, None, None]: +def logging_handler(use_color: bool) -> Generator[None]: handler = LoggingHandler(use_color) logger.addHandler(handler) logger.setLevel(logging.INFO) diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index e1f81ba96..99ea0979b 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -33,7 +33,7 @@ def _git_apply(patch: str) -> None: @contextlib.contextmanager -def _intent_to_add_cleared() -> Generator[None, None, None]: +def _intent_to_add_cleared() -> Generator[None]: intent_to_add = git.intent_to_add_files() if intent_to_add: logger.warning('Unstaged intent-to-add files detected.') @@ -48,7 +48,7 @@ def _intent_to_add_cleared() -> Generator[None, None, None]: @contextlib.contextmanager -def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: +def _unstaged_changes_cleared(patch_dir: str) -> Generator[None]: tree = cmd_output('git', 'write-tree')[1].strip() diff_cmd = ( 'git', 'diff-index', '--ignore-submodules', '--binary', @@ -105,7 +105,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: @contextlib.contextmanager -def staged_files_only(patch_dir: str) -> Generator[None, None, None]: +def staged_files_only(patch_dir: str) -> Generator[None]: """Clear any unstaged changes from the git working directory inside this context. """ diff --git a/pre_commit/store.py b/pre_commit/store.py index 84bc09a4c..36cc49456 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -101,7 +101,7 @@ def __init__(self, directory: str | None = None) -> None: os.replace(tmpfile, self.db_path) @contextlib.contextmanager - def exclusive_lock(self) -> Generator[None, None, None]: + def exclusive_lock(self) -> Generator[None]: def blocked_cb() -> None: # pragma: no cover (tests are in-process) logger.info('Locking pre-commit directory') @@ -112,7 +112,7 @@ def blocked_cb() -> None: # pragma: no cover (tests are in-process) def connect( self, db_path: str | None = None, - ) -> Generator[sqlite3.Connection, None, None]: + ) -> Generator[sqlite3.Connection]: db_path = db_path or self.db_path # sqlite doesn't close its fd with its contextmanager >.< # contextlib.closing fixes this. diff --git a/pre_commit/util.py b/pre_commit/util.py index 12aa3c0e1..e199d0807 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -25,7 +25,7 @@ def force_bytes(exc: Any) -> bytes: @contextlib.contextmanager -def clean_path_on_failure(path: str) -> Generator[None, None, None]: +def clean_path_on_failure(path: str) -> Generator[None]: """Cleans up the directory on an exceptional failure.""" try: yield diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index 22580f595..a1345b583 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -120,7 +120,6 @@ def partition( @contextlib.contextmanager def _thread_mapper(maxsize: int) -> Generator[ Callable[[Callable[[TArg], TRet], Iterable[TArg]], Iterable[TRet]], - None, None, ]: if maxsize == 1: yield map From d5c21926ab78fd3d89f4891b29bd426f6ee80c9c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:39:33 +0000 Subject: [PATCH 264/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 7.1.0 → 7.1.1](https://github.com/PyCQA/flake8/compare/7.1.0...7.1.1) - [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 16cec4cde..a6c853caa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,11 +33,11 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.0 + rev: v1.11.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] From c2c68d985ceac41afe63635c15789207c441614e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 22:18:35 +0000 Subject: [PATCH 265/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6c853caa..87b8551d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.1 + rev: v1.11.2 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 364e6d77f051b40d22ac9071ef64bc12f3e6a1fe Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 Sep 2024 20:05:29 -0400 Subject: [PATCH 266/342] change migrate-config to use yaml parse tree instead --- pre_commit/commands/migrate_config.py | 58 ++++++++++++++++++++++----- pre_commit/yaml.py | 1 + pre_commit/yaml_rewrite.py | 52 ++++++++++++++++++++++++ tests/commands/migrate_config_test.py | 46 +++++++++++++++++++++ tests/yaml_rewrite_test.py | 47 ++++++++++++++++++++++ 5 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 pre_commit/yaml_rewrite.py create mode 100644 tests/yaml_rewrite_test.py diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index 842fb3a7b..cdce83f54 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -1,13 +1,20 @@ from __future__ import annotations -import re +import functools import textwrap +from typing import Callable import cfgv import yaml +from yaml.nodes import ScalarNode from pre_commit.clientlib import InvalidConfigError +from pre_commit.yaml import yaml_compose from pre_commit.yaml import yaml_load +from pre_commit.yaml_rewrite import MappingKey +from pre_commit.yaml_rewrite import MappingValue +from pre_commit.yaml_rewrite import match +from pre_commit.yaml_rewrite import SequenceItem def _is_header_line(line: str) -> bool: @@ -38,16 +45,48 @@ def _migrate_map(contents: str) -> str: return contents -def _migrate_sha_to_rev(contents: str) -> str: - return re.sub(r'(\n\s+)sha:', r'\1rev:', contents) +def _preserve_style(n: ScalarNode, *, s: str) -> str: + return f'{n.style}{s}{n.style}' -def _migrate_python_venv(contents: str) -> str: - return re.sub( - r'(\n\s+)language: python_venv\b', - r'\1language: python', - contents, +def _migrate_composed(contents: str) -> str: + tree = yaml_compose(contents) + rewrites: list[tuple[ScalarNode, Callable[[ScalarNode], str]]] = [] + + # sha -> rev + sha_to_rev_replace = functools.partial(_preserve_style, s='rev') + sha_to_rev_matcher = ( + MappingValue('repos'), + SequenceItem(), + MappingKey('sha'), + ) + for node in match(tree, sha_to_rev_matcher): + rewrites.append((node, sha_to_rev_replace)) + + # python_venv -> python + language_matcher = ( + MappingValue('repos'), + SequenceItem(), + MappingValue('hooks'), + SequenceItem(), + MappingValue('language'), ) + python_venv_replace = functools.partial(_preserve_style, s='python') + for node in match(tree, language_matcher): + if node.value == 'python_venv': + rewrites.append((node, python_venv_replace)) + + rewrites.sort(reverse=True, key=lambda nf: nf[0].start_mark.index) + + src_parts = [] + end: int | None = None + for node, func in rewrites: + src_parts.append(contents[node.end_mark.index:end]) + src_parts.append(func(node)) + end = node.start_mark.index + src_parts.append(contents[:end]) + src_parts.reverse() + return ''.join(src_parts) def migrate_config(config_file: str, quiet: bool = False) -> int: @@ -62,8 +101,7 @@ def migrate_config(config_file: str, quiet: bool = False) -> int: raise cfgv.ValidationError(str(e)) contents = _migrate_map(contents) - contents = _migrate_sha_to_rev(contents) - contents = _migrate_python_venv(contents) + contents = _migrate_composed(contents) if contents != orig_contents: with open(config_file, 'w') as f: diff --git a/pre_commit/yaml.py b/pre_commit/yaml.py index bdf4ec47d..a5bbbc999 100644 --- a/pre_commit/yaml.py +++ b/pre_commit/yaml.py @@ -6,6 +6,7 @@ import yaml Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader) +yaml_compose = functools.partial(yaml.compose, Loader=Loader) yaml_load = functools.partial(yaml.load, Loader=Loader) Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) diff --git a/pre_commit/yaml_rewrite.py b/pre_commit/yaml_rewrite.py new file mode 100644 index 000000000..8d0e8fdb2 --- /dev/null +++ b/pre_commit/yaml_rewrite.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from collections.abc import Generator +from collections.abc import Iterable +from typing import NamedTuple +from typing import Protocol + +from yaml.nodes import MappingNode +from yaml.nodes import Node +from yaml.nodes import ScalarNode +from yaml.nodes import SequenceNode + + +class _Matcher(Protocol): + def match(self, n: Node) -> Generator[Node]: ... + + +class MappingKey(NamedTuple): + k: str + + def match(self, n: Node) -> Generator[Node]: + if isinstance(n, MappingNode): + for k, _ in n.value: + if k.value == self.k: + yield k + + +class MappingValue(NamedTuple): + k: str + + def match(self, n: Node) -> Generator[Node]: + if isinstance(n, MappingNode): + for k, v in n.value: + if k.value == self.k: + yield v + + +class SequenceItem(NamedTuple): + def match(self, n: Node) -> Generator[Node]: + if isinstance(n, SequenceNode): + yield from n.value + + +def _match(gen: Iterable[Node], m: _Matcher) -> Iterable[Node]: + return (n for src in gen for n in m.match(src)) + + +def match(n: Node, matcher: tuple[_Matcher, ...]) -> Generator[ScalarNode]: + gen: Iterable[Node] = (n,) + for m in matcher: + gen = _match(gen, m) + return (n for n in gen if isinstance(n, ScalarNode)) diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py index ba1846360..c563866d9 100644 --- a/tests/commands/migrate_config_test.py +++ b/tests/commands/migrate_config_test.py @@ -134,6 +134,27 @@ def test_migrate_config_sha_to_rev(tmpdir): ) +def test_migrate_config_sha_to_rev_json(tmp_path): + contents = """\ +{"repos": [{ + "repo": "https://github.com/pre-commit/pre-commit-hooks", + "sha": "v1.2.0", + "hooks": [] +}]} +""" + expected = """\ +{"repos": [{ + "repo": "https://github.com/pre-commit/pre-commit-hooks", + "rev": "v1.2.0", + "hooks": [] +}]} +""" + cfg = tmp_path.joinpath('cfg.yaml') + cfg.write_text(contents) + assert not migrate_config(str(cfg)) + assert cfg.read_text() == expected + + def test_migrate_config_language_python_venv(tmp_path): src = '''\ repos: @@ -167,6 +188,31 @@ def test_migrate_config_language_python_venv(tmp_path): assert cfg.read_text() == expected +def test_migrate_config_quoted_python_venv(tmp_path): + src = '''\ +repos: +- repo: local + hooks: + - id: example + name: example + entry: example + language: "python_venv" +''' + expected = '''\ +repos: +- repo: local + hooks: + - id: example + name: example + entry: example + language: "python" +''' + cfg = tmp_path.joinpath('cfg.yaml') + cfg.write_text(src) + assert migrate_config(str(cfg)) == 0 + assert cfg.read_text() == expected + + def test_migrate_config_invalid_yaml(tmpdir): contents = '[' cfg = tmpdir.join(C.CONFIG_FILE) diff --git a/tests/yaml_rewrite_test.py b/tests/yaml_rewrite_test.py new file mode 100644 index 000000000..d0f6841cf --- /dev/null +++ b/tests/yaml_rewrite_test.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import pytest + +from pre_commit.yaml import yaml_compose +from pre_commit.yaml_rewrite import MappingKey +from pre_commit.yaml_rewrite import MappingValue +from pre_commit.yaml_rewrite import match +from pre_commit.yaml_rewrite import SequenceItem + + +def test_match_produces_scalar_values_only(): + src = '''\ +- name: foo +- name: [not, foo] # not a scalar: should be skipped! +- name: bar +''' + matcher = (SequenceItem(), MappingValue('name')) + ret = [n.value for n in match(yaml_compose(src), matcher)] + assert ret == ['foo', 'bar'] + + +@pytest.mark.parametrize('cls', (MappingKey, MappingValue)) +def test_mapping_not_a_map(cls): + m = cls('s') + assert list(m.match(yaml_compose('[foo]'))) == [] + + +def test_sequence_item_not_a_sequence(): + assert list(SequenceItem().match(yaml_compose('s: val'))) == [] + + +def test_mapping_key(): + m = MappingKey('s') + ret = [n.value for n in m.match(yaml_compose('s: val\nt: val2'))] + assert ret == ['s'] + + +def test_mapping_value(): + m = MappingValue('s') + ret = [n.value for n in m.match(yaml_compose('s: val\nt: val2'))] + assert ret == ['val'] + + +def test_sequence_item(): + ret = [n.value for n in SequenceItem().match(yaml_compose('[a, b, c]'))] + assert ret == ['a', 'b', 'c'] From 5679399d905a30b37c8132e8a854353f3025dcc3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 16 Sep 2024 20:36:33 -0400 Subject: [PATCH 267/342] migrate-config rewrites deprecated stages --- pre_commit/commands/migrate_config.py | 21 ++++++++++++++ tests/commands/migrate_config_test.py | 42 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index cdce83f54..ada094fa2 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -1,6 +1,7 @@ from __future__ import annotations import functools +import itertools import textwrap from typing import Callable @@ -49,6 +50,10 @@ def _preserve_style(n: ScalarNode, *, s: str) -> str: return f'{n.style}{s}{n.style}' +def _fix_stage(n: ScalarNode) -> str: + return _preserve_style(n, s=f'pre-{n.value}') + + def _migrate_composed(contents: str) -> str: tree = yaml_compose(contents) rewrites: list[tuple[ScalarNode, Callable[[ScalarNode], str]]] = [] @@ -76,6 +81,22 @@ def _migrate_composed(contents: str) -> str: if node.value == 'python_venv': rewrites.append((node, python_venv_replace)) + # stages rewrites + default_stages_matcher = (MappingValue('default_stages'), SequenceItem()) + default_stages_match = match(tree, default_stages_matcher) + hook_stages_matcher = ( + MappingValue('repos'), + SequenceItem(), + MappingValue('hooks'), + SequenceItem(), + MappingValue('stages'), + SequenceItem(), + ) + hook_stages_match = match(tree, hook_stages_matcher) + for node in itertools.chain(default_stages_match, hook_stages_match): + if node.value in {'commit', 'push', 'merge-commit'}: + rewrites.append((node, _fix_stage)) + rewrites.sort(reverse=True, key=lambda nf: nf[0].start_mark.index) src_parts = [] diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py index c563866d9..9ffae6eef 100644 --- a/tests/commands/migrate_config_test.py +++ b/tests/commands/migrate_config_test.py @@ -213,6 +213,48 @@ def test_migrate_config_quoted_python_venv(tmp_path): assert cfg.read_text() == expected +def test_migrate_config_default_stages(tmp_path): + src = '''\ +default_stages: [commit, push, merge-commit, commit-msg] +repos: [] +''' + expected = '''\ +default_stages: [pre-commit, pre-push, pre-merge-commit, commit-msg] +repos: [] +''' + cfg = tmp_path.joinpath('cfg.yaml') + cfg.write_text(src) + assert migrate_config(str(cfg)) == 0 + assert cfg.read_text() == expected + + +def test_migrate_config_hook_stages(tmp_path): + src = '''\ +repos: +- repo: local + hooks: + - id: example + name: example + entry: example + language: system + stages: ["commit", "push", "merge-commit", "commit-msg"] +''' + expected = '''\ +repos: +- repo: local + hooks: + - id: example + name: example + entry: example + language: system + stages: ["pre-commit", "pre-push", "pre-merge-commit", "commit-msg"] +''' + cfg = tmp_path.joinpath('cfg.yaml') + cfg.write_text(src) + assert migrate_config(str(cfg)) == 0 + assert cfg.read_text() == expected + + def test_migrate_config_invalid_yaml(tmpdir): contents = '[' cfg = tmpdir.join(C.CONFIG_FILE) From a4e4cef335c62dc314fecbbd57e6fc57460c95d3 Mon Sep 17 00:00:00 2001 From: Travis Johnson Date: Thu, 9 May 2024 12:49:09 -0400 Subject: [PATCH 268/342] Upgrade to ruby-build v20240917 --- testing/make-archives | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/make-archives b/testing/make-archives index 3c7ab9dd0..251be4a58 100755 --- a/testing/make-archives +++ b/testing/make-archives @@ -17,7 +17,7 @@ from collections.abc import Sequence REPOS = ( ('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'), - ('ruby-build', 'https://github.com/rbenv/ruby-build', '855b963'), + ('ruby-build', 'https://github.com/rbenv/ruby-build', 'ed384c8'), ( 'ruby-download', 'https://github.com/garnieretienne/rvm-download', From e687548842aab3c3ccc7677492960c740c2ced11 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Sep 2024 13:06:21 -0400 Subject: [PATCH 269/342] regenerate archives with python3.12 --- pre_commit/resources/rbenv.tar.gz | Bin 32551 -> 32545 bytes pre_commit/resources/ruby-download.tar.gz | Bin 5271 -> 5269 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pre_commit/resources/rbenv.tar.gz b/pre_commit/resources/rbenv.tar.gz index da2514e71145e91debbad4bb1e11ca6d95ed3f63..111546e3dd9796511942c278495c21fe1ef947ae 100644 GIT binary patch delta 31950 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 delta 31979 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 delta 4959 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 From c9454e2ec3245b792eb7626e9bea799bf5e96bbf Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Sep 2024 13:07:55 -0400 Subject: [PATCH 270/342] regenerate ruby-build archive --- pre_commit/resources/ruby-build.tar.gz | Bin 75808 -> 88488 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz index 19d467fdd2867742cdefe402a78489481ce9abba..a4f7eb24fd0a7e4d59cf120014d8b054555b8b0f 100644 GIT binary patch literal 88488 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 aVnW&LI5^+h|Iiv<8JMAUgtgp` z6%>VJIf9IX`{e=~SHnA!55|;>>1h_Q7PMXTC@ZUs!!2xhZL2A(3l>xIje+nC^?>}VHJsGhjLGm_}h>2$AYkD-;A4d0b6a7hY zyNw_BiJnH(rQNwBWGM;VczTL}-N2kt@_FFE3ojvUrCEXWsry&ak9>dn&#FwN=8Wq{ zoBv9l$Lj8gki1>iCo{=od=4?==k5%Kl^r2SgT=5)S-nnZQ!w#+!02YGU*k1sCM-mq3#++VS^ztg2=d#bj;!n~w4IR^VI=`r! zL$KtJ&y%n|gQ;#PZ|Tt=q#x~E2;xK<94>l8JkWB;4QlkN9I!qEUVvik1`>~e3_NpS zn63CUQuE@BPg!7LR?%>OrV_ehU1?=i82(Wm5kg9mQjdaR=nIZ8QhJ;L$501@Hc4>s z7-g|Gf)-RV)_6iez->&AsaiqUZhfb0BR8gVius6u>@gi$C=2wfMt#eA@kn zz4*8jeDw$LqrPB_WaR;AfI++|IE&^!hNRJ6{hyy5c~2`&TLxIz;7~z*LIQ9<)pJEOj8aAFD>06f1R_3X?CO%D94PJIL%~kRWnsCyzeq855LKnW9)lowxO+!9svwGfUq9ea&ocoF~o1HxcUENjfi4gRt4&@nkL~ zP3y5qJCnAGSq@l*y=w@%Jsu9Wi`#k4+RO0eHiJ?KD^>)}Hm^?05&D2+^=rau=M^|c z@~b3_V3OC?ss$?(!Fyx){BYEr51A{e-1vAKQi}_Zdk68c z{Zp;d#oruSrOLOVyD?02193uu+I{)VIY`qkB(AsV?rYVaH~ZRwWT!+-H5m`S^i|2qQiyxp^l>twD(W~Ydi2ZcrW&B%uS+k-){$WM2L6P8? zz~);+zGXbZ(+)kA%w@lGq z5Tob+NiZag^c(`k;3EuzqQ!u_$B@y{qfbivI{C)4q1idFq0eT^>^~DH4Y?PjvSz$s zUOAhxbJg>LulW7bN{u_PLpWv}6CnQqy}t{`SP3a;$8gQMg1Nk4Y#ByEjRl1gs>uAj z^<&0ucaLyBhIt~kY(`$;Ml=SI%h3|;3IJnp&4L?DeW_Frmtb%q*!?+2B%sFwZ`&^B z+1N4J{VO&2S$WF+Jj}W0?*}6KJ5&@=a>ch}{!P024WOdnIsf1tv;3p&IS9fT6{&IC zzRF+p0j2#ypVePByXwKWmD{AGFG1L>Wpy0BEO;U6@@xP9((hkv=r3yZKl#tjB|un?=7+W@23Jj-UiK5^Ql;z+)DUYt)iY-o1<k<-iMe6?1RuLwNB)*?r-l{iRzOW1ygF$ z0N8|p8zeOTCk--Y{#D@d*=A#A?qJ`RfCdMfIbX?09sZ2}%99A5mzF@)t@=1_^|KB<8k60PceGF6 zR|I5Yzcj()PUV>jj36V=X2rF8yEf!rAxaj=@pc~^5Ua!tk!Avo(+ITT%Dpp7u+?maIAk_)_U;vU<@tW4gyepg7Rn!^cy7P>I^n?P;a;mNjm@-=Z9-uB6u)v zmn|;|8-rLIGD zqyMPZ*s+$T^4)Y#@Mvn%m-eDmR+?cdj!aqQuUHka`TwyR2IhzlhxX`6}sQblJ~p<^Xh&w~?u&)5RM>u!6v_=p{Ty&JTU* zx(lkn5(09G&^pQisAlw3KGKz$j|pN}ild>N)7_!~U2AFz-*KzURJq+~u55qHs?hnq zl*0!aR}{v2ra`%~7=Y;H)qb#uvZ$H>EuUw7%#9Z{jH5|js7ue-G)(TNle$}4&g2)E zzljZ>QqSRI*p@H(peyRtIgcafV;@X`ezm!Iy$Xk90K1bM--B1^vq?jwgoS6tW@SLk zD=wi_FU;HI8C6q}dO`vmt3oemprfM+R^MOby1@w1(#~iJki!77G%! zE7hkCWyY~jjVQLPV>6?E3A<#92-gOMO}9NeQughHw1i^(xV|mmk8H#Q*^JAh<2G7l z2EIR14rlG}-aCHGIrDaO$z4CQqP0t^;;{+eOl4ieU^6Z{{1~&)6O1$b3b}0-n2LdR zYT!-WkLg*cC&i%GATEM#6z+*TEL&!qcI5KmD$9p9c%74xDnktp-$B|#K^v&OhA@Mm zZ1B7?rC7_N&F+J|6l1VAXH>_d!u64OHiPD`)(NGFyoBPhvu#{ z5V`&r5!B_I(7A_K6luZsXzYg{KUIURl9!!vwBzrUz!}c!r?=vU#&7?`ta^h60Ke zz>mKci*{`~z-%-m5RGK0tW|^uj7c1HK3^SsXZ?x!2XQSXc;c&n>e*Zr<6%Ump3bd7BGH=epz0Hg|hZ53a7yQzwsvnJG)_%0UAfWTUwR zAsu6WzNpLn8%SoqGlt_KeTbZ={*VKx#Y$CkA*ZJ4o20a9W^?-7r0*EAm(t&-x}_alaI-oLk43>Fp<8pSpgOi=7Cc9QJ1C&P@)yA7AQ-XHY6TEk zjPHQXzJn%?!|{^DA)m5{sDX+fQ|2LSGMTqNx}hN#tHPg;v6qZ9mvJQ2^%u0 zwJ!&Pi6qjHONQ=D;`@v$fgFA}5Lh`7%mUxXAPWeJP||Q&U9u1svN_=BH-TQa#Hj+&>Af-au|n zTu_26rXYHHZVr-ualIM{j_4-?1RB4sB=WsbO@1{M%4Em&6}NAEP{`^vHv0#j`^{fZ zOWJyN!px5Qwuz=Fa8tm^CVFOtGG{AC^8~VwXxBEZ zoHdOnhid!BP=%4Of1jKa>|{t$vnb@jyHSH+J+cjXw1C&bg1+X(gw`?OBnpC}fXUn( zj@}g1VurdrzHA{-`NBrDNKE&hu`*8W__>jJMq!|~6Aw+ISQPwb-JPw)Z3g7>c$Py{CM5DA($W~Ge zEl6d_u9%QLSt7e@NhE8@k_rh~Cn1$(ELpOzL&VtE7`quWbKl>c`u=|Z-|v4;=jhaV zy!U*`c%Sql~|@g;k7A!xsIme zyYhv}>%96T6Ty9FTY@L8IBHDew1fghA8NSMXe$I{y)^KNjBbaJvD<}~`ZjsoMD1 zU1{#=R5-EQNkWwvpm6Cojpvk=+380Cn!5$qnX@ccMW1Bk8R6e+ccl;`00NI{KU4tZ zWVz=7ev9a4ued$9gc*Db@D5WqK!ArO=1@#3uEzO z-Y)QX7@4q$sL$*=cjqKXZ#DL^#QqsGa!yXIgbz2us zDjsYYyyn|~84sd4}ZRI0;vWjGL+e}AC zC8T#V9N)X5!^>sVlAiAP%r;S~?#WrpSG+7sb>13V;PVc_9F!wEfqpjujKHGSpUiT` zst|2uVXBf*tX0VTd`_D5^Am}EY#wIv*%?=um00^d(Nv5Pf-jMG;{ea|g7O_qfv)cN zL18ED8}*|JoVNGO7ak~xFr$uk-QMl^oxzi(PwyM~zVSB{ERF2%-=$s`hY_64uqdGN zB8iFD=4(n>+*40`6%7SP;k}HJi*DIwH_kI?II5z3LteT6TW}kpvvbHv$Zc7GGI_Xf zwCoiee2}6y&&+3}a{evf#Zye8oZV`JKjm8_jy>wg+O7vSn@DM=3u1sEW#p#7<$R74 zzy12<$5KS6}@rCYAYh%ulMKUN7h z9bdfELUtBunqw>C3QqXIQMY#1PA6LR{L22;^lRF;Lgr!?Kv5njya?>*f`N2FY1SS0 zrX%K`uJKv0pH`+t>L$JN*ppbJ=pH~(J;mIZZ7vb^J+WdQgd6`&?b8o*ReTdinqJDg zLnXEh)jw5>(`P(g*f}o9yK%76v8VIeu-mBBX`2sw;tnNB$R@h|<^lYOEIlu5w*akP zrAaUE5Xx5|JsOdouShEgCl?^pMJ5+K*dMj5oOk=WF~lrCW9oFeS@ok$TZh(^^G@@{ z@5PsIZb^;YH8xGNrJ*d0K?OZY(m9KI*90WR6ep>dFW1@#?*%ef4A=zQB|)m0am^F0 ziE4L8t+H4ijdMXFvt~Oa12V1vc9{h(h%JOV{-8f^Wp_NcRI@xKV6=~`FY~e5#XR8O z|LU=F(EY69wBJ6e7qr0h?naX`WdMWi#l{_poP_BYi zV>03$P(z)Jqv2^;KEG|Vr2Hhx`2Q38r@g&GPd=PS9r$s)l-Yk8OLL^CZ z940ee#}&?|Cn^w6!N_si{X0%9;#&GapRmN4R`kcF%V|yx%bxpWWbb@!IeXwA9L`~~PSy|&>JSfby~w9H)A!;)30*oW~S^f?>Ukxyg0;l0k=zd95# zm2Bg#o6zHa*}T_-T(zPj{St7OA~|0bc)c@LXB>C~X==G(H)~gNrZy^HvBP(&t47HA zrwN0hqFMK~2Xff z77tYN7Yo~_OnDP$Ep|0Cs>zw!0~cQ=Odwa3pD;u^QSB@09LVdqq77qP5Y2Ug2`@nt z8F(B8X~=HwkZ`8}LJv6u@B!%LtO2@C&j4c^$^0mF-Cu=_XA8_KBOBv+{~T|~&q#Vr zY|iD5yDgbF>~>FJQ%4RY6}1AwA`iYHMsng>k4c0v9yDyC(=C8>W_;t@M3LO{~w z%CQ3RwkLjPr4Pv&IbR5r;=A|tN^;=BEa-0S12p^&5+nXvMA{KRXE8K68jzKPx#<|> zsgd`>63yXCRX@uqT}*e@ZpB15>2%>Eo3nFKQ*lo*35hCsM}K5Kx1> zT*Pqb{MGcCWOtSVEi(;t3-!>sSrgHNy9Aw$G7QY#Bji$3wHlTR1>Je zo4jDBbuK{bjPa>=*%$_S$xED%mM#p%`3pKHG$aPs^a&JBi?lu;HuH|C$q5A)$u9iA z0KzZPS9STjN0vmf46N}3*}6Mf_>+4ic6m!1N;aN-(SN73c3SaJLFXIez<8Z)N->}z z&=l^ZTMrs9py>f=#WnK&2U& zP{EE5O7D;ko21qSjR=o@S7TLz1$hPb|I9H3O$bI=qGJX*5_FqJzYll80P~q$6vYnG z6U#x}4+3L9>>cy*nR7Uw1EoXOY%Rry7=9E@&-m@tb(dr?*2sp=zjp&gxl``!$^dMdpiM;JLl=6 z(cpN%xDhD@<;7=lYu9`}5t=#aHaIJES|PaR^~?iyh6#y(N9y+vRQ<(9oe?rjy+h@r zODijT4;s7)ZpuwFQ+mWzZQ9W9Eucf>91L?HMSe{sVTzD9h}=1yHEEq(5HIawJhB6o zFP{iW)W$J z>P<{1G_+O}g`T!$3c6O`wfu0Z+1=~#@8ZDkRGcc!Wjp|$MS`hmaD5YpyA0fD#Pm6f z_hq*)Uf(F4I>*^db9w8-b6Z=2U6R-1l&r^1!;lwh42@&4uXb_}!@hzPCAEVRQg|&B z3aW4`*h`{oe2&zsY~cB$CXCmzw|4DDXSej&ZTqMOc}3f>M6S!sDlE>RORfy#;!xt-(l267%@LH z;0+Pjm*GMNONH+q-~2ewZgM(&#Q3zf_1>G!nYE?(ya)zeg*qDp=(_PWNe*LD+Mk#H+Z>S35S3+b<2kMZI2UiWc-8Fg2_Uhmd+6rU} zVypfILWol1fwk#gukLQYz-PJ=n-(o@J0}hIaBM0t3HJ(`DCERFgfcCxrDiw9`517| zLH-WZ_nB@hr_*#0$^mqHb{J}~n(VETdMuUUU$AcDjeeS2np;`dm$Mm?S(a&D#z>L93cR;R=P^t5QYL+2O4{_V`{6`ySDH z=#0At3~XrV$27#Q%L88oPJ&qBXRF6VA`c7HYLs;LU*t;F57(7xCZ=YnSzlvLxx$>` zHkoJE`fro}VF2jN0ca`}?$8^m82VIuqSdf$)B0=NvZ|VSd*(_!yz97mn8)rf>C>m!jOHKRHuZ`14gK6Bqbr({!IG6! zed|G)qZ+ra?Wm%vUlECV61|ZJ z@ioOA)SOm5(G59(4xrTQK!D;!0Lh=A4@z`wxB!YL5ev(!|KaE+&$z<^&msR$b-K@lD_mXYZ6?ip#u?)0Y>%zV&|;=;rlH^$PxfBRq429uS8JTQ+~ilf zdG~1LYk2Bv@3m!T_;%~;+kHb%0?H8M1I5FruZu{$h&YYe2OzUwMcFLi+=0E(zZkNa zQ=V*!GR+(>Jd|^6>Y{*czkSI)=f3qS-Lb$eFeU+))BfGbCETw7%htier?o|2_hnxx zcsOD^n)=9ARk}5X0W-FTMfH2i3)Rr&78wMhRYv^7lAZssBuZVnRs-Ym`ZTU!r1)d> zi&03KYxm-qO#-{*eNMS}6|>mu_iZ!}ZP%mC8xZ_U_cL@RFCU;gmh2CX=2(U&eiO6pR|SZEvW%UW<}yE%+5lV-mKjR%ru5$2E#(V4@)Bm>ZA>FypGR*XE;xk4~9 z*YLy|N^|vUa&+>Pii*IB3-E`SA@Auoi~nFD{b2qB3xN&Gy)>=r`)2bc-iG;xx~U30 z$M04~%6MzcIEaRxG;)0@EHTOreEGotCK(9Er?b>n5BCI|_=Wc$n$tzT`OYi%Elup& zziEwFBv&wjqsi{n<4_?gm6D(8I0eb)Qw{vi9JsW# z`y^l1g{RnRVRhvWj+3Y#^({>ZJ#9gq7r1VW(DPk0%J@B{>wO)FCUhee=V+sEsJ6Uso`oF%K%htQc zR3zl=lghC(p~E(dMumGM_PqNb9m1BJ-T~VSmR8)@q)-|ED~A# zbCgZ`)XCG7PDx2$Wi#(HiAB3#7k#$9(WWrlm*MKfPQIG*%CuEAg$?B4YpF4$97Jd7 z87_IGS0CE3+4w1!d!^fh=-|Ck3bQbo3<)t8P9noy!<1>k~Y#EbF5-nY=M((h5{NRpW-T6~&6*R$FJ~?$}WuDCcda_8yu$MG-2tg6i`8r1BeY^@(;oHD5V^JRss>iK~ggaJHG~8u?Vc{90CV^ z;Seu?u>*$>GrqbO&@F=(==WX_vXN)`botR^L2Z^#-!)}^ny99ipBR1LCyTa?9W$rm zZo>4$S1Le9fk%V5_W@zd4xy88bdLTRni0l7?cVd?z<%FduVaGqgPxV?O7`FQ5jQkz zP6n=gNa++37rlTK8$kXaRBrSqqZ<56Vn#Ew^VygqQ>T}9;t$H3IsaDQ<;b|NAmVe- zpI$SX-+AEuKZ2M33UXzRa$XRDO}~j>xnLI1F35aEmZ9q~-;?(os?Gfw`RFT-(=&^bpda!%f^H+>PFsJ_cuIQ<<|KHLJ=s97@d^*frETmQ?7@yThU^dLjC&RXoz#{oz`SK`^;mH z`chUB4|%~=_p3CF9RkC29sNmxT&)227EHK9#@U0vgdhFLk$O4A@os?+FQcT~-!RDf zNU zWecf_++sg`h{iFv_<-~6&x`FtZX0ZTcS=fx^=iJ2Z4L3pURIXi()nBR!dl)$1rOVOH;x*7oIlwPZfpRF3NU~m=qYf0 z1c$iY%w>$Cy0N%L`(pQUj#kHWGh*S!@#T`f{RO-4Bu~F_JpbV9i5$%p>ecTImCr%z z8ra$)&|_+Z+HVMG5FhH@bNyM( zdSV3RpF%@-okt4U5O@Pkzy!jdaMUDadzea@u7V~Cab9aK1VAbS!(Kr7T`nVW40jag z2DAR00oM*cME|e@WQa0X^;~DVzD^orm%D3&`Bzw7vE93zvVRj z!ND@rrnKr`WpcFk-i6JVEXK?uU1geT?3n#4@d=4Bk-f%fR0I_$N0C- zQ_=_lPJlG7W_5UUdyEY}dK~Rf?#vIYc4-_Sz*OfzHx9;qLK-#h)|o7oJ$)$41O5Ku zl`B6KqU=~-iP7i=8J~-pV(X8ys)yYTQRY)-^4dLhxY+2J&D4+L3(Lc z-QiVUKvDwOo{x#9n?I}0JPA`#;(h-LU(RF#%WLy;F z>xQI8XQ!#X;_$pDjNvnSzN@`-Qv-i&!;piO(PIPOeovO;m!AEtOYB0P5i?hj=+8rA zWEFT)*PkOTYdEP{Brxj4u7d@WF#J#7ai2cjg6u-pI5qB~p$85AgVbn7PZKP!vq62! zuEhj}lh}4(Lv8(!C`Nl#8oMrCUb^qu^oNW%oO|M{c*s=X<5Im}QN4fusRnC6-$EMDa?728Lvd{SeadJz+(-)B7=J0QT}MTJBFEsNar z7I@X&liV33yXDae+e5v%!dgyd!4PWu0pOvXBUFcY!hK74) zN7X<^w59y0CCH-Uyy>hE?j6wIM6dYLxGVF(kwxV`r=`9JM~k_M$XDbZ%r9X5^;r6r z&7SV%eP=;gJ1EHVWVHiS#et+zkSPK%NQ6McP5ymRFs4p9oA=g9LcC8+lsrRqP$IMB zZl&9S#>HjX7xCjQ1%@LU(m%c!jxoWQYS?7}-0c7tb_n+XA30$64HLuCYj>@KYh<4* z9u0c&4jN_uZO`?Hm8IZA{;R_@`>zYmL@K5Uv<`r!owQamx}0I9k-f%wHqF88$f;XR z$9l7iPKk~#$X*)y=+`H4D?aw{qEak=!71###e-5wM6YrFG(r*h_6FH4gzOeIRtRHC z4Gz~83f(p|4}7q~7iX(srt92&V`<$!>yI4fl$e>#jO>HY50Q%qrWX;mjW|tB%LAbU zi(t%A}ewI{7Ldxq3rTlQG|>a>YdA5-VtA?tXIXZ zihroMMG{1&B860lav`~w@l7h^{R$9ExJ6oXqBEP5PViqXH67@a{e68fuIjnxM%|f= zkMm@w@-Y7oVn{dHI51sCI+<_*@FKEpcYxW3F&BVxMO3u5> zZ*LYLo*)D7aclUMBQ}*f5^BlIJw(PCuVJM-!6?AJ2KvijaTJ`M#qo>8MU*p-$k^VL z$c$&R^~XxxzoLJ14IA9gzklep+>r2T{OeLOE)Hz1QHeB=PevQU6yHsk3Q4%vOp!G1 z>XKg)cDpva(NgcCREg9LyY8N$eY@YD^;cUw%l&qcifM)*A9x!EpmVfBUgPmGzd*C? zoKsHiR?E$r>LjtcfMu=Erp@;=!yV)o&vA3dE(^sQ8u0ym=ZCJM@;<+55FGG}nJZ57 zanOC9=9}r-$2vMuYn4sa{M6!JVF4T(9?$CMW$(+!L>7z$px>bYb!1a))T9^m76 zQxq`s@@y<;hC%QWGKK&bXc_~(i-QW5Mq!9RMu;YO5>VjH3Q{jrf+_&xE+(O|AF(<-hJ&{)>ycxDIs--bH zUZ=mp-Pw%997qT-zDlhpjG>m-2zLkWuURzN-w(UQ`Jni{$)aUuqd`&O9D93mM*BWf z=leTd1&uSvyaNLH3O6H=02@A3Lh;fLXq`m61S7;FdOT^aGn`n59<)63PM*Ok`KyA7 zUPf~JiL+1Ag~NAA2&gTZ6_y?^K1%Mq=*$t8_7w8gMocrgMryu3hdo!v#{DX7;2|HR?Yf3Q;)(Vzn3oUqK)J;HKxZ zAec?HDvh!4qIi&%_o24dmZ_U-SCR}0^3SJQ$WEOP=Bc;-c~=8lBf7WN;ec4e_RX=` zah%F|P`L?7M}cq_2HZZz-)xL#MEkPtOJ)FnUr6-*hoph`4&1NXn67bJx^-?4YRTT+ zWBwuEh+jlI>(Q4kBHZqF9Vwi3jeDCM&H)~AsTy0zcZtOQN>AbA%vEU?d%vwdbaW&z zI0J8$z~cIbJ;t%%;%@K5@aiwRy_AlE{6&Fx8o0P$RGEcIUWuhTxjw`~ZZp&@j&;{| z8&zXG+a_k2FJ^}E0gFYm>@K7UAIy%Yy-R>9k# zgPvU1SUlr56DLmn+L1QJN1@e~os&Tr!Nal%vg%U`vYsCmfK%mkkWLdCeGy!QAxrA| zBxx2imWG~AE4bNE*W-Kh$-@ba5lFJQ@f~o{O^8Z}jSuQ`O)TnCBpyu9u0Z-0RtfLk z0ug{a3lC3{TyQlIB>~hA*Q?@HKUu@uk^KhulswO>D<59(_=MK&s!v>f{Xw-56nbLYwWAA>IlV zuhXXBB{}w!6dnK*ku)LeLqH(Va3y2{d%l3eLA66ukzYToaDJ{<)v^?LXZ(>Hs@Y624by&Zn|xl6s&oC-<7SXbC<5yBh<)Uz=8uWSgjWT}buJ_oPIF_}Dyx-e`b zdax)=L~g{zL1H1I=ez2oSs7m^gf*VPDW3;be-LYk{4EMRjs=g$2{DYQn^%xez#oka ziWWbDy6og@G9O@dT4BvFoD*s4%(x}z+;ya)lkjMjkd}t9x{xV#JA9QuME*p8!AhNG z0?_g*x~(hM8Qxg+BPxOf^as&w%qUrKAP?3qC-`T*FWQi=3u#-JK; zqmvloA7Gb`N&moh@7=O~B&V6+Xp$0ZiVZ`$TvZ?{UZytrhNHE@At7>)GFVI@h36~- z!^Z%dR0B?vhgld7;~6-Y6&OM*_SWq2eI?k~S|%*6+IsfhH)*z%H)Emrn4vCPLG7we zeDc@nlRh`tXl{?dT`JlVU@5aeX^Bjbjsu;Cf<&H<4STsD6)IjXv(z38G>=5WfiV#% z=Q?ieBo;$+0o$w>#)em4Ql$eUu!KO$txEE;hR@i*7F+?=s{AS;Ts7E%$gYdODu@=+ z>%?kbeoS~(Eak*h;OSy#_IFX>_Gol8jf#P;+mu%bdEFz_Km zLonpI4JsHBt!30D5*hq^7(V4~_4w^}3F&a9PO2<$ZXN0Nk%=j4@Nq4d$R96OE*LkL zQyu(`W(Awy$OIa&5C)sU)B!|;9T-j>Wa`aRv(9CIH;%6OVixbKqu6Opr=qJz<&CrnJuanG~{15&$jGG{-5v>vjRd#~6ay9c!4Z(T+Eb(5? zZg4wHY1VZ#W#9y_)C|3L_l8hW?gk9mo_CrUg4#jvx(*WIAQ(g*o)4z>B4*+Zqk2Tl zuH;1tg$RX9PmDAKR1y=ct@mkQGu!)Tle3Ps??jlP%XlE{lm5!-WRTu)^UcRlbnJX}IS zg7?|1Rl?zBsN(N)KVS?n?C~IT7z;=qyT2*3gQQw=v~OMPvUJU6D$*dj7%b;0vm`Eo zv`_&5K?Z=(Z8@w8Q~o~pa#i26sDSjc9@zqy2csu0zG|l2(=M6hJ6wN4KGd;r_v?0l zHeTeyv4XWfR2QU|ZxVC38TWe@vwWD*%6h;;c4k20U=*JchuwRflFQCQ&AtylzV9|kvHx5U;GP8?6$(kLI%i`o!uYbLDZ#X1p0yxmKvUkRS8*9)w zzcxd?okoxy%dSf56#|f(~MicW&(?oOTJ(Pp{& z;1J2~FIHOu7y-bY07i_#2oH#!vbb9lmNVygWKxks@x%E?s)w&w-^Rr8fEs^8^RUuy zF+Won|J0*98=-)QbdcD9K(0opstI(4gS!UlMyE=8^8y^Mmx||2j!6)_MxZPZE=~|U+cx;bna1()ci!IEZM?2`v)Il* zE=@@2oRv~(P0Go8&)BS`j*U>!++Yc@1nUrD36N(VH3p+RV9UzW<_o4Gm$T%lC3}(Q zt%Neivxj&e*1n{Cx_nJ}FQazXso+QJ;1YFvb{45+M*sp^7xu%@{4}T+3;h(fx^6o@ z-Kxib7&nSzOnBcMxghk;6n9}@pGmFqiJW%Tp>Qbe8RQcPBlqa+3Dab7=gnHO)g zGON1Dr08`2;My{~>H3yRsl9TLs5sr;)o?8-wXm-0Sy^!@9$}kTVs(`1vzwq*>LgO;8<UFNIYH?cQNinDH+c$ZSo=-*g5s&}-XlW%ZG=FFTT>l?2<`ZX<6_xi@aTL+(< z0_^MP$5c>9!p2VEMrVQCDk^15$|4rVjqOg?P}XKJ7911iyMIseN|nq2Po%Z>wV$zJ z+MdMSmyUyJM7t1Z*h7NuGU!4_m_DY3F$Aup{pK(6VC49!F?cF`g5OO*+c^=>xF{p~ zkrj#%h&jW^a;Ix~_m%-tszL#8>Bwqak5nJ9GhRZ%l|`)<)@aTpv0a{w5#(mL=w-d+ znar7G>ce*Jki-5S^;3-9bpfPVHiT@Sg*t(+C3P~B)I_09=1?c)8CQ8kY00$HcX#r* zv>kYi*2CoA6x)18r>@?#X3XOLcxAw2)NJhJ6g>|4i|FR{=;m`?AC@byd;hHKmaomb ziQD#u>Y*v(aEr8-^2`$h)Y+3?t-`PMu*ZHOLt5}Kf*3Y&2#(%51rG9jefk{YT<70%kVV$AC$yh ziiEy(;{016rGQM+V$J&RZHk!z={}sh>jf0{dtP(o31BfG9+$90=1cUeIWN;!#;Kr@ z)G+dj2Q*f(+vI`HE$~e}Rp0jQCuZK72EU|VVSar)i=iX}LW8JOo^luaSQ1rt?lBGg zB;_F4PGpuksl%^qWQ z;BdNeP5pLe+c{*E-aIMX8~|k8>EQ7iM9d)G{H#tCZ=|ciV1YnX~{ZLqV1P9>gfCJcSZ=i#< zz>({rK-vriz&eok<^n=K;GVQ!%pU8+;wY`#ll4bxDyEva(WC0CQqc_q@lYoPTm6N2 zbZ?!`NoL%4YNd2Fihv_8Q>0_M88*6)QLk>~8v|-5MAF$>#$pM=#+x(>sTVUEcwo|; zx~b!Wi#|^zK(sb~N;G+R8$%_9V7?Cm*Sk45+RhSE;x2?;$VbBi=ZQG5HC#@teomH< zCch2X-f2(6RDq;zLc@8mx=aC0)JCt>k!|8=Wgarq2I(#d&tC!2H1eXq^@3P0z)Yh^ zDxh=`bgsF@z6K*OH4WOyf~IB^ms2$>->pukO{V&j0cA3pGFeMN5no`J0|}kS13uio zl9_wtTxWs#r(C~q&R>+eThH;PZ04gt;?i$y z9f;3WKFUc_fKLZ@Kqm(`ady*a+C)A&5as|jII`G0iWnok{tSJXkis%PGPAkejP@VQ z9if8y6|gStzYb#CL3k%h1O_5h!Z5H#e#7Dq;1xKJLjqNPe`@b28DjzVVMd~NkWQV{ zD(>xy37kbJ0{APWz-IX><~+dNfybYbS$|NdL^66{8Wng5xc=Ha{XouHDOKsVjcGpV zwZ}(ahrQ2z-mHl*J-OB57KOzsge&PD@)WsMRnF+6N1arlH+Uhk&>`UUbf#JkB9CHHh$Iij+dLAHX8d+K(dx)<+WQ+EhcXQ5PWov`+Hn@e{p2$=_q0+&ud zgfYUvc;$a>6oxqGJ}O@h*ie-9H+!BxJQ4paXyU=3dU?HUy5j+NH(p@_<#onkDozm| zCvSr-7?hF235{s#rDi){@iNvgrWZbF^Bbw(*;T+3)5hsLt^M|XjO=mSDdn-^#!q#M zf9+Id2aMENV1J_FFEn&z1<1MdJrdRX&GAgvv!jfeoc1H(Sw896M?`9hbs~@Ra2~5K z+u-CuB%{FhO*HWS`%2zYaq${5%D#$`hP{yoX5CZ1?qj`wDyS)jA;v!cy8T5Znump9 zWq#`4qxtk#tRB&U7?{5*??(pXYZGJ5ovqu&rH>yFUzSzBCqKyA%pI#SdPzAt^vu8X zGZ_h2(E%XMY(5agB52x_jLaM+>flx|$3clT?$97X3=pcf%IzL7QuB}oQIKDc2j)Kk zv~vU4Tv_RLl1igasMj!Ae960$TA!hQD;E9pgj!JJ1BHZiQ_1$+tsX4}-N?(5w*LE= zz{YyH!#5T{Y=qF~LQv7;*e#I1Jps&+R)$MB5KBYj;GJ+6i}56X+^)GMEm52b(&5)fDKA%xd*>OOFnYA!1W!VW?Eip>k=$WYShNZE^Vg*1$HpjJ=ZjhWmyPUjX`kf0>c#KM*!wH)7~IHW z=&FCC=Pi?3RrGm5X(y$%=E`oqTl3l#30If6rC$Nxz3^fOK&ndCFgg=BxDB|~T^P&~ z6fC#BcHhz%^G!e@|J$c&ef1%xcMc~P96irm);}RQrI%ZNHf#0Y=DkCOfYZuhW7W;^?o#n@;yv$qt3z~&xG7D~Tr-(08 zzFB3ymcHqyR5`rpj`B6}=!UdEVpIr5#CekfyraA+NWGLp&cUDX*Fg9d=b zQLv#&2R-w4Fy)N%xi@^->LfG6JK3F4Es7_utPTwbD7EqC*)f?b%grC>>$&ZUbRWh* z)hys5fSys;#mKWdWG&g6zmLshl!+8)+ODHkq1jYk)iSWN5}ov)^b zeeG-q2i6EhwEz!rSK(nXvI|0i1AhSSU2eDY1xSy_qe zDK#|Yf?2+^&k3fX3$l{?++RD0TCsI-GRZy~B!ic$U~n05MgK#eq z^NUrXxuMswEpztIucYqR=fO8iUpU}@C8dFf`jI5(#@#XpU3S~I%VPY?BG73>n};Li zu|7W3UNLfx4msx{_4!16Tc>ymOv%_Gcd2|(BUar$v8(O37V{a!*Wqr5LVIYlyHd{1 z4Nl-rPSIT@BK?etYyg9v2Jf@Zlws{%s_#pJe_cDm_ zUyNaL+_R~q7nO2Tc9*?P6VPb}h8;+^KHhMpre+!tV>WE}{HFBxG#OK|p3}%ky z$x?eeDm&aXepc?*DSM+EjS*rPtYiOU8j+2RYayo<0>5X}NilMh4jK0b=-rXO^8t!| z7h^7j3adUR|J00U<;dP^-Y+~c5lxk!Vp3@O`L(U*flkx^ES!cc9PoGHHVY1sVc!5H z#aLCxT6}z|kwccYpbc;R;gn+q8)WlrHIc7R5gO!_1=@Nz9FLz5Hob==LoUO^2>3uomo-Cv$H0<4 zEIkX|T$osHk8wM~S5w-1*RwJCQmho=TIW(&NJM1~>4m~gO!+!ebuLgzSmQCw=6@tvYM<(w#C z64V&tL3z0MJA0c1Tj2b=XOUhf@-HjrJ#9C2c?u*%$pl2*I4LxL*cd5bZ%1;wP-V1^ z>@E~Eq=O%`Ek^~ug?u&$tD=ZT;!=?r8fb9}ZVHZ!S5c{lsU-=f$*wJb+%*r}ImBG| zg^O+7KysqFmbuWo;U@p(*+LRj#f6xF3Nrd7q#O=aJ+iv~$0UMP{MIQ?Gtt?yqiqgc zZ|wxhd`9o2PV*N|IVKhtY7|k=ks$x=*zi?u`f2!3p`mY6wN%2A$!FV%-yen@8S_4F zetr3z5pSsXt?|#^TO1J_H~6J0KfQPLx?Fx|9A^=Z1jR`0;1up%J(&J+bRy%D!2V;7 zJw_~yM+}qnlhm`^WrWIddR-DZV()rk18zBqwf{&+9S0KY&^8q@xZkL0ybQkuP3+&K z05>hSYz%*n*4P2#?Q^n+YV>x-LV5&?T{)Zr9)F2qJ7~tT>%0uuHgWZFhkdqzuOM}s zAOms)gD#ri@aB0D8^S9xZgPKi+~%X~tJ?by9<^_5UCG}3X>`KP$=X+H=eUO?%@0Yj z(ftZiq`L+gvFJ)&2nug;gTLB{cA3wN2`UC$)jO7lUx;E+^t3goIh*<1zMmx~zbtNe z42AfGBBa-@a~jn#0eZ@=_DshnZe!crYnJ!v+h2=d`d)M)DBHJ~lg+I3snCAm=3-{8 zaYPJpf}`|~!dCRV29V{%R$;6?cwky@Iu0o0{JNL@sI#;tyKvu~^!kJ3781^PZ>OeZ zfc<@sL)6~CNIw;+)yqM+1`o;WMeDYkWBbE=B#x{K+>Z9mI-1B<+vU?Hm9H%O2i0mX zX@A#Q!Ey5(Y1U~4M?t#qJEzk+=}*eMTb7pdS>Ex&)xiB6a(Es5wO4yu*P=FM0${B} z`#c6iY=rh@&D9K#;gpfc8+6VfvcslSOb5fk0ZwAh`PJokCy(pTyM8({hg|b`M@u#- zno&QNOboiNqP)*J+UF2;`+ru%r8R(QPSLWIIqqg&RacRInDMvYVwH(4XjiO))bgiZ z;?Gn@yx-zc@wMqr9RuA=HLUZ{ImGb*uL5u(pX!wht2LXvDn6?BEQ*2Mi0hsGsem&) z#xoLOTJwq(1s_JW%ov`(l6v?%0%k3$Lp(JyLSh;etP!$y&U01Ao@+QOSFoMF(Bmw1 z`IKg8Sz}z}-O`mQ?ruhuvYOe-mj>w(yQBTLi4#fZ4i zM;}3WdHymH_VH4_-7Zf+ARj{AAUvA{uzLlh`f-(G=kBg=wGaEn-|SetrW(#w#oIia z6FX6TRYG0A9$(2P9N_)(&M)xNX@&dW!9W+Dfg7x3CLjr!N@Fi`Ujned1WH)y!;mj&TTZ z{cdNydD$r~d}5?kH$vhyENuVKzZ-#U!gRaw~F#K`Eo8lZw+<*ZLepJx1epTX?uVGW8p zwi@s8S=R1A317Wj6ULMLXW?w+E6w$@5s7|9**8Bpp05L*8wl%&bOpZxFOeivH;75D zc{p;!oSLy~`Ic2F{(%%qkh$%PkgKN3hufDwh2M&ayevBuy#c}&=OAR-8vQk%oCjm? zfqs68?E{zUIN7hWL+?HEL!E^Vk3a)cLV?LaMc44%yLGkRW-96||9?vYWXbL3eHdQm z;NF4OAo)GFGW*(Ctp?A0tUADCFBBph$=v5JdN(`zvDgm!YYgl~pTm^12EBm!m**gk z!y18dpF5{ijBVam-zQl;w`R7_I3Zvld+?*k!Ur}Hw<8-;FD(CythbEIs%zSZ>Fx#z zk?wA#ySt?u=`K&YOFE@XxFzrBdvaa(^M7ui_uKxmezVuCnOVooniz;X z1L6P6+39~dyAQ^dX$8#Y(&JXm_v(dX&jhm)Z1#|%{OolRqCy4pdpVnUBUVT6jsKGzZB+N=>>wfkgAM<5hhSAHNXua$uW$1c96nE=zqWdyijYOqY}+KjGmgdjD`AdYrsCIbq z!9Nu}FK2>zst7P2N*IH323|45xj*m_W-+Pma30`IX=zO0b9uJ9G$FQ7VzJkyyxIT+ zAJ(d0ijT$txd%|^9$<}_erC5|9dpoSez9g1Cs-g~-KYb}4XPx_yqBZGc0CddB3VNM zbY>u33?bR!hG3OhU{ebCsn5zVS-L`BkMmJ8xQI!$#(q)DB+clOrnfS+ps{_@!yS5$ zQ*dqc{_kHUWP4bPJqcTuml5m$Pc9-dHBlQd)=xgMv)$7)320}>+nM2VsqqWKEdKke z?_ciamtQqb5xNvXl^?`D@q}${B~_j(~T* zkAUgJ8YE>i1X!yD-xePO6DJ}iTxCN)h7p&YK7H~5@;S{47|BrbF#UpQG+&YK!eym# z4%BP@p=m7yN?z;%um*c)iL6U} zRRPTXW%S%r(oBK{=S{T>+GqTa=uNzw zXU86B*+Gyzw{!ld+9O4blpD1BTR}_SPZblQ7!IOL#JGynWh)sD;s5UN6UO1;0b+yTvTD7~WLmNca6ByKb-{jW%DNZBa^94}OvyRY7Pg8sPBfC>)AW?pjIT*oDi}yu1^Z=yy+&Rj)@>#5&i3)N;Zes0F99aG znT_GIu<$23S;V#aC?tf(esyQGkd5MBh&6&2V}^6zt{_Rb|1EPDQUKFj@mF{?&jt=u0 zS1g5C0q;9XMEa5P#PG&=;-W2*`*(-Z51_NZ`076c@SfO0XHD)~=w@2Wt+ux}M}1Fk z!HIB4)MK}kh6y2)toIQS?!ta%`^&&iKuk0LAV~m$jWkc1Cj}#Z(?WfnLSGn4dn{{E zLtCQCS)plAlVb@$Xe{;bz3%p6(ILy&5~Dv6>YLi@M(B-k^@*$mL_$U@XwY_K?~lIj z8{)j@qu`z6uX3S!`Y(EwP|g)lReRM-*i_H{3e#&vfp1D#WLu4)6VgP}I&~Hc7e_c= z!|Ag?qWB@t(g%P)M9f}%iGOW~@m0@6u@&uU=8l1YW^b@9Y894h{bs=AtjE;C?Ck_S zyxn29Hc7Mq-bnS)-`5D_cWVcn|0E&zC{2FV4b_#D-%Ow`95GsBHd83n=zV(tt!rsI zv}*DTa?y7GbG85HqD?=&0`T`c4^$}x1)*SAsmcQm#1F83DGjV-c%aiYEt;zutMkAH zMgCtq**ypFNt+wIlXxJH=vWGGs-eM{mHOgGi%2G=Q(>;hQi{}b;weK1;ET3-7anBti949#?STS=~Snj#Yo zA(BwEZv4L?-aqR|@@v1e$E%@SyEHwrX!=4Uuw*E3NwVq7$g;Ih_N)77*Hr(O2Wmb4 z_qwEhep$%XS4O+od`GZKJyg<4u9F# z79reT_ScK^`hw`;`yRa2YRES69IrV~yh>&fn0Be=i6wSFl+1U6U(En5{|yd7KcxWs z6gjYs@S!-|(&UqmVz53}yETXGoQb)Zmsqw9(}O9Y6$0v;e9wQy_7DWYFaQL@R6==I zK>cZk^{nIqEQ#cNh^c%FxM3txN6uzLCQQ=uwLh?wE{B_|9s5I&)Ble_ahHr%l}hO; z?L>o}MQo!MJ@T}OF-pB{UsKKZ2~-)IS*bg+KbfFj?$g zTzcWbzv>GzW+5$^=Jh(Rw-R4Bom5M9qrUI>zUj&IkBa?2i1(BA!8k)WB;d-F$IiZK zWz-%aZ*~RFvDo%iyjdQB3#uCuM2?!ECtEpzt(gsA;HpOm42g>O7(buNZGd{*9=z_| zYdCQ%vIe;wmyL#5l{QhTiunT2c5jr0^ zjPPOstvA6O#Nw_xEi2|jBtwhlPKwuw4X!PMw2|)l$BQb4j7IPRs|0F%hx)K!?Z5Y= zm$>Bex-K3cy<|!JFyn4nzQZ3CuK1%n5c6suf?fVvN%(v^Gu?xcZV7)dsnBB1*P9+K z6>HA;8b(gZ4XTj>qo=J@O7*-(?HpqGp8vVT{sR!#KF$D(o!^n2o9-$-3M6VPV~d^> z@*g;eJ26PwzsI6I&?|Et2dbAW&M}VuZTS?SoYkOWi(=ODQ4Zpo?4RPDYs{Z0Oqn2BcKX+qdlLip3U(HBhqjqmNulnl9cv+diy+ISi2n5o zgX))qW+`P6#S9Bi&%dVR|3ST%<7`iI6JzhK@9gM{-I+;pkWnyp(k3di)WsS?tmddv z)^E-d+=Ku$<9>{n%R|H4Y(nmU(7f6X7tBQOeWRr z9s+i4xGu6L8t4U!7a{n7>BS@fJvzuZAR7REv5FP3@X5aS*K5;u z`!0s}rY+3VH8SsJ% zbaAV5b}6qi|NYw+=>89y8P5gx1STUDs1shIT?RzDtw3eZDP+b>HigFMPcY|~JVvCR zQP>L(ivJ%UwZ~Nk_{kM-ELv|h;b9RD(c(mT_t=L@#n%DKa!<{zhB+G3OMjM7U^z+J z{*T~77}OfgkwDKyLay}upl;WK^scmwC2+M!Y<2uIZvp$d=z&al@9^rT_U4POc)fID zdU2>hJB7e?s&L+f>QSRrcMp*vyf%J38Y(Bn=v(7>J*ZB*ON6&_`F>UUItF~RfN{-# zi+t)wYk*Cf$z(Q>g#ePObdMdrkw`N0Ti4e%&e>(CMP4h9zyaIz8loz6|0{XZI8g8L zMAf>qPhfsY3($fIj0^HP(lHl@E3XJDxe6G(^Jucz#)mxjCmc(_5N{gNk??;&;yA<} z1XC}C_3GAtRkntRg=uUh!<1+LL*? z8O3-Sss&5xIg3^{xo7!73ta&Cvrfg}vj0nv$&f{7u2{m@{e%9uCMan}d-$6TH!*If z{hMm{(-EjdgvuR4)#%@{0A|;JdqO%8hp`@xnGlY;GDSZkiln8yk!3ULSvO&vnLpZm zi4hO!nclJb&9U|k@R!Y7hygDlG6zeODoPp39}_PT9vdb!egvD6J8P9kGZlpxF{U8l z!O9spGwlLb|84SK_V`tDKVYA;GPSJUyV7Atq*`{DrZbZB*N71{A&=f9ol z1G48@C@WEs%gxHjP1z9VSSV*keN>;1%;enkwcmI`z)`Ft#dVxatet<|(v5I|UJpP^ z29BF;vjErPlA}_vOm*`)X|-Aubn=_@cO+!T!8XoKl=Cvjd>g?2#E8Zn*7NDhD>mIl zUh6sW-EKmA5!gCYy`saFwIUU!pI+6+^7U&aybJ}R>64I>CUad;O3pn8kE+%JpoHzv z1JZj0BJGOcLxCsbdC+ezd3f81+4O-X(n=fl;`xQK3MxhVjA?f=j!P-=H$3xyIK?}O z!L@UM-^jlaAO@)NbR;ON+N^z%MbDgjuZpAUjQ#bj3NZ$)l4%TYcjQxPCFC-bNbW_1 z9o|hy_Yp`%+{4;GmmfaVuPCTGos>j9cjPQLll%}go!9Zj0b{y6(%oN5QqOokEmFc6 z9htBE6Xg1zWdm|gYx_W#{@A+l630wgbHk7MQ7ykYu^7sH**{3kqMz=2(s={pOV`u- zM0@bT5Y4Ln=WPZu2`vhU0q5l=KT;JsX>vBOO|c(D5)k8){tT&uV;BB_;(6j2VMI^p zH3_|=h~pRvRPF(gX%~(VXCY5{6Ho?D-ES6TQ_?b_DbR(5@a)3ARV$VIsBL=i9O7*| zs7m0NPx5fQ1UMKUUK;1U#K(4%?|SwC%3r~Lcpgr0$+Jkh(oY1Gi){%>n0px%;V|f* zB$(Jya1H{mAuMBX4tykqnYe#tqLxclR0xw zspdgp0&h&S=*<_44w+i{uP43pvd*}W8!FNJRydzIS(=J0VE8#$CfQiilxAYvxKz9; zNc&TSUG7HM(!Zg@mwpB!>7aK}fY37tcQ2fHet@yP>ZD+OA*C>itwu>cM6hgQKI=r} zIOL0T`b>Vth{GAMcd<4Ap?N9t&s$l=%i96^I|Q)&D90TYZqQ;?)zt}a^i2?4lH8HH z6PL*KX!hH@@S^9_Ks1a$)qB9Y;ES#Mk4J=gvIorOqBa$wzD{RobTOVeTwh$-MuWbx zc}gA*{5kW~BaJEZ`MWbK*_QMss52h;kHD%h0>)o}Ea5MtuY7q?SzCi-Ah}UT==sPr zo4S@#a^NU65A_ryGDx|-jg0dva9+&-1X%v(e?b*sokQoQhthJMEhyV>rZn5RItDa^ za;XL)!_U~8gNn!pGmk285eo;dOiz~(9eI)1%%?m}a1YnBpH#FQI@RZL? z424}77tztWM$9S!E`#pmjvo|nQPJ8?wS$j4nM0x@qci|3xE{T|3Zly=f$Fd#e z0z223=R^LFOMJ#X&G+C+iuKB^%!IWoTx-Df{?M!&d~*Gh&-gi8=R7%p!RQaEGXHn8 zPo;CYu<;@MJQ-X&v`3Xz>*N4w>-a8~{$Jyo&;{AuBI=T>XAuBfjUu z6m?HRfW`i&|LQg*qqBBYu{dMSg|W;wZa5n--h}{Kk(sWio>oYf$MVxa3}im;BWg^Q zlB>g*It4#L9B-jIUXZ}Ij(M!6<)xX3*2lpWcQTp|f-6Wv0pRQreAjvhoczAE-|y!rg!9j%+h`kGU{DXG+%9EbBhxaO;(&Z=u!5T5{1N z@t4;UkLO>iZVpix|JIw$69x~pUx2{7B2RRFw@XV-@vR$=x94R&ozJwxU+)f8{Nma6 zerHm!UJ<; z;aUK2@~!e=YdZS&Pmh~S0c5BLyZfH59131OF_U9VTUDwsA2IIP}> zzxk%H@`O^YI7V3KI!Rv>KO-q~wWAz?bhv*EX!Ho>{RD)Z5*CIMkxMMZ_w8XQUF5TqgA+VKz2+v)y|HB;bR`Dyxy zH+_C~$L1A>pwir^sECAraCp`X8$RU+_}~8C)f^)njb%UBeK8(^`O~T08(`|E#sT&N zuH`|aB(@m=2|FA&3Wx28{9A&{oKF;u-d8B97}uQoR}KI~evUp0H%veVnj8bxUV{Pz zCj$u&_>aH_kBF1As9Q^{ktvToY%!GY`wbeBnW^t@HD z$n)3Bif3T^c&wMa)PyZ0s&g>whnYv=kZ&hEd$zj%^JI+l@MDb2=3ZX|ecteo8_-Vx z=;>;W`9wXo7%Cl%C`+xIh=r+xbh?)__2nc=@saQLXsTOdPV3!!u6OUdv=+GB_U&Ac z__#``xLn`7sq82%4Wx7Ty4^?lEUHhfZB`-`nf|{1D|3u@+Yu~I!=Njk|xJyWn za7JhPMKsMAc!1(1-8;KGiBnWxw*u#v7dZ(jH-kSAgFf(aLMQ4Bb`EM+h`Nf0UF9g* z5ya~qALFWoX1tooA(_CJZ}*CZnUvTS!~U+wi7AmliThNufw-rfUr4QOojE{4t7IrJ zTLaa6C&DzPU<{<&tU@L@0=xRcbOAg!AsaJBWt_ralTT&XwcYU52y*BnPxvZ+5FT;* z1~X$KjY(ZeTGANWE07gNhSp8$;?RUqMq4P;PonW^3XT_l$Ac!qA-_THmHxBwA>vH2 zkjt69EF6WLbt#$DXW|d?rmbB&Zp9$EFNM0sn-Z=&l?Y}Jaebn&NrBh6u9UW2BburO zNRv70ZZ(@#qa#cQ)2VZ|+92)GZFr7F_EM~&B(oBvKwRu3_M!GUdM?g39ISL~WRzqQ z8tg*tb8bs%H5aXCJ&e}h`=bU?i=8mwx#o3C5fmD6B=-q+ZHmqIWPscXeE7ylsLr4J9TjUm-|s>OmcAMbSGrqOgM zWXUJGRcHKEB2B-;07LB-%%srByv;^sXo{GiYc0m|oo5pm-{+12@HvB0^SiG4T`)U1 zj>-ZNWW~sIk7L;}^5uVu(x$%IBEy?m^}*tb zi5rY$HAbyUr!S$+phV?G2rIuLzNJhkX^0j`qC_38vd$EXg`M@GX@>H~W!G{3wVv3Q zC9j00vQ&z?C|TVhoI$BAuZ|~}$4H4b!dQ|ihdl{1n__LEBg^Jv)vhJ0x><3N^v2@d z%o_{TH&?#4p-k%{j!Pi@ryd+In*(Kzp+#Px=29Q@5xg8t@N)aSU^2VgZZKRrQAF6T ztPt6oOC|(2&yn5}oyZ`qk(9v-+3T4an>O=qYn+SQSI%Tud(Fq-Oz z5`9Xs2@CXHMLOV6#4aafP{s~kPVy4(34(M}T>_AI(C^k(04H*A@(#L%EA~@u)mAA( zK`==#JE=H3_K-R{uAjX|oPbW2Dye4hRT`pDyeVTC`l90>bc|GTtcqq0c#f=)b@?C(^VS4~5qlXgsW;`}qt+<u&d6H9An;9R)RuXNG2aSpcHdmVHf_=_q^-EoQP$tYzC{u;EOXEV5 zOXYx$a+%(>QJc)3b>`eg^Tn!Aw$*bnzr1 zrygAI1I=m`dO4ifIE8^&eWALtX=)$J&qITUGDxh*nn^=)1RQD1@}Et$S=v!dvtv0| zTOR;~eNZRD%K)XGs0Ru*%)&FND}Y*KbpvWF9w84itolu+d}jT;<=X{D5hjwH zbww1urv4Q>Q!WQn!B&#z9&%SrXg_f*^n<8}$zn<3s0K6!=oZYGABUoFe=NrHPorJ3 zW)(S-9=JV-8{r-`Z5y?pd&~sC9fIDzGa+d3)H|s+MUi_gW5;`o6t8nFZdju{ z3!YwY4y-Bxt)#H@%l=Rnx{2^$T;1d3z)yFsEq#Aoy=mv+!Hbxno8X<7*i*u?p{ke7 z8NTTK+b6`PygtyNS`1)+Z+QRYB=sh1WY(lyaZM|%e0eozu~BsWOPetA2kEFmLmU+> zmqjjx>cmxpG?$d%{oFU7$igu>T%cOYRA0{P?qo(PFc<(WvoGYSjg@s z2`Fn2UbJJWph*zhh?8yNLz$+`XxmRSSd|U|1dwVw;DZvxH3hBVJy}Dx+Vamu@y521>LeN8FyCfH*_KZrZVZ|=V{S%wVJjj z46ZEO5&jmi40Jonp4i#1*zAKTH{0@Ay(&GGESYKnbqR zAwj>(v!l$AuW&{vq2G2DV=i`*>zi#IR2556AchGrkzl8=&JA1+ykIEf=CApOs?@o0 z6(Yc4cMgQF_vqqmM!~}@g#vHk4bsBuQr_^aa->sEhl)qW#9HNq99I^L4%4fsi)V~= z&ZJp~nx1AFZ9D+u6*Hkg|18nE@k9z!wMGU`i6SurXJ`)BnF`6&PiubCp$S8zjl_QU zF_LWcX!n0k6$_}Xd-f9sa|y-(u}zOXd~iT|)5db=+H%{yLQ|7o8ZyG4swLZDi39y` zc$ASJ0z`L_ZYrEdJoHe#e3G2QaE4s-*p^r0y`Z6D8~uB+ip>>;H;q_+;UpoQlNqhL z0;yK3u6b{^u+%dL;nd`sCeT#05bE-hJa@{5fC&hoAq2PnP1Rmtj{IB`X+kY>eR57w zF5NepbNmyOUtV%7zmjcq!~Evn9b0kPe5ms+(*S^Jgu4DqM9132$JC9M5}(0EG<^V+c)1ljoP6uK(^_{oQ#top|0~ zmi43Vq`IHHmKKj8K@>lbb+q@5C*|9s36qb#MAn~~(D{+0UrSh7-u0NNbFcSE8Cq3L zH;5D?T^a<#9%gl~SceU^3S!9feg3!E3Vn2p{u##x;8Wzve&sF>5V1-sc zg&G8njJ?Z{77Qy`g9hj*Mk74w@PD(A=2CT;tT5Rhu;GN!u&!_wgy%%2R*$FWQ)VjU zCVHM1%nGCC{}T7~eG}L@`{z{8L5@h^vI*#}CcR%-XA~u)l$4Rw{Fd@K_UfUHDW;a6 zco~axkfJ)F);X!*HpAbHha!@_#@fm>goIu*@X+s015vWn%vroDMsUodpaA@HLsQY` zScSt9TFG9+$ieMbqP2x<%0AtVc+vZ{$*a=M$ni`+p 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 Date: Mon, 30 Sep 2024 18:09:04 -0400 Subject: [PATCH 271/342] also apply sensible regex warning for `repo: meta` --- pre_commit/clientlib.py | 2 ++ tests/clientlib_test.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index a49465e89..0127c4d05 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -289,6 +289,8 @@ def check(self, dct: dict[str, Any]) -> None: item for item in MANIFEST_HOOK_DICT.items ), + OptionalSensibleRegexAtHook('files', cfgv.check_string), + OptionalSensibleRegexAtHook('exclude', cfgv.check_string), ) CONFIG_HOOK_DICT = cfgv.Map( 'Hook', 'id', diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index eaa8a044c..9d31d3399 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -256,6 +256,24 @@ def test_validate_optional_sensible_regex_at_local_hook(caplog): ] +def test_validate_optional_sensible_regex_at_meta_hook(caplog): + config_obj = { + 'repo': 'meta', + 'hooks': [{'id': 'identity', 'files': 'dir/*.py'}], + } + + cfgv.validate(config_obj, CONFIG_REPO_DICT) + + assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + "The 'files' field in hook 'identity' is a regex, not a glob " + "-- matching '/*' probably isn't what you want here", + ), + ] + + @pytest.mark.parametrize( ('regex', 'warning'), ( From 7441a62eb1db5820d52a2c28afa3025773e4f015 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Sep 2024 18:41:13 -0400 Subject: [PATCH 272/342] add warning for deprecated stages names --- pre_commit/clientlib.py | 42 ++++++++++++++++++++++++++++++++++------- tests/clientlib_test.py | 26 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 0127c4d05..4e0425c33 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -99,6 +99,32 @@ def apply_default(self, dct: dict[str, Any]) -> None: super().apply_default(dct) +class DeprecatedStagesWarning(NamedTuple): + key: str + + def check(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + return + + val = dct[self.key] + cfgv.check_array(cfgv.check_any)(val) + + legacy_stages = [stage for stage in val if stage in _STAGES] + if legacy_stages: + logger.warning( + f'hook id `{dct["id"]}` uses deprecated stage names ' + f'({", ".join(legacy_stages)}) which will be removed in a ' + f'future version. ' + f'run: `pre-commit migrate-config` to automatically fix this.', + ) + + def apply_default(self, dct: dict[str, Any]) -> None: + pass + + def remove_default(self, dct: dict[str, Any]) -> None: + raise NotImplementedError + + MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -267,6 +293,12 @@ def check(self, dct: dict[str, Any]) -> None: raise cfgv.ValidationError(f'{self.key!r} cannot be overridden') +_COMMON_HOOK_WARNINGS = ( + OptionalSensibleRegexAtHook('files', cfgv.check_string), + OptionalSensibleRegexAtHook('exclude', cfgv.check_string), + DeprecatedStagesWarning('stages'), +) + META_HOOK_DICT = cfgv.Map( 'Hook', 'id', cfgv.Required('id', cfgv.check_string), @@ -289,8 +321,7 @@ def check(self, dct: dict[str, Any]) -> None: item for item in MANIFEST_HOOK_DICT.items ), - OptionalSensibleRegexAtHook('files', cfgv.check_string), - OptionalSensibleRegexAtHook('exclude', cfgv.check_string), + *_COMMON_HOOK_WARNINGS, ) CONFIG_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -308,16 +339,13 @@ def check(self, dct: dict[str, Any]) -> None: if item.key != 'stages' ), StagesMigrationNoDefault('stages', []), - OptionalSensibleRegexAtHook('files', cfgv.check_string), - OptionalSensibleRegexAtHook('exclude', cfgv.check_string), + *_COMMON_HOOK_WARNINGS, ) LOCAL_HOOK_DICT = cfgv.Map( 'Hook', 'id', *MANIFEST_HOOK_DICT.items, - - OptionalSensibleRegexAtHook('files', cfgv.check_string), - OptionalSensibleRegexAtHook('exclude', cfgv.check_string), + *_COMMON_HOOK_WARNINGS, ) CONFIG_REPO_DICT = cfgv.Map( 'Repository', 'repo', diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 9d31d3399..1335e0868 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -309,6 +309,32 @@ def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning): assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)] +def test_warning_for_deprecated_stages(caplog): + config_obj = sample_local_config() + config_obj['hooks'][0]['stages'] = ['commit', 'push'] + + cfgv.validate(config_obj, CONFIG_REPO_DICT) + + assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + 'hook id `do_not_commit` uses deprecated stage names ' + '(commit, push) which will be removed in a future version. ' + 'run: `pre-commit migrate-config` to automatically fix this.', + ), + ] + + +def test_no_warning_for_non_deprecated_stages(caplog): + config_obj = sample_local_config() + config_obj['hooks'][0]['stages'] = ['pre-commit', 'pre-push'] + + cfgv.validate(config_obj, CONFIG_REPO_DICT) + + assert caplog.record_tuples == [] + + @pytest.mark.parametrize( 'manifest_obj', ( From 33e020f315a0f8654500ffbbb103ef7b39fd7ff9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Sep 2024 19:22:14 -0400 Subject: [PATCH 273/342] add warning for deprecated stages values in `default_stages` --- pre_commit/clientlib.py | 27 +++++++++++++++++++++++++++ tests/clientlib_test.py | 24 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 4e0425c33..f78850718 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -125,6 +125,32 @@ def remove_default(self, dct: dict[str, Any]) -> None: raise NotImplementedError +class DeprecatedDefaultStagesWarning(NamedTuple): + key: str + + def check(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + return + + val = dct[self.key] + cfgv.check_array(cfgv.check_any)(val) + + legacy_stages = [stage for stage in val if stage in _STAGES] + if legacy_stages: + logger.warning( + f'top-level `default_stages` uses deprecated stage names ' + f'({", ".join(legacy_stages)}) which will be removed in a ' + f'future version. ' + f'run: `pre-commit migrate-config` to automatically fix this.', + ) + + def apply_default(self, dct: dict[str, Any]) -> None: + pass + + def remove_default(self, dct: dict[str, Any]) -> None: + raise NotImplementedError + + MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -398,6 +424,7 @@ def check(self, dct: dict[str, Any]) -> None: 'default_language_version', DEFAULT_LANGUAGE_VERSION, {}, ), StagesMigration('default_stages', STAGES), + DeprecatedDefaultStagesWarning('default_stages'), cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('exclude', check_string_regex, '^$'), cfgv.Optional('fail_fast', cfgv.check_bool, False), diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 1335e0868..7aa84af0e 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -335,6 +335,30 @@ def test_no_warning_for_non_deprecated_stages(caplog): assert caplog.record_tuples == [] +def test_warning_for_deprecated_default_stages(caplog): + cfg = {'default_stages': ['commit', 'push'], 'repos': []} + + cfgv.validate(cfg, CONFIG_SCHEMA) + + assert caplog.record_tuples == [ + ( + 'pre_commit', + logging.WARNING, + 'top-level `default_stages` uses deprecated stage names ' + '(commit, push) which will be removed in a future version. ' + 'run: `pre-commit migrate-config` to automatically fix this.', + ), + ] + + +def test_no_warning_for_non_deprecated_default_stages(caplog): + cfg = {'default_stages': ['pre-commit', 'pre-push'], 'repos': []} + + cfgv.validate(cfg, CONFIG_SCHEMA) + + assert caplog.record_tuples == [] + + @pytest.mark.parametrize( 'manifest_obj', ( From 1d2f1c0ccea63906c8bcc9265bb9940383341c0c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Sep 2024 19:58:16 -0400 Subject: [PATCH 274/342] replace log_info_mock with pytest's caplog --- tests/conftest.py | 7 ------- tests/repository_test.py | 8 ++++---- tests/store_test.py | 8 ++++---- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index bd4af9a52..8c9cd14db 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,6 @@ import functools import io -import logging import os.path from unittest import mock @@ -203,12 +202,6 @@ def store(tempdir_factory): yield Store(os.path.join(tempdir_factory.get(), '.pre-commit')) -@pytest.fixture -def log_info_mock(): - with mock.patch.object(logging.getLogger('pre_commit'), 'info') as mck: - yield mck - - class Fixture: def __init__(self, stream: io.BytesIO) -> None: self._stream = stream diff --git a/tests/repository_test.py b/tests/repository_test.py index ac065ec40..32c361efa 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -240,16 +240,16 @@ def test_unknown_keys(store, caplog): assert msg == 'Unexpected key(s) present on local => too-much: foo, hello' -def test_reinstall(tempdir_factory, store, log_info_mock): +def test_reinstall(tempdir_factory, store, caplog): path = make_repo(tempdir_factory, 'python_hooks_repo') config = make_config_from_repo(path) _get_hook(config, store, 'foo') # We print some logging during clone (1) + install (3) - assert log_info_mock.call_count == 4 - log_info_mock.reset_mock() + assert len(caplog.record_tuples) == 4 + caplog.clear() # Reinstall on another run should not trigger another install _get_hook(config, store, 'foo') - assert log_info_mock.call_count == 0 + assert len(caplog.record_tuples) == 0 def test_control_c_control_c_on_install(tempdir_factory, store): diff --git a/tests/store_test.py b/tests/store_test.py index 45ec73272..b6b0a0b0f 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -65,7 +65,7 @@ def test_store_init(store): assert text_line in readme_contents -def test_clone(store, tempdir_factory, log_info_mock): +def test_clone(store, tempdir_factory, caplog): path = git_dir(tempdir_factory) with cwd(path): git_commit() @@ -74,7 +74,7 @@ def test_clone(store, tempdir_factory, log_info_mock): ret = store.clone(path, rev) # Should have printed some stuff - assert log_info_mock.call_args_list[0][0][0].startswith( + assert caplog.record_tuples[0][-1].startswith( 'Initializing environment for ', ) @@ -118,7 +118,7 @@ def test_clone_when_repo_already_exists(store): def test_clone_shallow_failure_fallback_to_complete( store, tempdir_factory, - log_info_mock, + caplog, ): path = git_dir(tempdir_factory) with cwd(path): @@ -134,7 +134,7 @@ def fake_shallow_clone(self, *args, **kwargs): ret = store.clone(path, rev) # Should have printed some stuff - assert log_info_mock.call_args_list[0][0][0].startswith( + assert caplog.record_tuples[0][-1].startswith( 'Initializing environment for ', ) From d31722386e57a98d8d7d6d74228d255b9a9ffaf3 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 30 Sep 2024 20:29:19 -0400 Subject: [PATCH 275/342] add warning for deprecates stages for remote repos on init --- pre_commit/clientlib.py | 38 +++++++++++++++++++++++ pre_commit/store.py | 5 +++ tests/store_test.py | 69 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index f78850718..c0f736d92 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -2,6 +2,7 @@ import functools import logging +import os.path import re import shlex import sys @@ -70,6 +71,43 @@ def transform_stage(stage: str) -> str: return _STAGES.get(stage, stage) +MINIMAL_MANIFEST_SCHEMA = cfgv.Array( + cfgv.Map( + 'Hook', 'id', + cfgv.Required('id', cfgv.check_string), + cfgv.Optional('stages', cfgv.check_array(cfgv.check_string), []), + ), +) + + +def warn_for_stages_on_repo_init(repo: str, directory: str) -> None: + try: + manifest = cfgv.load_from_filename( + os.path.join(directory, C.MANIFEST_FILE), + schema=MINIMAL_MANIFEST_SCHEMA, + load_strategy=yaml_load, + exc_tp=InvalidManifestError, + ) + except InvalidManifestError: + return # they'll get a better error message when it actually loads! + + legacy_stages = {} # sorted set + for hook in manifest: + for stage in hook.get('stages', ()): + if stage in _STAGES: + legacy_stages[stage] = True + + if legacy_stages: + logger.warning( + f'repo `{repo}` uses deprecated stage names ' + f'({", ".join(legacy_stages)}) which will be removed in a ' + f'future version. ' + f'Hint: often `pre-commit autoupdate --repo {shlex.quote(repo)}` ' + f'will fix this. ' + f'if it does not -- consider reporting an issue to that repo.', + ) + + class StagesMigrationNoDefault(NamedTuple): key: str default: Sequence[str] diff --git a/pre_commit/store.py b/pre_commit/store.py index 36cc49456..1235942c5 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -10,6 +10,7 @@ from typing import Callable import pre_commit.constants as C +from pre_commit import clientlib from pre_commit import file_lock from pre_commit import git from pre_commit.util import CalledProcessError @@ -136,6 +137,7 @@ def _new_repo( deps: Sequence[str], make_strategy: Callable[[str], None], ) -> str: + original_repo = repo repo = self.db_repo_name(repo, deps) def _get_result() -> str | None: @@ -168,6 +170,9 @@ def _get_result() -> str | None: 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)', [repo, ref, directory], ) + + clientlib.warn_for_stages_on_repo_init(original_repo, directory) + return directory def _complete_clone(self, ref: str, git_cmd: Callable[..., None]) -> None: diff --git a/tests/store_test.py b/tests/store_test.py index b6b0a0b0f..7d4dffb09 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -1,12 +1,15 @@ from __future__ import annotations +import logging import os.path +import shlex import sqlite3 import stat from unittest import mock import pytest +import pre_commit.constants as C from pre_commit import git from pre_commit.store import _get_default_directory from pre_commit.store import _LOCAL_RESOURCES @@ -91,6 +94,72 @@ def test_clone(store, tempdir_factory, caplog): assert store.select_all_repos() == [(path, rev, ret)] +def test_warning_for_deprecated_stages_on_init(store, tempdir_factory, caplog): + manifest = '''\ +- id: hook1 + name: hook1 + language: system + entry: echo hook1 + stages: [commit, push] +- id: hook2 + name: hook2 + language: system + entry: echo hook2 + stages: [push, merge-commit] +''' + + path = git_dir(tempdir_factory) + with open(os.path.join(path, C.MANIFEST_FILE), 'w') as f: + f.write(manifest) + cmd_output('git', 'add', '.', cwd=path) + git_commit(cwd=path) + rev = git.head_rev(path) + + store.clone(path, rev) + assert caplog.record_tuples[1] == ( + 'pre_commit', + logging.WARNING, + f'repo `{path}` uses deprecated stage names ' + f'(commit, push, merge-commit) which will be removed in a future ' + f'version. ' + f'Hint: often `pre-commit autoupdate --repo {shlex.quote(path)}` ' + f'will fix this. ' + f'if it does not -- consider reporting an issue to that repo.', + ) + + # should not re-warn + caplog.clear() + store.clone(path, rev) + assert caplog.record_tuples == [] + + +def test_no_warning_for_non_deprecated_stages_on_init( + store, tempdir_factory, caplog, +): + manifest = '''\ +- id: hook1 + name: hook1 + language: system + entry: echo hook1 + stages: [pre-commit, pre-push] +- id: hook2 + name: hook2 + language: system + entry: echo hook2 + stages: [pre-push, pre-merge-commit] +''' + + path = git_dir(tempdir_factory) + with open(os.path.join(path, C.MANIFEST_FILE), 'w') as f: + f.write(manifest) + cmd_output('git', 'add', '.', cwd=path) + git_commit(cwd=path) + rev = git.head_rev(path) + + store.clone(path, rev) + assert logging.WARNING not in {tup[1] for tup in caplog.record_tuples} + + def test_clone_cleans_up_on_checkout_failure(store): with pytest.raises(Exception) as excinfo: # This raises an exception because you can't clone something that From 801b956304e2ad2738bdb76d9c65ed52e967bb57 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 5 Oct 2024 13:30:25 -0400 Subject: [PATCH 276/342] remove deprecated python_venv alias --- pre_commit/all_languages.py | 2 -- pre_commit/repository.py | 9 --------- tests/all_languages_test.py | 7 ------- tests/repository_test.py | 18 ------------------ 4 files changed, 36 deletions(-) delete mode 100644 tests/all_languages_test.py diff --git a/pre_commit/all_languages.py b/pre_commit/all_languages.py index 476bad9da..f2d11bb60 100644 --- a/pre_commit/all_languages.py +++ b/pre_commit/all_languages.py @@ -44,7 +44,5 @@ 'script': script, 'swift': swift, 'system': system, - # TODO: fully deprecate `python_venv` - 'python_venv': python, } language_names = sorted(languages) diff --git a/pre_commit/repository.py b/pre_commit/repository.py index aa8418563..a9461ab63 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -3,7 +3,6 @@ import json import logging import os -import shlex from collections.abc import Sequence from typing import Any @@ -68,14 +67,6 @@ def _hook_install(hook: Hook) -> None: logger.info('Once installed this environment will be reused.') logger.info('This may take a few minutes...') - if hook.language == 'python_venv': - logger.warning( - f'`repo: {hook.src}` uses deprecated `language: python_venv`. ' - f'This is an alias for `language: python`. ' - f'Often `pre-commit autoupdate --repo {shlex.quote(hook.src)}` ' - f'will fix this.', - ) - lang = languages[hook.language] assert lang.ENVIRONMENT_DIR is not None diff --git a/tests/all_languages_test.py b/tests/all_languages_test.py deleted file mode 100644 index 98c912150..000000000 --- a/tests/all_languages_test.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -from pre_commit.all_languages import languages - - -def test_python_venv_is_an_alias_to_python(): - assert languages['python_venv'] is languages['python'] diff --git a/tests/repository_test.py b/tests/repository_test.py index 32c361efa..b54c910d3 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -80,24 +80,6 @@ def _test_hook_repo( assert out == expected -def test_python_venv_deprecation(store, caplog): - config = { - 'repo': 'local', - 'hooks': [{ - 'id': 'example', - 'name': 'example', - 'language': 'python_venv', - 'entry': 'echo hi', - }], - } - _get_hook(config, store, 'example') - assert caplog.messages[-1] == ( - '`repo: local` uses deprecated `language: python_venv`. ' - 'This is an alias for `language: python`. ' - 'Often `pre-commit autoupdate --repo local` will fix this.' - ) - - def test_system_hook_with_spaces(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'system_hook_with_spaces_repo', From dbccd57db0e9cf993ea909e929eea97f6e4389ea Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 5 Oct 2024 14:58:22 -0400 Subject: [PATCH 277/342] v4.0.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49094bbb9..2e4dd3cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +4.0.0 - 2024-10-05 +================== + +### Features +- Improve `pre-commit migrate-config` to handle more yaml formats. + - #3301 PR by @asottile. +- Handle `stages` deprecation in `pre-commit migrate-config`. + - #3302 PR by @asottile. + - #2732 issue by @asottile. +- Upgrade `ruby-build`. + - #3199 PR by @ThisGuyCodes. +- Add "sensible regex" warnings to `repo: meta`. + - #3311 PR by @asottile. +- Add warnings for deprecated `stages` (`commit` -> `pre-commit`, `push` -> + `pre-push`, `merge-commit` -> `pre-merge-commit`). + - #3312 PR by @asottile. + - #3313 PR by @asottile. + - #3315 PR by @asottile. + - #2732 issue by @asottile. + +### Migrating +- `language: python_venv` has been removed -- use `language: python` instead. + - #3320 PR by @asottile. + - #2734 issue by @asottile. + 3.8.0 - 2024-07-28 ================== diff --git a/setup.cfg b/setup.cfg index 52b7681ef..70289e1fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 3.8.0 +version = 4.0.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 4235a877f3ac4998b41e9cce8a709ac13de159b5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:02:26 +0000 Subject: [PATCH 278/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87b8551d4..7bd2611f8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer From 222c62bc5d2907efbd6052c5fb89c4c027400044 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 8 Oct 2024 11:46:48 -0400 Subject: [PATCH 279/342] fix migrate-config for purelib yaml --- pre_commit/commands/migrate_config.py | 3 ++- tests/commands/migrate_config_test.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index ada094fa2..c5d47a08e 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -47,7 +47,8 @@ def _migrate_map(contents: str) -> str: def _preserve_style(n: ScalarNode, *, s: str) -> str: - return f'{n.style}{s}{n.style}' + style = n.style or '' + return f'{style}{s}{style}' def _fix_stage(n: ScalarNode) -> str: diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py index 9ffae6eef..a517d2f44 100644 --- a/tests/commands/migrate_config_test.py +++ b/tests/commands/migrate_config_test.py @@ -1,10 +1,26 @@ from __future__ import annotations +from unittest import mock + import pytest +import yaml import pre_commit.constants as C from pre_commit.clientlib import InvalidConfigError from pre_commit.commands.migrate_config import migrate_config +from pre_commit.yaml import yaml_compose + + +@pytest.fixture(autouse=True, params=['c', 'pure']) +def switch_pyyaml_impl(request): + if request.param == 'c': + yield + else: + with mock.patch.dict( + yaml_compose.keywords, + {'Loader': yaml.SafeLoader}, + ): + yield def test_migrate_config_normal_format(tmpdir, capsys): From cc4a52241565440ce200666799eef70626457488 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 8 Oct 2024 12:08:49 -0400 Subject: [PATCH 280/342] v4.0.1 --- CHANGELOG.md | 9 +++++++++ setup.cfg | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e4dd3cbb..a9b4c8c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +4.0.1 - 2024-10-08 +================== + +### Fixes +- Fix `pre-commit migrate-config` for unquoted deprecated stages names with + purelib `pyyaml`. + - #3324 PR by @asottile. + - pre-commit-ci/issues#234 issue by @lorenzwalthert. + 4.0.0 - 2024-10-05 ================== diff --git a/setup.cfg b/setup.cfg index 70289e1fa..6936a1f0d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.0.0 +version = 4.0.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 46de4da34e362e8dfa34c08205b662da8ab47788 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:30:38 +0000 Subject: [PATCH 281/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.5.0 → v2.7.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.5.0...v2.7.0) - [github.com/asottile/reorder-python-imports: v3.13.0 → v3.14.0](https://github.com/asottile/reorder-python-imports/compare/v3.13.0...v3.14.0) - [github.com/asottile/pyupgrade: v3.17.0 → v3.18.0](https://github.com/asottile/pyupgrade/compare/v3.17.0...v3.18.0) - [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.12.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.12.1) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7bd2611f8..33c905cd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,11 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.5.0 + rev: v2.7.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.13.0 + rev: v3.14.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.17.0 + rev: v3.18.0 hooks: - id: pyupgrade args: [--py39-plus] @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.12.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 0de4c8028a95c1a7dfd57e772ec11ce3a71834cc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Oct 2024 20:35:56 -0400 Subject: [PATCH 282/342] remove unused type ignore --- testing/make-archives | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testing/make-archives b/testing/make-archives index 251be4a58..eb3f3af85 100755 --- a/testing/make-archives +++ b/testing/make-archives @@ -57,8 +57,7 @@ def make_archive(name: str, repo: str, ref: str, destdir: str) -> str: arcs.sort() with gzip.GzipFile(output_path, 'wb', mtime=0) as gzipf: - # https://github.com/python/typeshed/issues/5491 - with tarfile.open(fileobj=gzipf, mode='w') as tf: # type: ignore + with tarfile.open(fileobj=gzipf, mode='w') as tf: for arcname, abspath in arcs: tf.add( abspath, From 708ca3b581f3fa033d918dd6d5b3794803d4dbb2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:56:52 +0000 Subject: [PATCH 283/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.18.0 → v3.19.0](https://github.com/asottile/pyupgrade/compare/v3.18.0...v3.19.0) - [github.com/pre-commit/mirrors-mypy: v1.12.1 → v1.13.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.12.1...v1.13.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33c905cd4..614170ba7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.18.0 + rev: v3.19.0 hooks: - id: pyupgrade args: [--py39-plus] @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.12.1 + rev: v1.13.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 85783bdc0ba86c3e772612a44b8825de1d24a6da Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Fri, 1 Nov 2024 15:24:51 +0100 Subject: [PATCH 284/342] Add support for julia hooks This patch adds 2nd class support for hooks using julia as the language. pre-commit will install any dependencies defined in the hooks repo `Project.toml` file, with support for `additional_dependencies` as well. Julia doesn't (yet) have a way to install binaries/scripts so for julia hooks the `entry` value is a (relative) path to a julia script within the hooks repository. When executing a julia hook the (globally installed) julia interpreter is prepended to the entry. Example `.pre-commit-hooks.yaml`: ```yaml - id: foo name: ... language: julia entry: bin/foo.jl --arg1 ``` Example hooks repo: https://github.com/fredrikekre/runic-pre-commit/tree/fe/julia Accompanying pre-commit.com PR: https://github.com/pre-commit/pre-commit.com/pull/998 Fixes #2689. --- pre_commit/all_languages.py | 2 + pre_commit/languages/julia.py | 132 ++++++++++++++++++++++++++++++++++ tests/languages/julia_test.py | 97 +++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 pre_commit/languages/julia.py create mode 100644 tests/languages/julia_test.py diff --git a/pre_commit/all_languages.py b/pre_commit/all_languages.py index f2d11bb60..ba569c377 100644 --- a/pre_commit/all_languages.py +++ b/pre_commit/all_languages.py @@ -10,6 +10,7 @@ from pre_commit.languages import fail from pre_commit.languages import golang from pre_commit.languages import haskell +from pre_commit.languages import julia from pre_commit.languages import lua from pre_commit.languages import node from pre_commit.languages import perl @@ -33,6 +34,7 @@ 'fail': fail, 'golang': golang, 'haskell': haskell, + 'julia': julia, 'lua': lua, 'node': node, 'perl': perl, diff --git a/pre_commit/languages/julia.py b/pre_commit/languages/julia.py new file mode 100644 index 000000000..df91c0697 --- /dev/null +++ b/pre_commit/languages/julia.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +import contextlib +import os +import shutil +from collections.abc import Generator +from collections.abc import Sequence + +from pre_commit import lang_base +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import UNSET +from pre_commit.prefix import Prefix +from pre_commit.util import cmd_output_b + +ENVIRONMENT_DIR = 'juliaenv' +health_check = lang_base.basic_health_check +get_default_version = lang_base.basic_get_default_version + + +def run_hook( + prefix: Prefix, + entry: str, + args: Sequence[str], + file_args: Sequence[str], + *, + is_local: bool, + require_serial: bool, + color: bool, +) -> tuple[int, bytes]: + # `entry` is a (hook-repo relative) file followed by (optional) args, e.g. + # `bin/id.jl` or `bin/hook.jl --arg1 --arg2` so we + # 1) shell parse it and join with args with hook_cmd + # 2) prepend the hooks prefix path to the first argument (the file), unless + # it is a local script + # 3) prepend `julia` as the interpreter + + cmd = lang_base.hook_cmd(entry, args) + script = cmd[0] if is_local else prefix.path(cmd[0]) + cmd = ('julia', script, *cmd[1:]) + return lang_base.run_xargs( + cmd, + file_args, + require_serial=require_serial, + color=color, + ) + + +def get_env_patch(target_dir: str, version: str) -> PatchesT: + return ( + ('JULIA_LOAD_PATH', target_dir), + # May be set, remove it to not interfer with LOAD_PATH + ('JULIA_PROJECT', UNSET), + ) + + +@contextlib.contextmanager +def in_env(prefix: Prefix, version: str) -> Generator[None]: + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + with envcontext(get_env_patch(envdir, version)): + yield + + +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version) + with in_env(prefix, version): + # TODO: Support language_version with juliaup similar to rust via + # rustup + # if version != 'system': + # ... + + # Copy Project.toml to hook env if it exist + os.makedirs(envdir, exist_ok=True) + project_names = ('JuliaProject.toml', 'Project.toml') + project_found = False + for project_name in project_names: + project_file = prefix.path(project_name) + if not os.path.isfile(project_file): + continue + shutil.copy(project_file, envdir) + project_found = True + break + + # If no project file was found we create an empty one so that the + # package manager doesn't error + if not project_found: + open(os.path.join(envdir, 'Project.toml'), 'a').close() + + # Copy Manifest.toml to hook env if it exists + manifest_names = ('JuliaManifest.toml', 'Manifest.toml') + for manifest_name in manifest_names: + manifest_file = prefix.path(manifest_name) + if not os.path.isfile(manifest_file): + continue + shutil.copy(manifest_file, envdir) + break + + # Julia code to instantiate the hook environment + julia_code = """ + @assert length(ARGS) > 0 + hook_env = ARGS[1] + deps = join(ARGS[2:end], " ") + + # We prepend @stdlib here so that we can load the package manager even + # though `get_env_patch` limits `JULIA_LOAD_PATH` to just the hook env. + pushfirst!(LOAD_PATH, "@stdlib") + using Pkg + popfirst!(LOAD_PATH) + + # Instantiate the environment shipped with the hook repo. If we have + # additional dependencies we disable precompilation in this step to + # avoid double work. + precompile = isempty(deps) ? "1" : "0" + withenv("JULIA_PKG_PRECOMPILE_AUTO" => precompile) do + Pkg.instantiate() + end + + # Add additional dependencies (with precompilation) + if !isempty(deps) + withenv("JULIA_PKG_PRECOMPILE_AUTO" => "1") do + Pkg.REPLMode.pkgstr("add " * deps) + end + end + """ + cmd_output_b( + 'julia', '-e', julia_code, '--', envdir, *additional_dependencies, + cwd=prefix.prefix_dir, + ) diff --git a/tests/languages/julia_test.py b/tests/languages/julia_test.py new file mode 100644 index 000000000..4ea3c25b2 --- /dev/null +++ b/tests/languages/julia_test.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +from pre_commit.languages import julia +from testing.language_helpers import run_language +from testing.util import cwd + + +def _make_hook(tmp_path, julia_code): + src_dir = tmp_path.joinpath('src') + src_dir.mkdir() + src_dir.joinpath('main.jl').write_text(julia_code) + tmp_path.joinpath('Project.toml').write_text( + '[deps]\n' + 'Example = "7876af07-990d-54b4-ab0e-23690620f79a"\n', + ) + + +def test_julia_hook(tmp_path): + code = """ + using Example + function main() + println("Hello, world!") + end + main() + """ + _make_hook(tmp_path, code) + expected = (0, b'Hello, world!\n') + assert run_language(tmp_path, julia, 'src/main.jl') == expected + + +def test_julia_hook_manifest(tmp_path): + code = """ + using Example + println(pkgversion(Example)) + """ + _make_hook(tmp_path, code) + + tmp_path.joinpath('Manifest.toml').write_text( + 'manifest_format = "2.0"\n\n' + '[[deps.Example]]\n' + 'git-tree-sha1 = "11820aa9c229fd3833d4bd69e5e75ef4e7273bf1"\n' + 'uuid = "7876af07-990d-54b4-ab0e-23690620f79a"\n' + 'version = "0.5.4"\n', + ) + expected = (0, b'0.5.4\n') + assert run_language(tmp_path, julia, 'src/main.jl') == expected + + +def test_julia_hook_args(tmp_path): + code = """ + function main(argv) + foreach(println, argv) + end + main(ARGS) + """ + _make_hook(tmp_path, code) + expected = (0, b'--arg1\n--arg2\n') + assert run_language( + tmp_path, julia, 'src/main.jl --arg1 --arg2', + ) == expected + + +def test_julia_hook_additional_deps(tmp_path): + code = """ + using TOML + function main() + project_file = Base.active_project() + dict = TOML.parsefile(project_file) + for (k, v) in dict["deps"] + println(k, " = ", v) + end + end + main() + """ + _make_hook(tmp_path, code) + deps = ('TOML=fa267f1f-6049-4f14-aa54-33bafae1ed76',) + ret, out = run_language(tmp_path, julia, 'src/main.jl', deps=deps) + assert ret == 0 + assert b'Example = 7876af07-990d-54b4-ab0e-23690620f79a' in out + assert b'TOML = fa267f1f-6049-4f14-aa54-33bafae1ed76' in out + + +def test_julia_repo_local(tmp_path): + env_dir = tmp_path.joinpath('envdir') + env_dir.mkdir() + local_dir = tmp_path.joinpath('local') + local_dir.mkdir() + local_dir.joinpath('local.jl').write_text( + 'using TOML; foreach(println, ARGS)', + ) + with cwd(local_dir): + deps = ('TOML=fa267f1f-6049-4f14-aa54-33bafae1ed76',) + expected = (0, b'--local-arg1\n--local-arg2\n') + assert run_language( + env_dir, julia, 'local.jl --local-arg1 --local-arg2', + deps=deps, is_local=True, + ) == expected From 109628c5058e6901cc69a1d0dfa3c2a0e0ea14d8 Mon Sep 17 00:00:00 2001 From: AleksaC Date: Thu, 19 Sep 2024 01:01:33 +0200 Subject: [PATCH 285/342] disable automatic toolchain switching for golang hooks --- pre_commit/languages/golang.py | 2 + tests/languages/golang_test.py | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 609087962..678c04b14 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -75,6 +75,7 @@ def get_env_patch(venv: str, version: str) -> PatchesT: return ( ('GOROOT', os.path.join(venv, '.go')), + ('GOTOOLCHAIN', 'local'), ( 'PATH', ( os.path.join(venv, 'bin'), os.pathsep, @@ -145,6 +146,7 @@ def install_environment( env = no_git_env(dict(os.environ, GOPATH=gopath)) env.pop('GOBIN', None) if version != 'system': + env['GOTOOLCHAIN'] = 'local' env['GOROOT'] = os.path.join(env_dir, '.go') env['PATH'] = os.pathsep.join(( os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'], diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py index 02e35d710..7fb6ab18b 100644 --- a/tests/languages/golang_test.py +++ b/tests/languages/golang_test.py @@ -11,11 +11,13 @@ from pre_commit.envcontext import envcontext from pre_commit.languages import golang from pre_commit.store import _make_local_repo +from pre_commit.util import CalledProcessError from pre_commit.util import cmd_output from testing.fixtures import add_config_to_repo from testing.fixtures import make_config_from_repo from testing.language_helpers import run_language from testing.util import cmd_output_mocked_pre_commit_home +from testing.util import cwd from testing.util import git_commit @@ -165,3 +167,70 @@ def test_during_commit_all(tmp_path, tempdir_factory, store, in_git_dir): fn=cmd_output_mocked_pre_commit_home, tempdir_factory=tempdir_factory, ) + + +def test_automatic_toolchain_switching(tmp_path): + go_mod = '''\ +module toolchain-version-test + +go 1.23.1 +''' + main_go = '''\ +package main + +func main() {} +''' + tmp_path.joinpath('go.mod').write_text(go_mod) + mod_dir = tmp_path.joinpath('toolchain-version-test') + mod_dir.mkdir() + main_file = mod_dir.joinpath('main.go') + main_file.write_text(main_go) + + with pytest.raises(CalledProcessError) as excinfo: + run_language( + path=tmp_path, + language=golang, + version='1.22.0', + exe='golang-version-test', + ) + + assert 'go.mod requires go >= 1.23.1' in excinfo.value.stderr.decode() + + +def test_automatic_toolchain_switching_go_fmt(tmp_path, monkeypatch): + go_mod_hook = '''\ +module toolchain-version-test + +go 1.22.0 +''' + go_mod = '''\ +module toolchain-version-test + +go 1.23.1 +''' + main_go = '''\ +package main + +func main() {} +''' + hook_dir = tmp_path.joinpath('hook') + hook_dir.mkdir() + hook_dir.joinpath('go.mod').write_text(go_mod_hook) + + test_dir = tmp_path.joinpath('test') + test_dir.mkdir() + test_dir.joinpath('go.mod').write_text(go_mod) + main_file = test_dir.joinpath('main.go') + main_file.write_text(main_go) + + with cwd(test_dir): + ret, out = run_language( + path=hook_dir, + language=golang, + version='1.22.0', + exe='go fmt', + file_args=(str(main_file),), + ) + + assert ret == 1 + assert 'go.mod requires go >= 1.23.1' in out.decode() From db85eeed2d114b1fb60cc6c969573fefa23c4fc8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:45:24 +0000 Subject: [PATCH 286/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.19.0 → v3.19.1](https://github.com/asottile/pyupgrade/compare/v3.19.0...v3.19.1) - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.14.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 614170ba7..5743224e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 + rev: v3.19.1 hooks: - id: pyupgrade args: [--py39-plus] @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.13.0 + rev: v1.14.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From aa85be934071e7c73fb49e9339e307285a784d16 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Dec 2024 15:43:55 -0500 Subject: [PATCH 287/342] fix docker_image test when ubuntu:22.04 image is not pre-pulled --- tests/languages/docker_image_test.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/languages/docker_image_test.py b/tests/languages/docker_image_test.py index 4e3a8789a..4f720600b 100644 --- a/tests/languages/docker_image_test.py +++ b/tests/languages/docker_image_test.py @@ -1,10 +1,18 @@ from __future__ import annotations +import pytest + from pre_commit.languages import docker_image +from pre_commit.util import cmd_output_b from testing.language_helpers import run_language from testing.util import xfailif_windows +@pytest.fixture(autouse=True, scope='module') +def _ensure_image_available(): + cmd_output_b('docker', 'run', '--rm', 'ubuntu:22.04', 'echo') + + @xfailif_windows # pragma: win32 no cover def test_docker_image_hook_via_entrypoint(tmp_path): ret = run_language( From 28c3d81bd27fe5e62eead459c1963a582e763bd7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Dec 2024 15:50:58 -0500 Subject: [PATCH 288/342] update .net tests to use .net 8 .net 6 and 7 were removed from github actions runners --- tests/languages/dotnet_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/languages/dotnet_test.py b/tests/languages/dotnet_test.py index 470c03b22..ee4082568 100644 --- a/tests/languages/dotnet_test.py +++ b/tests/languages/dotnet_test.py @@ -27,7 +27,7 @@ def _csproj(tool_name): Exe - net6 + net8 true {tool_name} ./nupkg From 77edad8455e88b403e055d2692c9545085cf3edb Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 28 Dec 2024 16:06:00 -0500 Subject: [PATCH 289/342] install r on ubuntu runners this was removed in ubuntu-24.04 github actions runner --- .github/workflows/languages.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index 7d50535f8..61293a0d8 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -65,6 +65,8 @@ jobs: if: matrix.os == 'windows-latest' && matrix.language == 'perl' - uses: haskell/actions/setup@v2 if: matrix.language == 'haskell' + - uses: r-lib/actions/setup-r@v2 + if: matrix.os == 'ubuntu-latest' && matrix.language == 'r' - name: install deps run: python -mpip install -e . -r requirements-dev.txt From 9b9f8e254d46da65c8544244c423596d54260e24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:30:19 +0000 Subject: [PATCH 290/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.14.0 → v1.14.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.0...v1.14.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5743224e8..4a23da2bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.0 + rev: v1.14.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] From c2c061cf63e00a3ff8c88a9054c47e96a36f2daa Mon Sep 17 00:00:00 2001 From: Lorenz Walthert Date: Sun, 19 Jan 2025 19:43:24 +0100 Subject: [PATCH 291/342] fix: ensure env patch is applied for vanilla emulation otherwise, installing the hooks when RENV_USER env variable is set (e.g. in RStudio with renv project) will result in executing the installation script in the wrong renv --- pre_commit/languages/r.py | 48 +++++++++++++++++++++++++++++---------- tests/languages/r_test.py | 2 +- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py index c75a30893..f70d2fdca 100644 --- a/pre_commit/languages/r.py +++ b/pre_commit/languages/r.py @@ -15,27 +15,50 @@ from pre_commit.envcontext import UNSET from pre_commit.prefix import Prefix from pre_commit.util import cmd_output -from pre_commit.util import cmd_output_b from pre_commit.util import win_exe ENVIRONMENT_DIR = 'renv' -RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') get_default_version = lang_base.basic_get_default_version +_RENV_ACTIVATED_OPTS = ( + '--no-save', '--no-restore', '--no-site-file', '--no-environ', +) -def _execute_vanilla_r_code_as_script( + +def _execute_r( code: str, *, prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str, + cli_opts: Sequence[str], ) -> str: with in_env(prefix, version), _r_code_in_tempfile(code) as f: _, out, _ = cmd_output( - _rscript_exec(), *RSCRIPT_OPTS, f, *args, cwd=cwd, + _rscript_exec(), *cli_opts, f, *args, cwd=cwd, ) return out.rstrip('\n') +def _execute_r_in_renv( + code: str, *, + prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str, +) -> str: + return _execute_r( + code=code, prefix=prefix, version=version, args=args, cwd=cwd, + cli_opts=_RENV_ACTIVATED_OPTS, + ) + + +def _execute_vanilla_r( + code: str, *, + prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str, +) -> str: + return _execute_r( + code=code, prefix=prefix, version=version, args=args, cwd=cwd, + cli_opts=('--vanilla',), + ) + + def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str: - return _execute_vanilla_r_code_as_script( + return _execute_r_in_renv( 'cat(renv::settings$r.version())', prefix=prefix, version=version, cwd=envdir, @@ -43,7 +66,7 @@ def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str: def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str: - return _execute_vanilla_r_code_as_script( + return _execute_r_in_renv( 'cat(as.character(getRversion()))', prefix=prefix, version=version, cwd=envdir, @@ -53,7 +76,7 @@ def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str: def _write_current_r_version( envdir: str, prefix: Prefix, version: str, ) -> None: - _execute_vanilla_r_code_as_script( + _execute_r_in_renv( 'renv::settings$r.version(as.character(getRversion()))', prefix=prefix, version=version, cwd=envdir, @@ -161,7 +184,7 @@ def _cmd_from_hook( _entry_validate(cmd) cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local) - return (cmd[0], *RSCRIPT_OPTS, *cmd_part, *args) + return (cmd[0], *_RENV_ACTIVATED_OPTS, *cmd_part, *args) def install_environment( @@ -204,14 +227,15 @@ def install_environment( renv::install(prefix_dir) }} """ - - with _r_code_in_tempfile(r_code_inst_environment) as f: - cmd_output_b(_rscript_exec(), '--vanilla', f, cwd=env_dir) + _execute_vanilla_r( + r_code_inst_environment, + prefix=prefix, version=version, cwd=env_dir, + ) _write_current_r_version(envdir=env_dir, prefix=prefix, version=version) if additional_dependencies: r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))' - _execute_vanilla_r_code_as_script( + _execute_r_in_renv( code=r_code_inst_add, prefix=prefix, version=version, args=additional_dependencies, cwd=env_dir, diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py index 10919e4a7..9e73129e1 100644 --- a/tests/languages/r_test.py +++ b/tests/languages/r_test.py @@ -286,7 +286,7 @@ def test_health_check_without_version(prefix, installed_environment, version): prefix, env_dir = installed_environment # simulate old pre-commit install by unsetting the installed version - r._execute_vanilla_r_code_as_script( + r._execute_r_in_renv( f'renv::settings$r.version({version})', prefix=prefix, version=C.DEFAULT, cwd=env_dir, ) From b152e922ef11a97efe22ca7dc4f90011f0d1711c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 20 Jan 2025 13:35:33 -0500 Subject: [PATCH 292/342] v4.1.0 --- CHANGELOG.md | 18 ++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b4c8c22..408afe68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +4.1.0 - 2025-01-20 +================== + +### Features +- Add `language: julia`. + - #3348 PR by @fredrikekre. + - #2689 issue @jmuchovej. + +### Fixes +- Disable automatic toolchain switching for `language: golang`. + - #3304 PR by @AleksaC. + - #3300 issue by @AleksaC. + - #3149 issue by @nijel. +- Fix `language: r` installation when initiated by RStudio. + - #3389 PR by @lorenzwalthert. + - #3385 issue by @lorenzwalthert. + + 4.0.1 - 2024-10-08 ================== diff --git a/setup.cfg b/setup.cfg index 6936a1f0d..60d976418 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.0.1 +version = 4.1.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From edd0002e4312dc62fc8a236a3b4dc08d1012555d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:30:07 +0000 Subject: [PATCH 293/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/hhatto/autopep8: v2.3.1 → v2.3.2](https://github.com/hhatto/autopep8/compare/v2.3.1...v2.3.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a23da2bc..b73622927 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 - rev: v2.3.1 + rev: v2.3.2 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 From e2210c97e2128703e41cc19e66f24c23b9157f69 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 30 Jan 2025 14:58:50 -0500 Subject: [PATCH 294/342] upgrade asottile/workflows Committed via https://github.com/asottile/all-repos --- .github/workflows/languages.yaml | 2 +- .github/workflows/main.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index 61293a0d8..fccf29892 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -36,7 +36,7 @@ jobs: matrix: include: ${{ fromJSON(needs.vars.outputs.languages) }} steps: - - uses: asottile/workflows/.github/actions/fast-checkout@v1.4.0 + - uses: asottile/workflows/.github/actions/fast-checkout@v1.8.1 - uses: actions/setup-python@v4 with: python-version: 3.9 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2355b6620..7fda646ff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,12 +12,12 @@ concurrency: jobs: main-windows: - uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 with: env: '["py39"]' os: windows-latest main-linux: - uses: asottile/workflows/.github/workflows/tox.yml@v1.6.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 with: env: '["py39", "py310", "py311", "py312"]' os: ubuntu-latest From 4f90a1e88a80dd460f36e21d774d06bf0e73921b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:44:01 +0000 Subject: [PATCH 295/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.14.1 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.1...v1.15.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b73622927..ead07d89e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.1 + rev: v1.15.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 94b97e28f7cc7d9bcb536d7a3cf7ef6311e076fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:07:26 +0000 Subject: [PATCH 296/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 7.1.1 → 7.1.2](https://github.com/PyCQA/flake8/compare/7.1.1...7.1.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ead07d89e..b216fbd69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 + rev: 7.1.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From b7eb412c798424a94ca83c72eed6f97271545dc4 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 13 Mar 2025 17:29:20 +0530 Subject: [PATCH 297/342] fix: crash on ambiguous ref 'HEAD' --- pre_commit/git.py | 2 +- tests/git_test.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pre_commit/git.py b/pre_commit/git.py index 19aac3872..2f424f89e 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -126,7 +126,7 @@ def get_conflicted_files() -> set[str]: merge_diff_filenames = zsplit( cmd_output( 'git', 'diff', '--name-only', '--no-ext-diff', '-z', - '-m', tree_hash, 'HEAD', 'MERGE_HEAD', + '-m', tree_hash, 'HEAD', 'MERGE_HEAD', '--', )[1], ) return set(merge_conflict_filenames) | set(merge_diff_filenames) diff --git a/tests/git_test.py b/tests/git_test.py index 93f5a1c6e..02b6ce3ae 100644 --- a/tests/git_test.py +++ b/tests/git_test.py @@ -141,6 +141,15 @@ def test_get_conflicted_files_unstaged_files(in_merge_conflict): assert ret == {'conflict_file'} +def test_get_conflicted_files_with_file_named_head(in_merge_conflict): + resolve_conflict() + open('HEAD', 'w').close() + cmd_output('git', 'add', 'HEAD') + + ret = set(git.get_conflicted_files()) + assert ret == {'conflict_file', 'HEAD'} + + MERGE_MSG = b"Merge branch 'foo' into bar\n\nConflicts:\n\tconflict_file\n" OTHER_MERGE_MSG = MERGE_MSG + b'\tother_conflict_file\n' From 3e8d0f5e1c449381272b80241140e985631f9912 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 18 Mar 2025 14:55:24 -0400 Subject: [PATCH 298/342] adjust python default_language_version to prefer versioned exe --- pre_commit/languages/python.py | 38 +++++++++++++------ tests/languages/python_test.py | 67 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 0c4bb62db..88ececce6 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -75,6 +75,13 @@ def _find_by_py_launcher( return None +def _impl_exe_name() -> str: + if sys.implementation.name == 'cpython': # pragma: cpython cover + return 'python' + else: # pragma: cpython no cover + return sys.implementation.name # pypy mostly + + def _find_by_sys_executable() -> str | None: def _norm(path: str) -> str | None: _, exe = os.path.split(path.lower()) @@ -100,18 +107,25 @@ def _norm(path: str) -> str | None: @functools.lru_cache(maxsize=1) def get_default_version() -> str: # pragma: no cover (platform dependent) - # First attempt from `sys.executable` (or the realpath) - exe = _find_by_sys_executable() - if exe: - return exe - - # Next try the `pythonX.X` executable - exe = f'python{sys.version_info[0]}.{sys.version_info[1]}' - if find_executable(exe): - return exe - - if _find_by_py_launcher(exe): - return exe + v_major = f'{sys.version_info[0]}' + v_minor = f'{sys.version_info[0]}.{sys.version_info[1]}' + + # attempt the likely implementation exe + for potential in (v_minor, v_major): + exe = f'{_impl_exe_name()}{potential}' + if find_executable(exe): + return exe + + # next try `sys.executable` (or the realpath) + maybe_exe = _find_by_sys_executable() + if maybe_exe: + return maybe_exe + + # maybe on windows we can find it via py launcher? + if sys.platform == 'win32': # pragma: win32 cover + exe = f'python{v_minor}' + if _find_by_py_launcher(exe): + return exe # We tried! return C.DEFAULT diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index ab26e14e7..565525a40 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -12,6 +12,7 @@ from pre_commit.prefix import Prefix from pre_commit.util import make_executable from pre_commit.util import win_exe +from testing.auto_namedtuple import auto_namedtuple from testing.language_helpers import run_language @@ -34,6 +35,72 @@ def test_read_pyvenv_cfg_non_utf8(tmpdir): assert python._read_pyvenv_cfg(pyvenv_cfg) == expected +def _get_default_version( + *, + impl: str, + exe: str, + found: set[str], + version: tuple[int, int], +) -> str: + sys_exe = f'/fake/path/{exe}' + sys_impl = auto_namedtuple(name=impl) + sys_ver = auto_namedtuple(major=version[0], minor=version[1]) + + def find_exe(s): + if s in found: + return f'/fake/path/found/{exe}' + else: + return None + + with ( + mock.patch.object(sys, 'implementation', sys_impl), + mock.patch.object(sys, 'executable', sys_exe), + mock.patch.object(sys, 'version_info', sys_ver), + mock.patch.object(python, 'find_executable', find_exe), + ): + return python.get_default_version.__wrapped__() + + +def test_default_version_sys_executable_found(): + ret = _get_default_version( + impl='cpython', + exe='python3.12', + found={'python3.12'}, + version=(3, 12), + ) + assert ret == 'python3.12' + + +def test_default_version_picks_specific_when_found(): + ret = _get_default_version( + impl='cpython', + exe='python3', + found={'python3', 'python3.12'}, + version=(3, 12), + ) + assert ret == 'python3.12' + + +def test_default_version_picks_pypy_versioned_exe(): + ret = _get_default_version( + impl='pypy', + exe='python', + found={'pypy3.12', 'python3'}, + version=(3, 12), + ) + assert ret == 'pypy3.12' + + +def test_default_version_picks_pypy_unversioned_exe(): + ret = _get_default_version( + impl='pypy', + exe='python', + found={'pypy3', 'python3'}, + version=(3, 12), + ) + assert ret == 'pypy3' + + def test_norm_version_expanduser(): home = os.path.expanduser('~') if sys.platform == 'win32': # pragma: win32 cover From aa48766b888990e7b118d12cf757109d96e65a7e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 18 Mar 2025 17:34:49 -0400 Subject: [PATCH 299/342] v4.2.0 --- CHANGELOG.md | 13 +++++++++++++ setup.cfg | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 408afe68b..b63f44317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +4.2.0 - 2025-03-18 +================== + +### Features +- For `language: python` first attempt a versioned python executable for + the default language version before consulting a potentially unversioned + `sys.executable`. + - #3430 PR by @asottile. + +### Fixes +- Handle error during conflict detection when a file is named "HEAD" + - #3425 PR by @tusharsadhwani. + 4.1.0 - 2025-01-20 ================== diff --git a/setup.cfg b/setup.cfg index 60d976418..af34452df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.1.0 +version = 4.2.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 6d47b8d52bd53320807886edd82b6fb4e1c67089 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:43:51 +0000 Subject: [PATCH 300/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.7.0 → v2.8.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.7.0...v2.8.0) - [github.com/PyCQA/flake8: 7.1.2 → 7.2.0](https://github.com/PyCQA/flake8/compare/7.1.2...7.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b216fbd69..a19b44bc5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.7.0 + rev: v2.8.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports @@ -33,7 +33,7 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 7.1.2 + rev: 7.2.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy From 43592c2a29c587aab7f70046a02ef95893841e67 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 19:44:12 +0000 Subject: [PATCH 301/342] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index af34452df..90f49df9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,6 @@ author_email = asottile@umich.edu license = MIT license_files = LICENSE classifiers = - License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython From 466f6c4a3939375dc2dc7a2fc34f553c2715d738 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Sun, 13 Apr 2025 11:18:18 +0100 Subject: [PATCH 302/342] Fix permission errors for mounts in rootless docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By running containers in a rootless docker context as root. This is because user and group IDs are remapped in the user namespaces uses by rootless docker, and it's unlikely that the current user ID will map to the same ID under this remap (see docs[1] for some more details). Specifically, it means ownership of mounted volumes will not be for the current user and trying to write can result in permission errors. This change borrows heavily from an existing PR[2]. The output format of `docker system info` I don't think is documented/guaranteed anywhere, but it should corresponding to the format of a `/info` API request to Docker[3] The added test _hopes_ to avoid regressions in this behaviour, but since tests aren't run in a rootless docker context on the PR checks (and I couldn't find an easy way to make it the case) there's still a risk of regressions sneaking in. Link: https://docs.docker.com/engine/security/rootless/ [1] Link: https://github.com/pre-commit/pre-commit/pull/1484/ [2] Link: https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemAuth [3] Co-authored-by: Kurt von Laven Co-authored-by: Fabrice Flore-Thébault --- pre_commit/languages/docker.py | 26 +++++++++++++++++++ tests/languages/docker_test.py | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 4de1d5824..086e874d5 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -1,5 +1,6 @@ from __future__ import annotations +import functools import hashlib import json import os @@ -101,7 +102,32 @@ def install_environment( os.mkdir(directory) +@functools.lru_cache(maxsize=1) +def _is_rootless() -> bool: # pragma: win32 no cover + retcode, out, _ = cmd_output_b( + 'docker', 'system', 'info', '--format', '{{ json . }}', + ) + if retcode != 0: + return False + + info = json.loads(out) + try: + return ( + # docker: + # https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemInfo + 'name=rootless' in info.get('SecurityOptions', ()) or + # podman: + # https://docs.podman.io/en/latest/_static/api.html?version=v5.4#tag/system/operation/SystemInfoLibpod + info['host']['security']['rootless'] + ) + except KeyError: + return False + + def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover + if _is_rootless(): + return () + try: return ('-u', f'{os.getuid()}:{os.getgid()}') except AttributeError: diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py index 836382a8a..03235c46b 100644 --- a/tests/languages/docker_test.py +++ b/tests/languages/docker_test.py @@ -62,6 +62,42 @@ def invalid_attribute(): assert docker.get_docker_user() == () +@pytest.fixture(autouse=True) +def _avoid_cache(): + with mock.patch.object( + docker, + '_is_rootless', + docker._is_rootless.__wrapped__, + ): + yield + + +@pytest.mark.parametrize( + 'info_ret', + ( + (0, b'{"SecurityOptions": ["name=rootless","name=cgroupns"]}', b''), + (0, b'{"host": {"security": {"rootless": true}}}', b''), + ), +) +def test_docker_user_rootless(info_ret): + with mock.patch.object(docker, 'cmd_output_b', return_value=info_ret): + assert docker.get_docker_user() == () + + +@pytest.mark.parametrize( + 'info_ret', + ( + (0, b'{"SecurityOptions": ["name=cgroupns"]}', b''), + (0, b'{"host": {"security": {"rootless": false}}}', b''), + (0, b'{"respone_from_some_other_container_engine": true}', b''), + (1, b'', b''), + ), +) +def test_docker_user_non_rootless(info_ret): + with mock.patch.object(docker, 'cmd_output_b', return_value=info_ret): + assert docker.get_docker_user() != () + + def test_in_docker_no_file(): with mock.patch.object(builtins, 'open', side_effect=FileNotFoundError): assert docker._is_in_docker() is False @@ -195,3 +231,14 @@ def test_docker_hook(tmp_path): ret = run_language(tmp_path, docker, 'echo hello hello world') assert ret == (0, b'hello hello world\n') + + +@xfailif_windows # pragma: win32 no cover +def test_docker_hook_mount_permissions(tmp_path): + dockerfile = '''\ +FROM ubuntu:22.04 +''' + tmp_path.joinpath('Dockerfile').write_text(dockerfile) + + retcode, _ = run_language(tmp_path, docker, 'touch', ('README.md',)) + assert retcode == 0 From 43b426a501e621cc1c837f07b5633cb12525e79b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 19:45:48 +0000 Subject: [PATCH 303/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/reorder-python-imports: v3.14.0 → v3.15.0](https://github.com/asottile/reorder-python-imports/compare/v3.14.0...v3.15.0) - [github.com/asottile/add-trailing-comma: v3.1.0 → v3.2.0](https://github.com/asottile/add-trailing-comma/compare/v3.1.0...v3.2.0) - [github.com/asottile/pyupgrade: v3.19.1 → v3.20.0](https://github.com/asottile/pyupgrade/compare/v3.19.1...v3.20.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a19b44bc5..97d1174d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,17 +14,17 @@ repos: hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.14.0 + rev: v3.15.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py39-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.1.0 + rev: v3.2.0 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + rev: v3.20.0 hooks: - id: pyupgrade args: [--py39-plus] From d4f0c6e8a7db7c29177f16fe3e56ab5c9aad7d73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 19:57:10 +0000 Subject: [PATCH 304/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.16.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.16.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97d1174d0..4ddf34061 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: v1.16.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From d1d5b3d5648d213f8dcb1648eae77b411a27ac05 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:55:22 +0000 Subject: [PATCH 305/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 7.2.0 → 7.3.0](https://github.com/PyCQA/flake8/compare/7.2.0...7.3.0) - [github.com/pre-commit/mirrors-mypy: v1.16.0 → v1.16.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.16.0...v1.16.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ddf34061..2dc7f4c11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,11 +33,11 @@ repos: hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 7.2.0 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.16.0 + rev: v1.16.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 4fd4537bc69e6804998d99e4851a9dbe43e91757 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 20:02:20 +0000 Subject: [PATCH 306/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.16.1 → v1.17.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.16.1...v1.17.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2dc7f4c11..3ef94b45c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.16.1 + rev: v1.17.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 6f1f433a9cea94a70828ade95931a703c9a9c82b Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:05:54 +0200 Subject: [PATCH 307/342] Julia language: skip startup.jl file --- pre_commit/languages/julia.py | 5 +++-- tests/languages/julia_test.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pre_commit/languages/julia.py b/pre_commit/languages/julia.py index df91c0697..7559b5ba6 100644 --- a/pre_commit/languages/julia.py +++ b/pre_commit/languages/julia.py @@ -37,7 +37,7 @@ def run_hook( cmd = lang_base.hook_cmd(entry, args) script = cmd[0] if is_local else prefix.path(cmd[0]) - cmd = ('julia', script, *cmd[1:]) + cmd = ('julia', '--startup-file=no', script, *cmd[1:]) return lang_base.run_xargs( cmd, file_args, @@ -127,6 +127,7 @@ def install_environment( end """ cmd_output_b( - 'julia', '-e', julia_code, '--', envdir, *additional_dependencies, + 'julia', '--startup-file=no', '-e', julia_code, '--', envdir, + *additional_dependencies, cwd=prefix.prefix_dir, ) diff --git a/tests/languages/julia_test.py b/tests/languages/julia_test.py index 4ea3c25b2..175622d65 100644 --- a/tests/languages/julia_test.py +++ b/tests/languages/julia_test.py @@ -1,5 +1,8 @@ from __future__ import annotations +import os +from unittest import mock + from pre_commit.languages import julia from testing.language_helpers import run_language from testing.util import cwd @@ -28,6 +31,17 @@ def test_julia_hook(tmp_path): assert run_language(tmp_path, julia, 'src/main.jl') == expected +def test_julia_hook_with_startup(tmp_path): + depot_path = tmp_path.joinpath('depot') + depot_path.joinpath('config').mkdir(parents=True) + startup = depot_path.joinpath('config', 'startup.jl') + startup.write_text('error("Startup file used!")\n') + + depo_path_var = f'{depot_path}{os.pathsep}' + with mock.patch.dict(os.environ, {'JULIA_DEPOT_PATH': depo_path_var}): + test_julia_hook(tmp_path) + + def test_julia_hook_manifest(tmp_path): code = """ using Example From c8925a457afb1d6850c8f105671846bae408aae0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:31:31 +0000 Subject: [PATCH 308/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.17.0 → v1.17.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.0...v1.17.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3ef94b45c..da8e24ad9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.17.0 + rev: v1.17.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] From f1cc7a445f1adbfc9ea4072e180fbe3054af669b Mon Sep 17 00:00:00 2001 From: Byoungchan Lee Date: Fri, 8 Aug 2025 17:02:53 +0900 Subject: [PATCH 309/342] Make Dart pre-commit hook compatible with the latest Dart SDKs Dart introduced sound null safety in version 2.12.0, and as of Dart 3, null safety is mandatory. Older Dart SDKs allowed both pre-null safety and null-safe packages, but modern Dart SDKs, where most source code is null-safe, now reject pre-null safety packages. The current `pubspec.yaml` template with `sdk: '>=2.10.0'` is incompatible with recent Dart SDKs, leading to the following error: An unexpected error has occurred: CalledProcessError: command: ('/path/to/dart-sdk/bin/dart', 'pub', 'get') return code: 65 stdout: Resolving dependencies... stderr: The lower bound of "sdk: '>=2.10.0'" must be 2.12.0' or higher to enable null safety. The current Dart SDK (3.8.3) only supports null safety. For details, see https://dart.dev/null-safety To ensure compatibility with the modern Dart ecosystem, this commit updates the minimum Dart SDK version to 2.12.0 or higher, which implicitly supports null safety. Additionally, `testing/get-dart.sh` has been updated to verify that the pre-commit hook functions correctly with the latest Dart versions. --- pre_commit/resources/empty_template_pubspec.yaml | 2 +- testing/get-dart.sh | 2 +- tests/languages/dart_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pre_commit/resources/empty_template_pubspec.yaml b/pre_commit/resources/empty_template_pubspec.yaml index 3be6ffe36..8306aeb64 100644 --- a/pre_commit/resources/empty_template_pubspec.yaml +++ b/pre_commit/resources/empty_template_pubspec.yaml @@ -1,4 +1,4 @@ name: pre_commit_empty_pubspec environment: - sdk: '>=2.10.0' + sdk: '>=2.12.0' executables: {} diff --git a/testing/get-dart.sh b/testing/get-dart.sh index 998b9d98f..3402c4212 100755 --- a/testing/get-dart.sh +++ b/testing/get-dart.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -VERSION=2.13.4 +VERSION=3.8.3 if [ "$OSTYPE" = msys ]; then URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip" diff --git a/tests/languages/dart_test.py b/tests/languages/dart_test.py index 5bb5aa68f..213d888eb 100644 --- a/tests/languages/dart_test.py +++ b/tests/languages/dart_test.py @@ -10,7 +10,7 @@ def test_dart(tmp_path): pubspec_yaml = '''\ environment: - sdk: '>=2.10.0 <3.0.0' + sdk: '>=2.12.0 <4.0.0' name: hello_world_dart From 2a0bcea7570620416a550362d9b2d2b24eb80dd8 Mon Sep 17 00:00:00 2001 From: Byoungchan Lee Date: Fri, 8 Aug 2025 17:40:30 +0900 Subject: [PATCH 310/342] Downgrade Dart SDK version installed in the CI --- testing/get-dart.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/get-dart.sh b/testing/get-dart.sh index 3402c4212..b4545e71e 100755 --- a/testing/get-dart.sh +++ b/testing/get-dart.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -VERSION=3.8.3 +VERSION=2.19.6 if [ "$OSTYPE" = msys ]; then URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip" From b74a22d96cca546b8e0bb9f68f1d7d8565205b65 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 9 Aug 2025 14:54:49 -0400 Subject: [PATCH 311/342] v4.3.0 --- CHANGELOG.md | 13 +++++++++++++ setup.cfg | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b63f44317..42a63f781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +4.3.0 - 2025-08-09 +================== + +### Features +- `language: docker` / `language: docker_image`: detect rootless docker. + - #3446 PR by @matthewhughes934. + - #1243 issue by @dkolepp. +- `language: julia`: avoid `startup.jl` when executing hooks. + - #3496 PR by @ericphanson. +- `language: dart`: support latest dart versions which require a higher sdk + lower bound. + - #3507 PR by @bc-lee. + 4.2.0 - 2025-03-18 ================== diff --git a/setup.cfg b/setup.cfg index 90f49df9d..9b0e02ad4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.2.0 +version = 4.3.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 87a681f8662554ee888a02e162d8772d64eee6cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:46:13 +0000 Subject: [PATCH 312/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da8e24ad9..464cfe600 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer From c78f248c60846fa48c9d38b488f3acc0fed96207 Mon Sep 17 00:00:00 2001 From: JulianMaurin Date: Mon, 25 Aug 2025 23:20:07 +0200 Subject: [PATCH 313/342] Add fail-fast argument for run command --- pre_commit/commands/hook_impl.py | 1 + pre_commit/commands/run.py | 3 ++- pre_commit/main.py | 4 ++++ testing/util.py | 2 ++ tests/commands/run_test.py | 13 +++++++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 49a80b7b3..de5c8f346 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -106,6 +106,7 @@ def _ns( hook=None, verbose=False, show_diff_on_failure=False, + fail_fast=False, ) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 793adbdb2..8ab505ffb 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -298,7 +298,8 @@ def _run_hooks( verbose=args.verbose, use_color=args.color, ) retval |= current_retval - if current_retval and (config['fail_fast'] or hook.fail_fast): + fail_fast = (config['fail_fast'] or hook.fail_fast or args.fail_fast) + if current_retval and fail_fast: break if retval and args.show_diff_on_failure and prior_diff: if args.all_files: diff --git a/pre_commit/main.py b/pre_commit/main.py index 559c927c9..fc4531b82 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -76,6 +76,10 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: '--show-diff-on-failure', action='store_true', help='When hooks fail, run `git diff` directly afterward.', ) + parser.add_argument( + '--fail-fast', action='store_true', + help='Stop after the first failing hook.', + ) parser.add_argument( '--hook-stage', choices=clientlib.STAGES, diff --git a/testing/util.py b/testing/util.py index 08d52cbc3..1646ccd2a 100644 --- a/testing/util.py +++ b/testing/util.py @@ -40,6 +40,7 @@ def run_opts( color=False, verbose=False, hook=None, + fail_fast=False, remote_branch='', local_branch='', from_ref='', @@ -65,6 +66,7 @@ def run_opts( color=color, verbose=verbose, hook=hook, + fail_fast=fail_fast, remote_branch=remote_branch, local_branch=local_branch, from_ref=from_ref, diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 50a20f377..e4af1e162 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -1104,6 +1104,19 @@ def test_fail_fast_not_prev_failures(cap_out, store, repo_with_failing_hook): assert printed.count(b'run me!') == 1 +def test_fail_fast_run_arg(cap_out, store, repo_with_failing_hook): + with modify_config() as config: + # More than one hook to demonstrate early exit + config['repos'][0]['hooks'] *= 2 + stage_a_file() + + ret, printed = _do_run( + cap_out, store, repo_with_failing_hook, run_opts(fail_fast=True), + ) + # it should have only run one hook due to the CLI flag + assert printed.count(b'Failing hook') == 1 + + def test_classifier_removes_dne(): classifier = Classifier(('this_file_does_not_exist',)) assert classifier.filenames == [] From e67183040220cd8662b5b886b24841e2d04bac9c Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 6 Sep 2025 14:20:01 -0400 Subject: [PATCH 314/342] store_true does not need default=... --- pre_commit/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pre_commit/main.py b/pre_commit/main.py index 559c927c9..b7ac3dc26 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -62,10 +62,10 @@ def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: def _add_run_options(parser: argparse.ArgumentParser) -> None: parser.add_argument('hook', nargs='?', help='A single hook-id to run') - parser.add_argument('--verbose', '-v', action='store_true', default=False) + parser.add_argument('--verbose', '-v', action='store_true') mutex_group = parser.add_mutually_exclusive_group(required=False) mutex_group.add_argument( - '--all-files', '-a', action='store_true', default=False, + '--all-files', '-a', action='store_true', help='Run on all the files in the repo.', ) mutex_group.add_argument( @@ -275,7 +275,7 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: ) _add_hook_type_option(install_parser) install_parser.add_argument( - '--allow-missing-config', action='store_true', default=False, + '--allow-missing-config', action='store_true', help=( 'Whether to allow a missing `pre-commit` configuration file ' 'or exit with a failure code.' From 2930ea0fcd481f4c2cbeae0245a8bb748bae905a Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 6 Sep 2025 14:40:20 -0400 Subject: [PATCH 315/342] handle `SecurityOptions: null` in docker response --- pre_commit/languages/docker.py | 2 +- tests/languages/docker_test.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 086e874d5..d5ce1eb70 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -115,7 +115,7 @@ def _is_rootless() -> bool: # pragma: win32 no cover return ( # docker: # https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemInfo - 'name=rootless' in info.get('SecurityOptions', ()) or + 'name=rootless' in (info.get('SecurityOptions') or ()) or # podman: # https://docs.podman.io/en/latest/_static/api.html?version=v5.4#tag/system/operation/SystemInfoLibpod info['host']['security']['rootless'] diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py index 03235c46b..b830439a2 100644 --- a/tests/languages/docker_test.py +++ b/tests/languages/docker_test.py @@ -89,7 +89,8 @@ def test_docker_user_rootless(info_ret): ( (0, b'{"SecurityOptions": ["name=cgroupns"]}', b''), (0, b'{"host": {"security": {"rootless": false}}}', b''), - (0, b'{"respone_from_some_other_container_engine": true}', b''), + (0, b'{"response_from_some_other_container_engine": true}', b''), + (0, b'{"SecurityOptions": null}', b''), (1, b'', b''), ), ) From ad0d4cd4271cab68ddbf5e5c3386f38320e0ccd2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:44:04 +0000 Subject: [PATCH 316/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.17.1 → v1.18.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.1...v1.18.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 464cfe600..0a24427f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.17.1 + rev: v1.18.2 hooks: - id: mypy additional_dependencies: [types-pyyaml] From f415f6c4d72224363ba794429b25cc3f52e04933 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Thu, 9 Oct 2025 17:44:05 -0400 Subject: [PATCH 317/342] py310+ Committed via https://github.com/asottile/all-repos --- .github/workflows/languages.yaml | 4 ++-- .github/workflows/main.yml | 4 ++-- .pre-commit-config.yaml | 4 ++-- pre_commit/commands/migrate_config.py | 2 +- pre_commit/file_lock.py | 2 +- pre_commit/languages/golang.py | 3 +-- pre_commit/store.py | 2 +- pre_commit/util.py | 2 +- pre_commit/xargs.py | 2 +- setup.cfg | 2 +- 10 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/languages.yaml b/.github/workflows/languages.yaml index fccf29892..be8963bac 100644 --- a/.github/workflows/languages.yaml +++ b/.github/workflows/languages.yaml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: install deps run: python -mpip install -e . -r requirements-dev.txt - name: vars @@ -39,7 +39,7 @@ jobs: - uses: asottile/workflows/.github/actions/fast-checkout@v1.8.1 - uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - run: echo "$CONDA\Scripts" >> "$GITHUB_PATH" shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7fda646ff..02b11ae28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,10 +14,10 @@ jobs: main-windows: uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 with: - env: '["py39"]' + env: '["py310"]' os: windows-latest main-linux: uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 with: - env: '["py39", "py310", "py311", "py312"]' + env: '["py310", "py311", "py312", "py313"]' os: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a24427f9..58b96f76f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) - args: [--py39-plus, --add-import, 'from __future__ import annotations'] + args: [--py310-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma rev: v3.2.0 hooks: @@ -27,7 +27,7 @@ repos: rev: v3.20.0 hooks: - id: pyupgrade - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/hhatto/autopep8 rev: v2.3.2 hooks: diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index c5d47a08e..b04c53a5e 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -3,7 +3,7 @@ import functools import itertools import textwrap -from typing import Callable +from collections.abc import Callable import cfgv import yaml diff --git a/pre_commit/file_lock.py b/pre_commit/file_lock.py index c840ad8b5..6223f869e 100644 --- a/pre_commit/file_lock.py +++ b/pre_commit/file_lock.py @@ -3,8 +3,8 @@ import contextlib import errno import sys +from collections.abc import Callable from collections.abc import Generator -from typing import Callable if sys.platform == 'win32': # pragma: no cover (windows) diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py index 678c04b14..bedbd114b 100644 --- a/pre_commit/languages/golang.py +++ b/pre_commit/languages/golang.py @@ -90,8 +90,7 @@ def _infer_go_version(version: str) -> str: if version != C.DEFAULT: return version resp = urllib.request.urlopen('https://go.dev/dl/?mode=json') - # TODO: 3.9+ .removeprefix('go') - return json.load(resp)[0]['version'][2:] + return json.load(resp)[0]['version'].removeprefix('go') def _get_url(version: str) -> str: diff --git a/pre_commit/store.py b/pre_commit/store.py index 1235942c5..9e3b40485 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -5,9 +5,9 @@ import os.path import sqlite3 import tempfile +from collections.abc import Callable from collections.abc import Generator from collections.abc import Sequence -from typing import Callable import pre_commit.constants as C from pre_commit import clientlib diff --git a/pre_commit/util.py b/pre_commit/util.py index e199d0807..19b1880bf 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -8,10 +8,10 @@ import stat import subprocess import sys +from collections.abc import Callable from collections.abc import Generator from types import TracebackType from typing import Any -from typing import Callable from pre_commit import parse_shebang diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index a1345b583..7c98d1674 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -7,12 +7,12 @@ import os import subprocess import sys +from collections.abc import Callable from collections.abc import Generator from collections.abc import Iterable from collections.abc import MutableMapping from collections.abc import Sequence from typing import Any -from typing import Callable from typing import TypeVar from pre_commit import parse_shebang diff --git a/setup.cfg b/setup.cfg index 9b0e02ad4..8fb6e6aab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,7 @@ install_requires = nodeenv>=0.11.1 pyyaml>=5.1 virtualenv>=20.10.0 -python_requires = >=3.9 +python_requires = >=3.10 [options.packages.find] exclude = From 221637b0cbdfbfe8ca209ba5df0111b08f9d8cda Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:38:45 +0000 Subject: [PATCH 318/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v2.8.0 → v3.1.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.8.0...v3.1.0) - [github.com/asottile/reorder-python-imports: v3.15.0 → v3.16.0](https://github.com/asottile/reorder-python-imports/compare/v3.15.0...v3.16.0) - [github.com/asottile/add-trailing-comma: v3.2.0 → v4.0.0](https://github.com/asottile/add-trailing-comma/compare/v3.2.0...v4.0.0) - [github.com/asottile/pyupgrade: v3.20.0 → v3.21.0](https://github.com/asottile/pyupgrade/compare/v3.20.0...v3.21.0) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58b96f76f..b1623a640 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,21 +10,21 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.8.0 + rev: v3.1.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.15.0 + rev: v3.16.0 hooks: - id: reorder-python-imports exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) args: [--py310-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.2.0 + rev: v4.0.0 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.20.0 + rev: v3.21.0 hooks: - id: pyupgrade args: [--py310-plus] From ddfcf4034bc72445497b5e6708205523a9da7ed7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 16 Oct 2025 10:23:27 -0400 Subject: [PATCH 319/342] fix deprecated call --- pre_commit/git.py | 2 +- setup.cfg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pre_commit/git.py b/pre_commit/git.py index 2f424f89e..ec1928f37 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -219,7 +219,7 @@ def check_for_cygwin_mismatch() -> None: if is_cygwin_python ^ is_cygwin_git: exe_type = {True: '(cygwin)', False: '(windows)'} - logger.warn( + logger.warning( f'pre-commit has detected a mix of cygwin python / git\n' f'This combination is not supported, it is likely you will ' f'receive an error later in the program.\n' diff --git a/setup.cfg b/setup.cfg index 8fb6e6aab..17c3fe0eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,6 +52,7 @@ check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true +enable_error_code = deprecated warn_redundant_casts = true warn_unused_ignores = true From fc33a62f3c55c671cdef8306b6c3dc91d81e2b4a Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 24 Oct 2025 15:18:07 -0400 Subject: [PATCH 320/342] upgrade rbenv / ruby-build --- pre_commit/resources/rbenv.tar.gz | Bin 32545 -> 31297 bytes pre_commit/resources/ruby-build.tar.gz | Bin 88488 -> 93998 bytes testing/make-archives | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pre_commit/resources/rbenv.tar.gz b/pre_commit/resources/rbenv.tar.gz index 111546e3dd9796511942c278495c21fe1ef947ae..b5df08744c17c6fd950d89e8a5caf6186905dedc 100644 GIT binary patch literal 31297 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 B1~UKv diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz index a4f7eb24fd0a7e4d59cf120014d8b054555b8b0f..5c82c90609384836116acd7df8bbe9f79a4c7a86 100644 GIT binary patch literal 93998 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 literal 88488 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 Date: Sat, 8 Nov 2025 13:16:33 -0500 Subject: [PATCH 321/342] remove redundant system spaces test `test_args_with_spaces_and_quotes` also covers this behaviour --- .../system_hook_with_spaces_repo/.pre-commit-hooks.yaml | 5 ----- tests/repository_test.py | 7 ------- 2 files changed, 12 deletions(-) delete mode 100644 testing/resources/system_hook_with_spaces_repo/.pre-commit-hooks.yaml diff --git a/testing/resources/system_hook_with_spaces_repo/.pre-commit-hooks.yaml b/testing/resources/system_hook_with_spaces_repo/.pre-commit-hooks.yaml deleted file mode 100644 index b2c347c14..000000000 --- a/testing/resources/system_hook_with_spaces_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- id: system-hook-with-spaces - name: System hook with spaces - entry: bash -c 'echo "Hello World"' - language: system - files: \.sh$ diff --git a/tests/repository_test.py b/tests/repository_test.py index b54c910d3..f1559301f 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -80,13 +80,6 @@ def _test_hook_repo( assert out == expected -def test_system_hook_with_spaces(tempdir_factory, store): - _test_hook_repo( - tempdir_factory, store, 'system_hook_with_spaces_repo', - 'system-hook-with-spaces', [os.devnull], b'Hello World\n', - ) - - def test_missing_executable(tempdir_factory, store): _test_hook_repo( tempdir_factory, store, 'not_found_exe', From 95eec7500464500d2ca0cc13d0986000508830e5 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 8 Nov 2025 13:33:50 -0500 Subject: [PATCH 322/342] rm python3_hooks_repo --- .pre-commit-config.yaml | 2 +- .../resources/python3_hooks_repo/.pre-commit-hooks.yaml | 6 ------ testing/resources/python3_hooks_repo/py3_hook.py | 8 -------- testing/resources/python3_hooks_repo/setup.py | 8 -------- 4 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml delete mode 100644 testing/resources/python3_hooks_repo/py3_hook.py delete mode 100644 testing/resources/python3_hooks_repo/setup.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1623a640..fa0773656 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: rev: v3.16.0 hooks: - id: reorder-python-imports - exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) + exclude: ^pre_commit/resources/ args: [--py310-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma rev: v4.0.0 diff --git a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml deleted file mode 100644 index 2c2370092..000000000 --- a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml +++ /dev/null @@ -1,6 +0,0 @@ -- id: python3-hook - name: Python 3 Hook - entry: python3-hook - language: python - language_version: python3 - files: \.py$ diff --git a/testing/resources/python3_hooks_repo/py3_hook.py b/testing/resources/python3_hooks_repo/py3_hook.py deleted file mode 100644 index 8c9cda4c6..000000000 --- a/testing/resources/python3_hooks_repo/py3_hook.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys - - -def main(): - print(sys.version_info[0]) - print(repr(sys.argv[1:])) - print('Hello World') - return 0 diff --git a/testing/resources/python3_hooks_repo/setup.py b/testing/resources/python3_hooks_repo/setup.py deleted file mode 100644 index 9125dc1df..000000000 --- a/testing/resources/python3_hooks_repo/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -from setuptools import setup - -setup( - name='python3_hook', - version='0.0.0', - py_modules=['py3_hook'], - entry_points={'console_scripts': ['python3-hook = py3_hook:main']}, -) From aa2961c122b4aa834c77e612232c154f9439c388 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 8 Nov 2025 14:31:11 -0500 Subject: [PATCH 323/342] fix missing context in error for stages --- pre_commit/clientlib.py | 9 +++++---- tests/clientlib_test.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index c0f736d92..51514bd36 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -116,11 +116,12 @@ def check(self, dct: dict[str, Any]) -> None: if self.key not in dct: return - val = dct[self.key] - cfgv.check_array(cfgv.check_any)(val) + with cfgv.validate_context(f'At key: {self.key}'): + val = dct[self.key] + cfgv.check_array(cfgv.check_any)(val) - val = [transform_stage(v) for v in val] - cfgv.check_array(cfgv.check_one_of(STAGES))(val) + val = [transform_stage(v) for v in val] + cfgv.check_array(cfgv.check_one_of(STAGES))(val) def apply_default(self, dct: dict[str, Any]) -> None: if self.key not in dct: diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 7aa84af0e..2251abc4e 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -309,6 +309,27 @@ def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning): assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)] +def test_invalid_stages_error(): + cfg = {'repos': [sample_local_config()]} + cfg['repos'][0]['hooks'][0]['stages'] = ['invalid'] + + with pytest.raises(cfgv.ValidationError) as excinfo: + cfgv.validate(cfg, CONFIG_SCHEMA) + + assert str(excinfo.value) == ( + '\n' + '==> At Config()\n' + '==> At key: repos\n' + "==> At Repository(repo='local')\n" + '==> At key: hooks\n' + "==> At Hook(id='do_not_commit')\n" + # this line was missing due to the custom validator + '==> At key: stages\n' + '==> At index 0\n' + "=====> Expected one of commit-msg, manual, post-checkout, post-commit, post-merge, post-rewrite, pre-commit, pre-merge-commit, pre-push, pre-rebase, prepare-commit-msg but got: 'invalid'" # noqa: E501 + ) + + def test_warning_for_deprecated_stages(caplog): config_obj = sample_local_config() config_obj['hooks'][0]['stages'] = ['commit', 'push'] From 725acc969a28a6bc9a7e2260f035426bc932e8da Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 8 Nov 2025 13:13:18 -0500 Subject: [PATCH 324/342] rename system and script languages to unsupported / unsupported_script --- pre_commit/all_languages.py | 8 ++-- pre_commit/clientlib.py | 47 +++++++++++++++++-- .../languages/{system.py => unsupported.py} | 0 .../{script.py => unsupported_script.py} | 0 tests/clientlib_test.py | 20 ++++++++ tests/languages/system_test.py | 9 ---- ...ipt_test.py => unsupported_script_test.py} | 6 +-- tests/languages/unsupported_test.py | 10 ++++ tests/repository_test.py | 14 +++--- 9 files changed, 88 insertions(+), 26 deletions(-) rename pre_commit/languages/{system.py => unsupported.py} (100%) rename pre_commit/languages/{script.py => unsupported_script.py} (100%) delete mode 100644 tests/languages/system_test.py rename tests/languages/{script_test.py => unsupported_script_test.py} (63%) create mode 100644 tests/languages/unsupported_test.py diff --git a/pre_commit/all_languages.py b/pre_commit/all_languages.py index ba569c377..166bc167f 100644 --- a/pre_commit/all_languages.py +++ b/pre_commit/all_languages.py @@ -19,9 +19,9 @@ from pre_commit.languages import r from pre_commit.languages import ruby from pre_commit.languages import rust -from pre_commit.languages import script from pre_commit.languages import swift -from pre_commit.languages import system +from pre_commit.languages import unsupported +from pre_commit.languages import unsupported_script languages: dict[str, Language] = { @@ -43,8 +43,8 @@ 'r': r, 'ruby': ruby, 'rust': rust, - 'script': script, 'swift': swift, - 'system': system, + 'unsupported': unsupported, + 'unsupported_script': unsupported_script, } language_names = sorted(languages) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 51514bd36..eb0fd4d68 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -6,6 +6,7 @@ import re import shlex import sys +from collections.abc import Callable from collections.abc import Sequence from typing import Any from typing import NamedTuple @@ -190,6 +191,42 @@ def remove_default(self, dct: dict[str, Any]) -> None: raise NotImplementedError +def _translate_language(name: str) -> str: + return { + 'system': 'unsupported', + 'script': 'unsupported_script', + }.get(name, name) + + +class LanguageMigration(NamedTuple): # remove + key: str + check_fn: Callable[[object], None] + + def check(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + return + + with cfgv.validate_context(f'At key: {self.key}'): + self.check_fn(_translate_language(dct[self.key])) + + def apply_default(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + return + + dct[self.key] = _translate_language(dct[self.key]) + + def remove_default(self, dct: dict[str, Any]) -> None: + raise NotImplementedError + + +class LanguageMigrationRequired(LanguageMigration): # replace with Required + def check(self, dct: dict[str, Any]) -> None: + if self.key not in dct: + raise cfgv.ValidationError(f'Missing required key: {self.key}') + + super().check(dct) + + MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -203,7 +240,7 @@ def remove_default(self, dct: dict[str, Any]) -> None: cfgv.Required('id', cfgv.check_string), cfgv.Required('name', cfgv.check_string), cfgv.Required('entry', cfgv.check_string), - cfgv.Required('language', cfgv.check_one_of(language_names)), + LanguageMigrationRequired('language', cfgv.check_one_of(language_names)), cfgv.Optional('alias', cfgv.check_string, ''), cfgv.Optional('files', check_string_regex, ''), @@ -368,8 +405,10 @@ def check(self, dct: dict[str, Any]) -> None: 'Hook', 'id', cfgv.Required('id', cfgv.check_string), cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))), - # language must be system - cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'), + # language must be `unsupported` + cfgv.Optional( + 'language', cfgv.check_one_of({'unsupported'}), 'unsupported', + ), # entry cannot be overridden NotAllowed('entry', cfgv.check_any), *( @@ -402,8 +441,10 @@ def check(self, dct: dict[str, Any]) -> None: for item in MANIFEST_HOOK_DICT.items if item.key != 'id' if item.key != 'stages' + if item.key != 'language' # remove ), StagesMigrationNoDefault('stages', []), + LanguageMigration('language', cfgv.check_one_of(language_names)), # remove *_COMMON_HOOK_WARNINGS, ) LOCAL_HOOK_DICT = cfgv.Map( diff --git a/pre_commit/languages/system.py b/pre_commit/languages/unsupported.py similarity index 100% rename from pre_commit/languages/system.py rename to pre_commit/languages/unsupported.py diff --git a/pre_commit/languages/script.py b/pre_commit/languages/unsupported_script.py similarity index 100% rename from pre_commit/languages/script.py rename to pre_commit/languages/unsupported_script.py diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 2251abc4e..93c698f79 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -380,6 +380,26 @@ def test_no_warning_for_non_deprecated_default_stages(caplog): assert caplog.record_tuples == [] +def test_unsupported_language_migration(): + cfg = {'repos': [sample_local_config(), sample_local_config()]} + cfg['repos'][0]['hooks'][0]['language'] = 'system' + cfg['repos'][1]['hooks'][0]['language'] = 'script' + + cfgv.validate(cfg, CONFIG_SCHEMA) + ret = cfgv.apply_defaults(cfg, CONFIG_SCHEMA) + + assert ret['repos'][0]['hooks'][0]['language'] == 'unsupported' + assert ret['repos'][1]['hooks'][0]['language'] == 'unsupported_script' + + +def test_unsupported_language_migration_language_required(): + cfg = {'repos': [sample_local_config()]} + del cfg['repos'][0]['hooks'][0]['language'] + + with pytest.raises(cfgv.ValidationError): + cfgv.validate(cfg, CONFIG_SCHEMA) + + @pytest.mark.parametrize( 'manifest_obj', ( diff --git a/tests/languages/system_test.py b/tests/languages/system_test.py deleted file mode 100644 index dcd9cf1e0..000000000 --- a/tests/languages/system_test.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -from pre_commit.languages import system -from testing.language_helpers import run_language - - -def test_system_language(tmp_path): - expected = (0, b'hello hello world\n') - assert run_language(tmp_path, system, 'echo hello hello world') == expected diff --git a/tests/languages/script_test.py b/tests/languages/unsupported_script_test.py similarity index 63% rename from tests/languages/script_test.py rename to tests/languages/unsupported_script_test.py index a02f615a9..b15b67e76 100644 --- a/tests/languages/script_test.py +++ b/tests/languages/unsupported_script_test.py @@ -1,14 +1,14 @@ from __future__ import annotations -from pre_commit.languages import script +from pre_commit.languages import unsupported_script from pre_commit.util import make_executable from testing.language_helpers import run_language -def test_script_language(tmp_path): +def test_unsupported_script_language(tmp_path): exe = tmp_path.joinpath('main') exe.write_text('#!/usr/bin/env bash\necho hello hello world\n') make_executable(exe) expected = (0, b'hello hello world\n') - assert run_language(tmp_path, script, 'main') == expected + assert run_language(tmp_path, unsupported_script, 'main') == expected diff --git a/tests/languages/unsupported_test.py b/tests/languages/unsupported_test.py new file mode 100644 index 000000000..7f8461e02 --- /dev/null +++ b/tests/languages/unsupported_test.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from pre_commit.languages import unsupported +from testing.language_helpers import run_language + + +def test_unsupported_language(tmp_path): + expected = (0, b'hello hello world\n') + ret = run_language(tmp_path, unsupported, 'echo hello hello world') + assert ret == expected diff --git a/tests/repository_test.py b/tests/repository_test.py index f1559301f..b1c7a0024 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -17,7 +17,7 @@ from pre_commit.clientlib import load_manifest from pre_commit.hook import Hook from pre_commit.languages import python -from pre_commit.languages import system +from pre_commit.languages import unsupported from pre_commit.prefix import Prefix from pre_commit.repository import _hook_installed from pre_commit.repository import all_hooks @@ -424,7 +424,7 @@ def test_manifest_hooks(tempdir_factory, store): exclude_types=[], files='', id='bash_hook', - language='script', + language='unsupported_script', language_version='default', log_file='', minimum_pre_commit_version='0', @@ -457,7 +457,7 @@ def test_non_installable_hook_error_for_language_version(store, caplog): 'hooks': [{ 'id': 'system-hook', 'name': 'system-hook', - 'language': 'system', + 'language': 'unsupported', 'entry': 'python3 -c "import sys; print(sys.version)"', 'language_version': 'python3.10', }], @@ -469,7 +469,7 @@ def test_non_installable_hook_error_for_language_version(store, caplog): msg, = caplog.messages assert msg == ( 'The hook `system-hook` specifies `language_version` but is using ' - 'language `system` which does not install an environment. ' + 'language `unsupported` which does not install an environment. ' 'Perhaps you meant to use a specific language?' ) @@ -480,7 +480,7 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog): 'hooks': [{ 'id': 'system-hook', 'name': 'system-hook', - 'language': 'system', + 'language': 'unsupported', 'entry': 'python3 -c "import sys; print(sys.version)"', 'additional_dependencies': ['astpretty'], }], @@ -492,14 +492,14 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog): msg, = caplog.messages assert msg == ( 'The hook `system-hook` specifies `additional_dependencies` but is ' - 'using language `system` which does not install an environment. ' + 'using language `unsupported` which does not install an environment. ' 'Perhaps you meant to use a specific language?' ) def test_args_with_spaces_and_quotes(tmp_path): ret = run_language( - tmp_path, system, + tmp_path, unsupported, f"{shlex.quote(sys.executable)} -c 'import sys; print(sys.argv[1:])'", ('i have spaces', 'and"\'quotes', '$and !this'), ) From f80801d75a429d5eafa1d87e9f88f73b108d1890 Mon Sep 17 00:00:00 2001 From: Radek Hrbacek Date: Fri, 5 Sep 2025 15:01:10 +0200 Subject: [PATCH 325/342] Fix docker-in-docker detection for cgroups v2 --- pre_commit/languages/docker.py | 40 +++--- tests/languages/docker_test.py | 230 +++++++++++++++++++++++++-------- 2 files changed, 201 insertions(+), 69 deletions(-) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index d5ce1eb70..7f45ac865 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -1,9 +1,11 @@ from __future__ import annotations +import contextlib import functools import hashlib import json import os +import re from collections.abc import Sequence from pre_commit import lang_base @@ -17,31 +19,33 @@ health_check = lang_base.basic_health_check in_env = lang_base.no_env # no special environment for docker +_HOSTNAME_MOUNT_RE = re.compile( + rb""" + /containers + (?:/overlay-containers)? + /([a-z0-9]{64}) + (?:/userdata)? + /hostname + """, + re.VERBOSE, +) -def _is_in_docker() -> bool: - try: - with open('/proc/1/cgroup', 'rb') as f: - return b'docker' in f.read() - except FileNotFoundError: - return False +def _get_container_id() -> str | None: + with contextlib.suppress(FileNotFoundError): + with open('/proc/1/mountinfo', 'rb') as f: + for line in f: + m = _HOSTNAME_MOUNT_RE.search(line) + if m: + return m[1].decode() -def _get_container_id() -> str: - # It's assumed that we already check /proc/1/cgroup in _is_in_docker. The - # cpuset cgroup controller existed since cgroups were introduced so this - # way of getting the container ID is pretty reliable. - with open('/proc/1/cgroup', 'rb') as f: - for line in f.readlines(): - if line.split(b':')[1] == b'cpuset': - return os.path.basename(line.split(b':')[2]).strip().decode() - raise RuntimeError('Failed to find the container ID in /proc/1/cgroup.') + return None def _get_docker_path(path: str) -> str: - if not _is_in_docker(): - return path - container_id = _get_container_id() + if container_id is None: + return path try: _, out, _ = cmd_output_b('docker', 'inspect', container_id) diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py index b830439a2..e269976f7 100644 --- a/tests/languages/docker_test.py +++ b/tests/languages/docker_test.py @@ -14,40 +14,173 @@ from testing.language_helpers import run_language from testing.util import xfailif_windows -DOCKER_CGROUP_EXAMPLE = b'''\ -12:hugetlb:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -11:blkio:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -10:freezer:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -9:cpu,cpuacct:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -8:pids:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -7:rdma:/ -6:net_cls,net_prio:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -5:cpuset:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -4:devices:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -3:memory:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -2:perf_event:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -1:name=systemd:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 -0::/system.slice/containerd.service +DOCKER_CGROUPS_V1_MOUNTINFO_EXAMPLE = b'''\ +759 717 0:52 / / rw,relatime master:300 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/PCPE5P5IVGM7CFCPJR353N3ONK:/var/lib/docker/overlay2/l/EQFSDHFAJ333VEMEJD4ZTRIZCB,upperdir=/var/lib/docker/overlay2/0d9f6bf186030d796505b87d6daa92297355e47641e283d3c09d83a7f221e462/diff,workdir=/var/lib/docker/overlay2/0d9f6bf186030d796505b87d6daa92297355e47641e283d3c09d83a7f221e462/work +760 759 0:58 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +761 759 0:59 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +762 761 0:60 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +763 759 0:61 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +764 763 0:62 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755,inode64 +765 764 0:29 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/systemd ro,nosuid,nodev,noexec,relatime master:11 - cgroup cgroup rw,xattr,name=systemd +766 764 0:32 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/rdma ro,nosuid,nodev,noexec,relatime master:15 - cgroup cgroup rw,rdma +767 764 0:33 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime master:16 - cgroup cgroup rw,cpu,cpuacct +768 764 0:34 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/cpuset ro,nosuid,nodev,noexec,relatime master:17 - cgroup cgroup rw,cpuset +769 764 0:35 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/pids ro,nosuid,nodev,noexec,relatime master:18 - cgroup cgroup rw,pids +770 764 0:36 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime master:19 - cgroup cgroup rw,memory +771 764 0:37 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/perf_event ro,nosuid,nodev,noexec,relatime master:20 - cgroup cgroup rw,perf_event +772 764 0:38 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/net_cls,net_prio ro,nosuid,nodev,noexec,relatime master:21 - cgroup cgroup rw,net_cls,net_prio +773 764 0:39 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/blkio ro,nosuid,nodev,noexec,relatime master:22 - cgroup cgroup rw,blkio +774 764 0:40 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/misc ro,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,misc +775 764 0:41 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/hugetlb ro,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,hugetlb +776 764 0:42 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/devices ro,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,devices +777 764 0:43 /docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 /sys/fs/cgroup/freezer ro,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,freezer +778 761 0:57 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +779 761 0:63 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64 +780 759 8:5 /var/lib/docker/containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/sda5 rw,errors=remount-ro +781 759 8:5 /var/lib/docker/containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/hostname /etc/hostname rw,relatime - ext4 /dev/sda5 rw,errors=remount-ro +782 759 8:5 /var/lib/docker/containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/hosts /etc/hosts rw,relatime - ext4 /dev/sda5 rw,errors=remount-ro +718 761 0:60 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +719 760 0:58 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +720 760 0:58 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +721 760 0:58 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +722 760 0:58 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +723 760 0:58 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +724 760 0:64 / /proc/asound ro,relatime - tmpfs tmpfs ro,inode64 +725 760 0:65 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64 +726 760 0:59 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +727 760 0:59 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +728 760 0:59 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +729 760 0:66 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64 +730 763 0:67 / /sys/firmware ro,relatime - tmpfs tmpfs ro,inode64 +731 763 0:68 / /sys/devices/virtual/powercap ro,relatime - tmpfs tmpfs ro,inode64 +''' # noqa: E501 + +DOCKER_CGROUPS_V2_MOUNTINFO_EXAMPLE = b'''\ +721 386 0:45 / / rw,relatime master:218 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/QHZ7OM7P4AQD3XLG274ZPWAJCV:/var/lib/docker/overlay2/l/5RFG6SZWVGOG2NKEYXJDQCQYX5,upperdir=/var/lib/docker/overlay2/e4ad859fc5d4791932b9b976052f01fb0063e01de3cef916e40ae2121f6a166e/diff,workdir=/var/lib/docker/overlay2/e4ad859fc5d4791932b9b976052f01fb0063e01de3cef916e40ae2121f6a166e/work,nouserxattr +722 721 0:48 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +723 721 0:50 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +724 723 0:51 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +725 721 0:52 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +726 725 0:26 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot +727 723 0:47 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +728 723 0:53 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64 +729 721 8:3 /var/lib/docker/containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/sda3 rw,errors=remount-ro +730 721 8:3 /var/lib/docker/containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/hostname /etc/hostname rw,relatime - ext4 /dev/sda3 rw,errors=remount-ro +731 721 8:3 /var/lib/docker/containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/hosts /etc/hosts rw,relatime - ext4 /dev/sda3 rw,errors=remount-ro +387 723 0:51 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +388 722 0:48 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +389 722 0:48 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +525 722 0:48 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +526 722 0:48 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +571 722 0:48 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +572 722 0:57 / /proc/asound ro,relatime - tmpfs tmpfs ro,inode64 +575 722 0:58 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64 +576 722 0:50 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +577 722 0:50 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +578 722 0:50 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64 +579 722 0:59 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64 +580 725 0:60 / /sys/firmware ro,relatime - tmpfs tmpfs ro,inode64 +''' # noqa: E501 + +PODMAN_CGROUPS_V1_MOUNTINFO_EXAMPLE = b'''\ +1200 915 0:57 / / rw,relatime - overlay overlay rw,lowerdir=/home/asottile/.local/share/containers/storage/overlay/l/ZWAU3VY3ZHABQJRBUAFPBX7R5D,upperdir=/home/asottile/.local/share/containers/storage/overlay/72504ef163fda63838930450553b7306412ccad139a007626732b3dc43af5200/diff,workdir=/home/asottile/.local/share/containers/storage/overlay/72504ef163fda63838930450553b7306412ccad139a007626732b3dc43af5200/work,volatile,userxattr +1204 1200 0:62 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +1205 1200 0:63 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,uid=1000,gid=1000,inode64 +1206 1200 0:64 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs rw +1207 1205 0:65 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=100004,mode=620,ptmxmode=666 +1208 1205 0:61 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +1209 1200 0:53 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/.containerenv /run/.containerenv rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=814036k,mode=700,uid=1000,gid=1000,inode64 +1210 1200 0:53 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/resolv.conf /etc/resolv.conf rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=814036k,mode=700,uid=1000,gid=1000,inode64 +1211 1200 0:53 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/hosts /etc/hosts rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=814036k,mode=700,uid=1000,gid=1000,inode64 +1212 1205 0:56 / /dev/shm rw,relatime - tmpfs shm rw,size=64000k,uid=1000,gid=1000,inode64 +1213 1200 0:53 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/hostname /etc/hostname rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=814036k,mode=700,uid=1000,gid=1000,inode64 +1214 1206 0:66 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs cgroup rw,size=1024k,uid=1000,gid=1000,inode64 +1215 1214 0:43 / /sys/fs/cgroup/freezer ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer +1216 1214 0:42 /user.slice /sys/fs/cgroup/devices ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices +1217 1214 0:41 / /sys/fs/cgroup/hugetlb ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb +1218 1214 0:40 / /sys/fs/cgroup/misc ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,misc +1219 1214 0:39 / /sys/fs/cgroup/blkio ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio +1220 1214 0:38 / /sys/fs/cgroup/net_cls,net_prio ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls,net_prio +1221 1214 0:37 / /sys/fs/cgroup/perf_event ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event +1222 1214 0:36 /user.slice/user-1000.slice/user@1000.service /sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory +1223 1214 0:35 /user.slice/user-1000.slice/user@1000.service /sys/fs/cgroup/pids ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,pids +1224 1214 0:34 / /sys/fs/cgroup/cpuset ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset +1225 1214 0:33 / /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu,cpuacct +1226 1214 0:32 / /sys/fs/cgroup/rdma ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,rdma +1227 1214 0:29 /user.slice/user-1000.slice/user@1000.service/apps.slice/apps-org.gnome.Terminal.slice/vte-spawn-0c50448e-b395-4d76-8b92-379f16e5066f.scope /sys/fs/cgroup/systemd ro,nosuid,nodev,noexec,relatime - cgroup cgroup rw,xattr,name=systemd +1228 1205 0:5 /null /dev/null rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1229 1205 0:5 /zero /dev/zero rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1230 1205 0:5 /full /dev/full rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1231 1205 0:5 /tty /dev/tty rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1232 1205 0:5 /random /dev/random rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1233 1205 0:5 /urandom /dev/urandom rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1234 1204 0:67 / /proc/acpi ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +1235 1204 0:5 /null /proc/kcore rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1236 1204 0:5 /null /proc/keys rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1237 1204 0:5 /null /proc/timer_list rw,nosuid,noexec,relatime - devtmpfs udev rw,size=4031656k,nr_inodes=1007914,mode=755,inode64 +1238 1204 0:68 / /proc/scsi ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +1239 1206 0:69 / /sys/firmware ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +1240 1206 0:70 / /sys/dev/block ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +1241 1204 0:62 /asound /proc/asound ro,relatime - proc proc rw +1242 1204 0:62 /bus /proc/bus ro,relatime - proc proc rw +1243 1204 0:62 /fs /proc/fs ro,relatime - proc proc rw +1244 1204 0:62 /irq /proc/irq ro,relatime - proc proc rw +1245 1204 0:62 /sys /proc/sys ro,relatime - proc proc rw +1256 1204 0:62 /sysrq-trigger /proc/sysrq-trigger ro,relatime - proc proc rw +916 1205 0:65 /0 /dev/console rw,relatime - devpts devpts rw,gid=100004,mode=620,ptmxmode=666 +''' # noqa: E501 + +PODMAN_CGROUPS_V2_MOUNTINFO_EXAMPLE = b'''\ +685 690 0:63 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/resolv.conf /etc/resolv.conf rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=1637624k,nr_inodes=409406,mode=700,uid=1000,gid=1000,inode64 +686 690 0:63 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/hosts /etc/hosts rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=1637624k,nr_inodes=409406,mode=700,uid=1000,gid=1000,inode64 +687 692 0:50 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=64000k,uid=1000,gid=1000,inode64 +688 690 0:63 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/.containerenv /run/.containerenv rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=1637624k,nr_inodes=409406,mode=700,uid=1000,gid=1000,inode64 +689 690 0:63 /containers/overlay-containers/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7/userdata/hostname /etc/hostname rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=1637624k,nr_inodes=409406,mode=700,uid=1000,gid=1000,inode64 +690 546 0:55 / / rw,relatime - overlay overlay rw,lowerdir=/home/asottile/.local/share/containers/storage/overlay/l/NPOHYOD3PI3YW6TQSGBOVOUSK6,upperdir=/home/asottile/.local/share/containers/storage/overlay/565c206fb79f876ffd5f069b8bd7a97fb5e47d5d07396b0c395a4ed6725d4a8e/diff,workdir=/home/asottile/.local/share/containers/storage/overlay/565c206fb79f876ffd5f069b8bd7a97fb5e47d5d07396b0c395a4ed6725d4a8e/work,redirect_dir=nofollow,uuid=on,volatile,userxattr +691 690 0:59 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +692 690 0:61 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,uid=1000,gid=1000,inode64 +693 690 0:62 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs rw +694 692 0:66 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=100004,mode=620,ptmxmode=666 +695 692 0:58 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +696 693 0:28 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot +698 692 0:6 /null /dev/null rw,nosuid,noexec,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +699 692 0:6 /zero /dev/zero rw,nosuid,noexec,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +700 692 0:6 /full /dev/full rw,nosuid,noexec,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +701 692 0:6 /tty /dev/tty rw,nosuid,noexec,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +702 692 0:6 /random /dev/random rw,nosuid,noexec,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +703 692 0:6 /urandom /dev/urandom rw,nosuid,noexec,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +704 691 0:67 / /proc/acpi ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +705 691 0:6 /null /proc/kcore ro,nosuid,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +706 691 0:6 /null /proc/keys ro,nosuid,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +707 691 0:6 /null /proc/latency_stats ro,nosuid,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +708 691 0:6 /null /proc/timer_list ro,nosuid,relatime - devtmpfs udev rw,size=8147812k,nr_inodes=2036953,mode=755,inode64 +709 691 0:68 / /proc/scsi ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +710 693 0:69 / /sys/firmware ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +711 693 0:70 / /sys/dev/block ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +712 693 0:71 / /sys/devices/virtual/powercap ro,relatime - tmpfs tmpfs rw,size=0k,uid=1000,gid=1000,inode64 +713 691 0:59 /asound /proc/asound ro,nosuid,nodev,noexec,relatime - proc proc rw +714 691 0:59 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +715 691 0:59 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +716 691 0:59 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +717 691 0:59 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +718 691 0:59 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +547 692 0:66 /0 /dev/console rw,relatime - devpts devpts rw,gid=100004,mode=620,ptmxmode=666 ''' # noqa: E501 # The ID should match the above cgroup example. CONTAINER_ID = 'c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7' # noqa: E501 -NON_DOCKER_CGROUP_EXAMPLE = b'''\ -12:perf_event:/ -11:hugetlb:/ -10:devices:/ -9:blkio:/ -8:rdma:/ -7:cpuset:/ -6:cpu,cpuacct:/ -5:freezer:/ -4:memory:/ -3:pids:/ -2:net_cls,net_prio:/ -1:name=systemd:/init.scope -0::/init.scope -''' +NON_DOCKER_MOUNTINFO_EXAMPLE = b'''\ +21 27 0:19 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw +22 27 0:20 / /proc rw,nosuid,nodev,noexec,relatime shared:14 - proc proc rw +23 27 0:5 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=10219484k,nr_inodes=2554871,mode=755,inode64 +24 23 0:21 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000 +25 27 0:22 / /run rw,nosuid,nodev,noexec,relatime shared:5 - tmpfs tmpfs rw,size=2047768k,mode=755,inode64 +27 1 8:2 / / rw,relatime shared:1 - ext4 /dev/sda2 rw,errors=remount-ro +28 21 0:6 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:8 - securityfs securityfs rw +29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw,inode64 +30 25 0:25 / /run/lock rw,nosuid,nodev,noexec,relatime shared:6 - tmpfs tmpfs rw,size=5120k,inode64 +''' # noqa: E501 def test_docker_fallback_user(): @@ -99,9 +232,9 @@ def test_docker_user_non_rootless(info_ret): assert docker.get_docker_user() != () -def test_in_docker_no_file(): +def test_container_id_no_file(): with mock.patch.object(builtins, 'open', side_effect=FileNotFoundError): - assert docker._is_in_docker() is False + assert docker._get_container_id() is None def _mock_open(data): @@ -113,38 +246,33 @@ def _mock_open(data): ) -def test_in_docker_docker_in_file(): - with _mock_open(DOCKER_CGROUP_EXAMPLE): - assert docker._is_in_docker() is True - - -def test_in_docker_docker_not_in_file(): - with _mock_open(NON_DOCKER_CGROUP_EXAMPLE): - assert docker._is_in_docker() is False +def test_container_id_not_in_file(): + with _mock_open(NON_DOCKER_MOUNTINFO_EXAMPLE): + assert docker._get_container_id() is None def test_get_container_id(): - with _mock_open(DOCKER_CGROUP_EXAMPLE): + with _mock_open(DOCKER_CGROUPS_V1_MOUNTINFO_EXAMPLE): + assert docker._get_container_id() == CONTAINER_ID + with _mock_open(DOCKER_CGROUPS_V2_MOUNTINFO_EXAMPLE): + assert docker._get_container_id() == CONTAINER_ID + with _mock_open(PODMAN_CGROUPS_V1_MOUNTINFO_EXAMPLE): + assert docker._get_container_id() == CONTAINER_ID + with _mock_open(PODMAN_CGROUPS_V2_MOUNTINFO_EXAMPLE): assert docker._get_container_id() == CONTAINER_ID - - -def test_get_container_id_failure(): - with _mock_open(b''), pytest.raises(RuntimeError): - docker._get_container_id() def test_get_docker_path_not_in_docker_returns_same(): - with mock.patch.object(docker, '_is_in_docker', return_value=False): + with _mock_open(b''): assert docker._get_docker_path('abc') == 'abc' @pytest.fixture def in_docker(): - with mock.patch.object(docker, '_is_in_docker', return_value=True): - with mock.patch.object( - docker, '_get_container_id', return_value=CONTAINER_ID, - ): - yield + with mock.patch.object( + docker, '_get_container_id', return_value=CONTAINER_ID, + ): + yield def _linux_commonpath(): From 17cf8864737af2ce75c73839a0cdedc26ce50598 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 8 Nov 2025 16:11:43 -0500 Subject: [PATCH 326/342] v4.4.0 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- setup.cfg | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a63f781..b27af5e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +4.4.0 - 2025-11-08 +================== + +### Features +- Add `--fail-fast` option to `pre-commit run`. + - #3528 PR by @JulianMaurin. +- Upgrade `ruby-build` / `rbenv`. + - #3566 PR by @asottile. + - #3565 issue by @MRigal. +- Add `language: unsupported` / `language: unsupported_script` as aliases + for `language: system` / `language: script` (which will eventually be + deprecated). + - #3577 PR by @asottile. +- Add support docker-in-docker detection for cgroups v2. + - #3535 PR by @br-rhrbacek. + - #3360 issue by @JasonAlt. + +### Fixes +- Handle when docker gives `SecurityOptions: null`. + - #3537 PR by @asottile. + - #3514 issue by @jenstroeger. +- Fix error context for invalid `stages` in `.pre-commit-config.yaml`. + - #3576 PR by @asottile. + 4.3.0 - 2025-08-09 ================== @@ -71,7 +95,7 @@ - #3315 PR by @asottile. - #2732 issue by @asottile. -### Migrating +### Updating - `language: python_venv` has been removed -- use `language: python` instead. - #3320 PR by @asottile. - #2734 issue by @asottile. @@ -159,7 +183,7 @@ - Use `time.monotonic()` for more accurate hook timing. - #3024 PR by @adamchainz. -### Migrating +### Updating - Require npm 6.x+ for `language: node` hooks. - #2996 PR by @RoelAdriaans. - #1983 issue by @henryiii. diff --git a/setup.cfg b/setup.cfg index 17c3fe0eb..be031c3ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.3.0 +version = 4.4.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From d5c273a2ba0c712659640e9487adb38bd7da68f6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 9 Nov 2025 16:57:40 -0500 Subject: [PATCH 327/342] refactor gc into store this will make refactoring this easier later and limits the api surface of Store --- pre_commit/commands/gc.py | 82 +-------------------------------- pre_commit/store.py | 96 +++++++++++++++++++++++++++++++-------- tests/commands/gc_test.py | 9 ++-- tests/store_test.py | 25 +++++++--- 4 files changed, 101 insertions(+), 111 deletions(-) diff --git a/pre_commit/commands/gc.py b/pre_commit/commands/gc.py index 6892e097c..d1941e4bb 100644 --- a/pre_commit/commands/gc.py +++ b/pre_commit/commands/gc.py @@ -1,89 +1,9 @@ from __future__ import annotations -import os.path -from typing import Any - -import pre_commit.constants as C from pre_commit import output -from pre_commit.clientlib import InvalidConfigError -from pre_commit.clientlib import InvalidManifestError -from pre_commit.clientlib import load_config -from pre_commit.clientlib import load_manifest -from pre_commit.clientlib import LOCAL -from pre_commit.clientlib import META from pre_commit.store import Store -def _mark_used_repos( - store: Store, - all_repos: dict[tuple[str, str], str], - unused_repos: set[tuple[str, str]], - repo: dict[str, Any], -) -> None: - if repo['repo'] == META: - return - elif repo['repo'] == LOCAL: - for hook in repo['hooks']: - deps = hook.get('additional_dependencies') - unused_repos.discard(( - store.db_repo_name(repo['repo'], deps), C.LOCAL_REPO_VERSION, - )) - else: - key = (repo['repo'], repo['rev']) - path = all_repos.get(key) - # can't inspect manifest if it isn't cloned - if path is None: - return - - try: - manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE)) - except InvalidManifestError: - return - else: - unused_repos.discard(key) - by_id = {hook['id']: hook for hook in manifest} - - for hook in repo['hooks']: - if hook['id'] not in by_id: - continue - - deps = hook.get( - 'additional_dependencies', - by_id[hook['id']]['additional_dependencies'], - ) - unused_repos.discard(( - store.db_repo_name(repo['repo'], deps), repo['rev'], - )) - - -def _gc_repos(store: Store) -> int: - configs = store.select_all_configs() - repos = store.select_all_repos() - - # delete config paths which do not exist - dead_configs = [p for p in configs if not os.path.exists(p)] - live_configs = [p for p in configs if os.path.exists(p)] - - all_repos = {(repo, ref): path for repo, ref, path in repos} - unused_repos = set(all_repos) - for config_path in live_configs: - try: - config = load_config(config_path) - except InvalidConfigError: - dead_configs.append(config_path) - continue - else: - for repo in config['repos']: - _mark_used_repos(store, all_repos, unused_repos, repo) - - store.delete_configs(dead_configs) - for db_repo_name, ref in unused_repos: - store.delete_repo(db_repo_name, ref, all_repos[(db_repo_name, ref)]) - return len(unused_repos) - - def gc(store: Store) -> int: - with store.exclusive_lock(): - repos_removed = _gc_repos(store) - output.write_line(f'{repos_removed} repo(s) removed.') + output.write_line(f'{store.gc()} repo(s) removed.') return 0 diff --git a/pre_commit/store.py b/pre_commit/store.py index 9e3b40485..34c5f0d92 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -8,6 +8,7 @@ from collections.abc import Callable from collections.abc import Generator from collections.abc import Sequence +from typing import Any import pre_commit.constants as C from pre_commit import clientlib @@ -96,7 +97,7 @@ def __init__(self, directory: str | None = None) -> None: ' PRIMARY KEY (repo, ref)' ');', ) - self._create_config_table(db) + self._create_configs_table(db) # Atomic file move os.replace(tmpfile, self.db_path) @@ -215,7 +216,7 @@ def make_local(self, deps: Sequence[str]) -> str: 'local', C.LOCAL_REPO_VERSION, deps, _make_local_repo, ) - def _create_config_table(self, db: sqlite3.Connection) -> None: + def _create_configs_table(self, db: sqlite3.Connection) -> None: db.executescript( 'CREATE TABLE IF NOT EXISTS configs (' ' path TEXT NOT NULL,' @@ -232,28 +233,83 @@ def mark_config_used(self, path: str) -> None: return with self.connect() as db: # TODO: eventually remove this and only create in _create - self._create_config_table(db) + self._create_configs_table(db) db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,)) - def select_all_configs(self) -> list[str]: - with self.connect() as db: - self._create_config_table(db) - rows = db.execute('SELECT path FROM configs').fetchall() - return [path for path, in rows] + def _mark_used_repos( + self, + all_repos: dict[tuple[str, str], str], + unused_repos: set[tuple[str, str]], + repo: dict[str, Any], + ) -> None: + if repo['repo'] == clientlib.META: + return + elif repo['repo'] == clientlib.LOCAL: + for hook in repo['hooks']: + deps = hook.get('additional_dependencies') + unused_repos.discard(( + self.db_repo_name(repo['repo'], deps), + C.LOCAL_REPO_VERSION, + )) + else: + key = (repo['repo'], repo['rev']) + path = all_repos.get(key) + # can't inspect manifest if it isn't cloned + if path is None: + return - def delete_configs(self, configs: list[str]) -> None: - with self.connect() as db: - rows = [(path,) for path in configs] - db.executemany('DELETE FROM configs WHERE path = ?', rows) + try: + manifest = clientlib.load_manifest( + os.path.join(path, C.MANIFEST_FILE), + ) + except clientlib.InvalidManifestError: + return + else: + unused_repos.discard(key) + by_id = {hook['id']: hook for hook in manifest} - def select_all_repos(self) -> list[tuple[str, str, str]]: - with self.connect() as db: - return db.execute('SELECT repo, ref, path from repos').fetchall() + for hook in repo['hooks']: + if hook['id'] not in by_id: + continue - def delete_repo(self, db_repo_name: str, ref: str, path: str) -> None: - with self.connect() as db: - db.execute( + deps = hook.get( + 'additional_dependencies', + by_id[hook['id']]['additional_dependencies'], + ) + unused_repos.discard(( + self.db_repo_name(repo['repo'], deps), repo['rev'], + )) + + def gc(self) -> int: + with self.exclusive_lock(), self.connect() as db: + self._create_configs_table(db) + + repos = db.execute('SELECT repo, ref, path FROM repos').fetchall() + all_repos = {(repo, ref): path for repo, ref, path in repos} + unused_repos = set(all_repos) + + configs_rows = db.execute('SELECT path FROM configs').fetchall() + configs = [path for path, in configs_rows] + + dead_configs = [] + for config_path in configs: + try: + config = clientlib.load_config(config_path) + except clientlib.InvalidConfigError: + dead_configs.append(config_path) + continue + else: + for repo in config['repos']: + self._mark_used_repos(all_repos, unused_repos, repo) + + paths = [(path,) for path in dead_configs] + db.executemany('DELETE FROM configs WHERE path = ?', paths) + + db.executemany( 'DELETE FROM repos WHERE repo = ? and ref = ?', - (db_repo_name, ref), + sorted(unused_repos), ) - rmtree(path) + for k in unused_repos: + rmtree(all_repos[k]) + + return len(unused_repos) diff --git a/tests/commands/gc_test.py b/tests/commands/gc_test.py index 95113ed5c..85e66977e 100644 --- a/tests/commands/gc_test.py +++ b/tests/commands/gc_test.py @@ -19,11 +19,13 @@ def _repo_count(store): - return len(store.select_all_repos()) + with store.connect() as db: + return db.execute('SELECT COUNT(1) FROM repos').fetchone()[0] def _config_count(store): - return len(store.select_all_configs()) + with store.connect() as db: + return db.execute('SELECT COUNT(1) FROM configs').fetchone()[0] def _remove_config_assert_cleared(store, cap_out): @@ -153,7 +155,8 @@ def test_invalid_manifest_gcd(tempdir_factory, store, in_git_dir, cap_out): install_hooks(C.CONFIG_FILE, store) # we'll "break" the manifest to simulate an old version clone - (_, _, path), = store.select_all_repos() + with store.connect() as db: + path, = db.execute('SELECT path FROM repos').fetchone() os.remove(os.path.join(path, C.MANIFEST_FILE)) assert _config_count(store) == 1 diff --git a/tests/store_test.py b/tests/store_test.py index 7d4dffb09..4b04a8e7f 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -22,6 +22,17 @@ from testing.util import xfailif_windows +def _select_all_configs(store: Store) -> list[str]: + with store.connect() as db: + rows = db.execute('SELECT * FROM configs').fetchall() + return [path for path, in rows] + + +def _select_all_repos(store: Store) -> list[tuple[str, str, str]]: + with store.connect() as db: + return db.execute('SELECT repo, ref, path FROM repos').fetchall() + + def test_our_session_fixture_works(): """There's a session fixture which makes `Store` invariantly raise to prevent writing to the home directory. @@ -91,7 +102,7 @@ def test_clone(store, tempdir_factory, caplog): assert git.head_rev(ret) == rev # Assert there's an entry in the sqlite db for this - assert store.select_all_repos() == [(path, rev, ret)] + assert _select_all_repos(store) == [(path, rev, ret)] def test_warning_for_deprecated_stages_on_init(store, tempdir_factory, caplog): @@ -217,7 +228,7 @@ def fake_shallow_clone(self, *args, **kwargs): assert git.head_rev(ret) == rev # Assert there's an entry in the sqlite db for this - assert store.select_all_repos() == [(path, rev, ret)] + assert _select_all_repos(store) == [(path, rev, ret)] def test_clone_tag_not_on_mainline(store, tempdir_factory): @@ -265,7 +276,7 @@ def test_mark_config_as_used(store, tmpdir): with tmpdir.as_cwd(): f = tmpdir.join('f').ensure() store.mark_config_used('f') - assert store.select_all_configs() == [f.strpath] + assert _select_all_configs(store) == [f.strpath] def test_mark_config_as_used_idempotent(store, tmpdir): @@ -275,7 +286,7 @@ def test_mark_config_as_used_idempotent(store, tmpdir): def test_mark_config_as_used_does_not_exist(store): store.mark_config_used('f') - assert store.select_all_configs() == [] + assert _select_all_configs(store) == [] def _simulate_pre_1_14_0(store): @@ -283,9 +294,9 @@ def _simulate_pre_1_14_0(store): db.executescript('DROP TABLE configs') -def test_select_all_configs_roll_forward(store): +def test_gc_roll_forward(store): _simulate_pre_1_14_0(store) - assert store.select_all_configs() == [] + assert store.gc() == 0 def test_mark_config_as_used_roll_forward(store, tmpdir): @@ -314,7 +325,7 @@ def _chmod_minus_w(p): assert store.readonly # should be skipped due to readonly store.mark_config_used(str(cfg)) - assert store.select_all_configs() == [] + assert _select_all_configs(store) == [] def test_clone_with_recursive_submodules(store, tmp_path): From 063229aee77ba2da3e9ed5c8217070b4128234fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:59:54 +0000 Subject: [PATCH 328/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.21.0 → v3.21.1](https://github.com/asottile/pyupgrade/compare/v3.21.0...v3.21.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa0773656..e47d56ca3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.21.0 + rev: v3.21.1 hooks: - id: pyupgrade args: [--py310-plus] From 66278a9a0b69a69fde820d2b85a7e198eae52981 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 19 Nov 2025 14:29:50 -0500 Subject: [PATCH 329/342] move logic for gc back to commands.gc --- pre_commit/commands/gc.py | 91 ++++++++++++++++++++++++++++++++++++++- pre_commit/store.py | 80 ---------------------------------- tests/commands/gc_test.py | 8 ++++ tests/store_test.py | 13 +----- 4 files changed, 100 insertions(+), 92 deletions(-) diff --git a/pre_commit/commands/gc.py b/pre_commit/commands/gc.py index d1941e4bb..975d5e4c1 100644 --- a/pre_commit/commands/gc.py +++ b/pre_commit/commands/gc.py @@ -1,9 +1,98 @@ from __future__ import annotations +import os.path +from typing import Any + +import pre_commit.constants as C from pre_commit import output +from pre_commit.clientlib import InvalidConfigError +from pre_commit.clientlib import InvalidManifestError +from pre_commit.clientlib import load_config +from pre_commit.clientlib import load_manifest +from pre_commit.clientlib import LOCAL +from pre_commit.clientlib import META from pre_commit.store import Store +from pre_commit.util import rmtree + + +def _mark_used_repos( + store: Store, + all_repos: dict[tuple[str, str], str], + unused_repos: set[tuple[str, str]], + repo: dict[str, Any], +) -> None: + if repo['repo'] == META: + return + elif repo['repo'] == LOCAL: + for hook in repo['hooks']: + deps = hook.get('additional_dependencies') + unused_repos.discard(( + store.db_repo_name(repo['repo'], deps), + C.LOCAL_REPO_VERSION, + )) + else: + key = (repo['repo'], repo['rev']) + path = all_repos.get(key) + # can't inspect manifest if it isn't cloned + if path is None: + return + + try: + manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE)) + except InvalidManifestError: + return + else: + unused_repos.discard(key) + by_id = {hook['id']: hook for hook in manifest} + + for hook in repo['hooks']: + if hook['id'] not in by_id: + continue + + deps = hook.get( + 'additional_dependencies', + by_id[hook['id']]['additional_dependencies'], + ) + unused_repos.discard(( + store.db_repo_name(repo['repo'], deps), repo['rev'], + )) + + +def _gc(store: Store) -> int: + with store.exclusive_lock(), store.connect() as db: + store._create_configs_table(db) + + repos = db.execute('SELECT repo, ref, path FROM repos').fetchall() + all_repos = {(repo, ref): path for repo, ref, path in repos} + unused_repos = set(all_repos) + + configs_rows = db.execute('SELECT path FROM configs').fetchall() + configs = [path for path, in configs_rows] + + dead_configs = [] + for config_path in configs: + try: + config = load_config(config_path) + except InvalidConfigError: + dead_configs.append(config_path) + continue + else: + for repo in config['repos']: + _mark_used_repos(store, all_repos, unused_repos, repo) + + paths = [(path,) for path in dead_configs] + db.executemany('DELETE FROM configs WHERE path = ?', paths) + + db.executemany( + 'DELETE FROM repos WHERE repo = ? and ref = ?', + sorted(unused_repos), + ) + for k in unused_repos: + rmtree(all_repos[k]) + + return len(unused_repos) def gc(store: Store) -> int: - output.write_line(f'{store.gc()} repo(s) removed.') + output.write_line(f'{_gc(store)} repo(s) removed.') return 0 diff --git a/pre_commit/store.py b/pre_commit/store.py index 34c5f0d92..dc90c0519 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -8,7 +8,6 @@ from collections.abc import Callable from collections.abc import Generator from collections.abc import Sequence -from typing import Any import pre_commit.constants as C from pre_commit import clientlib @@ -18,7 +17,6 @@ from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b from pre_commit.util import resource_text -from pre_commit.util import rmtree logger = logging.getLogger('pre_commit') @@ -235,81 +233,3 @@ def mark_config_used(self, path: str) -> None: # TODO: eventually remove this and only create in _create self._create_configs_table(db) db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,)) - - def _mark_used_repos( - self, - all_repos: dict[tuple[str, str], str], - unused_repos: set[tuple[str, str]], - repo: dict[str, Any], - ) -> None: - if repo['repo'] == clientlib.META: - return - elif repo['repo'] == clientlib.LOCAL: - for hook in repo['hooks']: - deps = hook.get('additional_dependencies') - unused_repos.discard(( - self.db_repo_name(repo['repo'], deps), - C.LOCAL_REPO_VERSION, - )) - else: - key = (repo['repo'], repo['rev']) - path = all_repos.get(key) - # can't inspect manifest if it isn't cloned - if path is None: - return - - try: - manifest = clientlib.load_manifest( - os.path.join(path, C.MANIFEST_FILE), - ) - except clientlib.InvalidManifestError: - return - else: - unused_repos.discard(key) - by_id = {hook['id']: hook for hook in manifest} - - for hook in repo['hooks']: - if hook['id'] not in by_id: - continue - - deps = hook.get( - 'additional_dependencies', - by_id[hook['id']]['additional_dependencies'], - ) - unused_repos.discard(( - self.db_repo_name(repo['repo'], deps), repo['rev'], - )) - - def gc(self) -> int: - with self.exclusive_lock(), self.connect() as db: - self._create_configs_table(db) - - repos = db.execute('SELECT repo, ref, path FROM repos').fetchall() - all_repos = {(repo, ref): path for repo, ref, path in repos} - unused_repos = set(all_repos) - - configs_rows = db.execute('SELECT path FROM configs').fetchall() - configs = [path for path, in configs_rows] - - dead_configs = [] - for config_path in configs: - try: - config = clientlib.load_config(config_path) - except clientlib.InvalidConfigError: - dead_configs.append(config_path) - continue - else: - for repo in config['repos']: - self._mark_used_repos(all_repos, unused_repos, repo) - - paths = [(path,) for path in dead_configs] - db.executemany('DELETE FROM configs WHERE path = ?', paths) - - db.executemany( - 'DELETE FROM repos WHERE repo = ? and ref = ?', - sorted(unused_repos), - ) - for k in unused_repos: - rmtree(all_repos[k]) - - return len(unused_repos) diff --git a/tests/commands/gc_test.py b/tests/commands/gc_test.py index 85e66977e..992b02f3b 100644 --- a/tests/commands/gc_test.py +++ b/tests/commands/gc_test.py @@ -165,3 +165,11 @@ def test_invalid_manifest_gcd(tempdir_factory, store, in_git_dir, cap_out): assert _config_count(store) == 1 assert _repo_count(store) == 0 assert cap_out.get().splitlines()[-1] == '1 repo(s) removed.' + + +def test_gc_pre_1_14_roll_forward(store, cap_out): + with store.connect() as db: # simulate pre-1.14.0 + db.executescript('DROP TABLE configs') + + assert not gc(store) + assert cap_out.get() == '0 repo(s) removed.\n' diff --git a/tests/store_test.py b/tests/store_test.py index 4b04a8e7f..13f198ea2 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -289,18 +289,9 @@ def test_mark_config_as_used_does_not_exist(store): assert _select_all_configs(store) == [] -def _simulate_pre_1_14_0(store): - with store.connect() as db: - db.executescript('DROP TABLE configs') - - -def test_gc_roll_forward(store): - _simulate_pre_1_14_0(store) - assert store.gc() == 0 - - def test_mark_config_as_used_roll_forward(store, tmpdir): - _simulate_pre_1_14_0(store) + with store.connect() as db: # simulate pre-1.14.0 + db.executescript('DROP TABLE configs') test_mark_config_as_used(store, tmpdir) From 844dacc168d68a32553ecf8a99178ab395fdb11e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 19 Nov 2025 14:57:01 -0500 Subject: [PATCH 330/342] add forward-compat error message --- pre_commit/clientlib.py | 11 ++++++++++- tests/clientlib_test.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index eb0fd4d68..51f14d26e 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -270,10 +270,19 @@ class InvalidManifestError(FatalError): pass +def _load_manifest_forward_compat(contents: str) -> object: + obj = yaml_load(contents) + if isinstance(obj, dict): + check_min_version('5') + raise AssertionError('unreachable') + else: + return obj + + load_manifest = functools.partial( cfgv.load_from_filename, schema=MANIFEST_SCHEMA, - load_strategy=yaml_load, + load_strategy=_load_manifest_forward_compat, exc_tp=InvalidManifestError, ) diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 93c698f79..2c42b80cf 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -12,6 +12,8 @@ from pre_commit.clientlib import CONFIG_REPO_DICT from pre_commit.clientlib import CONFIG_SCHEMA from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION +from pre_commit.clientlib import InvalidManifestError +from pre_commit.clientlib import load_manifest from pre_commit.clientlib import MANIFEST_HOOK_DICT from pre_commit.clientlib import MANIFEST_SCHEMA from pre_commit.clientlib import META_HOOK_DICT @@ -588,3 +590,18 @@ def test_config_hook_stages_defaulting(): 'id': 'fake-hook', 'stages': ['commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit'], } + + +def test_manifest_v5_forward_compat(tmp_path): + manifest = tmp_path.joinpath('.pre-commit-hooks.yaml') + manifest.write_text('hooks: {}') + + with pytest.raises(InvalidManifestError) as excinfo: + load_manifest(manifest) + assert str(excinfo.value) == ( + f'\n' + f'==> File {manifest}\n' + f'=====> \n' + f'=====> pre-commit version 5 is required but version {C.VERSION} ' + f'is installed. Perhaps run `pip install --upgrade pre-commit`.' + ) From 8d34f95308fc4c14dea3d3e90153acfdaf55e2de Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 21 Nov 2025 15:09:41 -0500 Subject: [PATCH 331/342] use ExitStack instead of start + stop --- tests/main_test.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index 945349fa4..325792d89 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import contextlib import os.path from unittest import mock @@ -97,11 +98,9 @@ def test_adjust_args_try_repo_repo_relative(in_git_dir): @pytest.fixture def mock_commands(): - mcks = {fn: mock.patch.object(main, fn).start() for fn in FNS} - ret = auto_namedtuple(**mcks) - yield ret - for mck in ret: - mck.stop() + with contextlib.ExitStack() as ctx: + mcks = {f: ctx.enter_context(mock.patch.object(main, f)) for f in FNS} + yield auto_namedtuple(**mcks) @pytest.fixture From bdf68790b78158268bbc8482f76491a61d75809a Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Fri, 21 Nov 2025 16:38:55 -0500 Subject: [PATCH 332/342] add pre-commit hazmat --- pre_commit/commands/hazmat.py | 95 +++++++++++++++++++++++++++++++++ pre_commit/lang_base.py | 6 ++- pre_commit/main.py | 10 +++- tests/commands/hazmat_test.py | 99 +++++++++++++++++++++++++++++++++++ tests/lang_base_test.py | 12 +++++ tests/main_test.py | 12 +++++ tests/repository_test.py | 11 ++++ 7 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 pre_commit/commands/hazmat.py create mode 100644 tests/commands/hazmat_test.py diff --git a/pre_commit/commands/hazmat.py b/pre_commit/commands/hazmat.py new file mode 100644 index 000000000..01b27ce61 --- /dev/null +++ b/pre_commit/commands/hazmat.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import argparse +import subprocess +from collections.abc import Sequence + +from pre_commit.parse_shebang import normalize_cmd + + +def add_parsers(parser: argparse.ArgumentParser) -> None: + subparsers = parser.add_subparsers(dest='tool') + + cd_parser = subparsers.add_parser( + 'cd', help='cd to a subdir and run the command', + ) + cd_parser.add_argument('subdir') + cd_parser.add_argument('cmd', nargs=argparse.REMAINDER) + + ignore_exit_code_parser = subparsers.add_parser( + 'ignore-exit-code', help='run the command but ignore the exit code', + ) + ignore_exit_code_parser.add_argument('cmd', nargs=argparse.REMAINDER) + + n1_parser = subparsers.add_parser( + 'n1', help='run the command once per filename', + ) + n1_parser.add_argument('cmd', nargs=argparse.REMAINDER) + + +def _cmd_filenames(cmd: tuple[str, ...]) -> tuple[ + tuple[str, ...], + tuple[str, ...], +]: + for idx, val in enumerate(reversed(cmd)): + if val == '--': + split = len(cmd) - idx + break + else: + raise SystemExit('hazmat entry must end with `--`') + + return cmd[:split - 1], cmd[split:] + + +def cd(subdir: str, cmd: tuple[str, ...]) -> int: + cmd, filenames = _cmd_filenames(cmd) + + prefix = f'{subdir}/' + new_filenames = [] + for filename in filenames: + if not filename.startswith(prefix): + raise SystemExit(f'unexpected file without {prefix=}: {filename}') + else: + new_filenames.append(filename.removeprefix(prefix)) + + cmd = normalize_cmd(cmd) + return subprocess.call((*cmd, *new_filenames), cwd=subdir) + + +def ignore_exit_code(cmd: tuple[str, ...]) -> int: + cmd = normalize_cmd(cmd) + subprocess.call(cmd) + return 0 + + +def n1(cmd: tuple[str, ...]) -> int: + cmd, filenames = _cmd_filenames(cmd) + cmd = normalize_cmd(cmd) + ret = 0 + for filename in filenames: + ret |= subprocess.call((*cmd, filename)) + return ret + + +def impl(args: argparse.Namespace) -> int: + args.cmd = tuple(args.cmd) + if args.tool == 'cd': + return cd(args.subdir, args.cmd) + elif args.tool == 'ignore-exit-code': + return ignore_exit_code(args.cmd) + elif args.tool == 'n1': + return n1(args.cmd) + else: + raise NotImplementedError(f'unexpected tool: {args.tool}') + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + add_parsers(parser) + args = parser.parse_args(argv) + + return impl(args) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/pre_commit/lang_base.py b/pre_commit/lang_base.py index 95be7b9b3..198e93657 100644 --- a/pre_commit/lang_base.py +++ b/pre_commit/lang_base.py @@ -5,6 +5,7 @@ import random import re import shlex +import sys from collections.abc import Generator from collections.abc import Sequence from typing import Any @@ -171,7 +172,10 @@ def run_xargs( def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]: - return (*shlex.split(entry), *args) + cmd = shlex.split(entry) + if cmd[:2] == ['pre-commit', 'hazmat']: + cmd = [sys.executable, '-m', 'pre_commit.commands.hazmat', *cmd[2:]] + return (*cmd, *args) def basic_run_hook( diff --git a/pre_commit/main.py b/pre_commit/main.py index c33fbfdaa..0c3eefdaa 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -10,6 +10,7 @@ from pre_commit import clientlib from pre_commit import git from pre_commit.color import add_color_option +from pre_commit.commands import hazmat from pre_commit.commands.autoupdate import autoupdate from pre_commit.commands.clean import clean from pre_commit.commands.gc import gc @@ -41,7 +42,7 @@ os.environ.pop('PYTHONEXECUTABLE', None) COMMANDS_NO_GIT = { - 'clean', 'gc', 'init-templatedir', 'sample-config', + 'clean', 'gc', 'hazmat', 'init-templatedir', 'sample-config', 'validate-config', 'validate-manifest', } @@ -245,6 +246,11 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: _add_cmd('gc', help='Clean unused cached repos.') + hazmat_parser = _add_cmd( + 'hazmat', help='Composable tools for rare use in hook `entry`.', + ) + hazmat.add_parsers(hazmat_parser) + init_templatedir_parser = _add_cmd( 'init-templatedir', help=( @@ -389,6 +395,8 @@ def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: return clean(store) elif args.command == 'gc': return gc(store) + elif args.command == 'hazmat': + return hazmat.impl(args) elif args.command == 'hook-impl': return hook_impl( store, diff --git a/tests/commands/hazmat_test.py b/tests/commands/hazmat_test.py new file mode 100644 index 000000000..df957e36e --- /dev/null +++ b/tests/commands/hazmat_test.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import sys + +import pytest + +from pre_commit.commands.hazmat import _cmd_filenames +from pre_commit.commands.hazmat import main +from testing.util import cwd + + +def test_cmd_filenames_no_dash_dash(): + with pytest.raises(SystemExit) as excinfo: + _cmd_filenames(('no', 'dashdash', 'here')) + msg, = excinfo.value.args + assert msg == 'hazmat entry must end with `--`' + + +def test_cmd_filenames_no_filenames(): + cmd, filenames = _cmd_filenames(('hello', 'world', '--')) + assert cmd == ('hello', 'world') + assert filenames == () + + +def test_cmd_filenames_some_filenames(): + cmd, filenames = _cmd_filenames(('hello', 'world', '--', 'f1', 'f2')) + assert cmd == ('hello', 'world') + assert filenames == ('f1', 'f2') + + +def test_cmd_filenames_multiple_dashdash(): + cmd, filenames = _cmd_filenames(('hello', '--', 'arg', '--', 'f1', 'f2')) + assert cmd == ('hello', '--', 'arg') + assert filenames == ('f1', 'f2') + + +def test_cd_unexpected_filename(): + with pytest.raises(SystemExit) as excinfo: + main(('cd', 'subdir', 'cmd', '--', 'subdir/1', 'not-subdir/2')) + msg, = excinfo.value.args + assert msg == "unexpected file without prefix='subdir/': not-subdir/2" + + +def _norm(out): + return out.replace('\r\n', '\n') + + +def test_cd(tmp_path, capfd): + subdir = tmp_path.joinpath('subdir') + subdir.mkdir() + subdir.joinpath('a').write_text('a') + subdir.joinpath('b').write_text('b') + + with cwd(tmp_path): + ret = main(( + 'cd', 'subdir', + sys.executable, '-c', + 'import os; print(os.getcwd());' + 'import sys; [print(open(f).read()) for f in sys.argv[1:]]', + '--', + 'subdir/a', 'subdir/b', + )) + + assert ret == 0 + out, err = capfd.readouterr() + assert _norm(out) == f'{subdir}\na\nb\n' + assert err == '' + + +def test_ignore_exit_code(capfd): + ret = main(( + 'ignore-exit-code', sys.executable, '-c', 'raise SystemExit("bye")', + )) + assert ret == 0 + out, err = capfd.readouterr() + assert out == '' + assert _norm(err) == 'bye\n' + + +def test_n1(capfd): + ret = main(( + 'n1', sys.executable, '-c', 'import sys; print(sys.argv[1:])', + '--', + 'foo', 'bar', 'baz', + )) + assert ret == 0 + out, err = capfd.readouterr() + assert _norm(out) == "['foo']\n['bar']\n['baz']\n" + assert err == '' + + +def test_n1_some_error_code(): + ret = main(( + 'n1', sys.executable, '-c', + 'import sys; raise SystemExit(sys.argv[1] == "error")', + '--', + 'ok', 'error', 'ok', + )) + assert ret == 1 diff --git a/tests/lang_base_test.py b/tests/lang_base_test.py index da289aef8..9fac83da2 100644 --- a/tests/lang_base_test.py +++ b/tests/lang_base_test.py @@ -164,3 +164,15 @@ def test_basic_run_hook(tmp_path): assert ret == 0 out = out.replace(b'\r\n', b'\n') assert out == b'hi hello file file file\n' + + +def test_hook_cmd(): + assert lang_base.hook_cmd('echo hi', ()) == ('echo', 'hi') + + +def test_hook_cmd_hazmat(): + ret = lang_base.hook_cmd('pre-commit hazmat cd a echo -- b', ()) + assert ret == ( + sys.executable, '-m', 'pre_commit.commands.hazmat', + 'cd', 'a', 'echo', '--', 'b', + ) diff --git a/tests/main_test.py b/tests/main_test.py index 945349fa4..eb9ea18d8 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -8,6 +8,7 @@ import pre_commit.constants as C from pre_commit import main +from pre_commit.commands import hazmat from pre_commit.errors import FatalError from pre_commit.util import cmd_output from testing.auto_namedtuple import auto_namedtuple @@ -158,6 +159,17 @@ def test_all_cmds(command, mock_commands, mock_store_dir): assert_only_one_mock_called(mock_commands) +def test_hazmat(mock_store_dir): + with mock.patch.object(hazmat, 'impl') as mck: + main.main(('hazmat', 'cd', 'subdir', '--', 'cmd', '--', 'f1', 'f2')) + assert mck.call_count == 1 + (arg,), dct = mck.call_args + assert dct == {} + assert arg.tool == 'cd' + assert arg.subdir == 'subdir' + assert arg.cmd == ['cmd', '--', 'f1', 'f2'] + + def test_try_repo(mock_store_dir): with mock.patch.object(main, 'try_repo') as patch: main.main(('try-repo', '.')) diff --git a/tests/repository_test.py b/tests/repository_test.py index b1c7a0024..5d71c3e4c 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -506,3 +506,14 @@ def test_args_with_spaces_and_quotes(tmp_path): expected = b"['i have spaces', 'and\"\\'quotes', '$and !this']\n" assert ret == (0, expected) + + +def test_hazmat(tmp_path): + ret = run_language( + tmp_path, unsupported, + f'pre-commit hazmat ignore-exit-code {shlex.quote(sys.executable)} ' + f"-c 'import sys; raise SystemExit(sys.argv[1:])'", + ('f1', 'f2'), + ) + expected = b"['f1', 'f2']\n" + assert ret == (0, expected) From 1af6c8fa9502336c6977c2ff3e79185bd97a6e57 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 22 Nov 2025 16:02:16 -0500 Subject: [PATCH 333/342] v4.5.0 --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b27af5e7e..1434728d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +4.5.0 - 2025-11-22 +================== + +### Features +- Add `pre-commit hazmat`. + - #3585 PR by @asottile. + 4.4.0 - 2025-11-08 ================== diff --git a/setup.cfg b/setup.cfg index be031c3ef..00c71759a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.4.0 +version = 4.5.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 8ea2b790d817088444b2328ff6cfe6742260070f Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Sat, 22 Nov 2025 16:06:27 -0500 Subject: [PATCH 334/342] remove sha256 file from zipapp script github displays the checksum for us now! --- testing/zipapp/make | 3 --- 1 file changed, 3 deletions(-) diff --git a/testing/zipapp/make b/testing/zipapp/make index 165046f66..43bb4373f 100755 --- a/testing/zipapp/make +++ b/testing/zipapp/make @@ -107,9 +107,6 @@ def main() -> int: shebang = '/usr/bin/env python3' zipapp.create_archive(tmpdir, filename, interpreter=shebang) - with open(f'{filename}.sha256sum', 'w') as f: - subprocess.check_call(('sha256sum', filename), stdout=f) - return 0 From 465192d7de58d569776eaaa818c94cb2b962d436 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:53:38 +0000 Subject: [PATCH 335/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.21.1 → v3.21.2](https://github.com/asottile/pyupgrade/compare/v3.21.1...v3.21.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e47d56ca3..50893030f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.21.1 + rev: v3.21.2 hooks: - id: pyupgrade args: [--py310-plus] From 48953556d06f8cdb4248002c1a0044e69e0916b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:05:15 +0000 Subject: [PATCH 336/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.18.2 → v1.19.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.18.2...v1.19.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 50893030f..cedeae5e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.18.2 + rev: v1.19.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From c251e6b6d011b3b262339dc8e109de29b0ff8db1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:48:45 +0000 Subject: [PATCH 337/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.19.0 → v1.19.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.0...v1.19.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cedeae5e8..83ff03f3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.0 + rev: v1.19.1 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 51592eececd13b99c40ec477ad8f810799147227 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 16 Dec 2025 15:45:01 -0500 Subject: [PATCH 338/342] fix python local template when artifact dirs are present --- pre_commit/resources/empty_template_setup.py | 2 +- tests/languages/python_test.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pre_commit/resources/empty_template_setup.py b/pre_commit/resources/empty_template_setup.py index ef05eef84..e8b1ff02c 100644 --- a/pre_commit/resources/empty_template_setup.py +++ b/pre_commit/resources/empty_template_setup.py @@ -1,4 +1,4 @@ from setuptools import setup -setup(name='pre-commit-placeholder-package', version='0.0.0') +setup(name='pre-commit-placeholder-package', version='0.0.0', py_modules=[]) diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index 565525a40..593634b79 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -10,6 +10,8 @@ from pre_commit.envcontext import envcontext from pre_commit.languages import python from pre_commit.prefix import Prefix +from pre_commit.store import _make_local_repo +from pre_commit.util import cmd_output_b from pre_commit.util import make_executable from pre_commit.util import win_exe from testing.auto_namedtuple import auto_namedtuple @@ -351,3 +353,15 @@ def test_python_hook_weird_setup_cfg(tmp_path): ret = run_language(tmp_path, python, 'socks', [os.devnull]) assert ret == (0, f'[{os.devnull!r}]\nhello hello\n'.encode()) + + +def test_local_repo_with_other_artifacts(tmp_path): + cmd_output_b('git', 'init', tmp_path) + _make_local_repo(str(tmp_path)) + # pretend a rust install also ran here + tmp_path.joinpath('target').mkdir() + + ret, out = run_language(tmp_path, python, 'python --version') + + assert ret == 0 + assert out.startswith(b'Python ') From 8a0630ca1aa7f6d5665effe674ebe2022af17919 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 16 Dec 2025 16:13:56 -0500 Subject: [PATCH 339/342] v4.5.1 --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1434728d0..879ae0731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +4.5.1 - 2025-12-16 +================== + +### Fixes +- Fix `language: python` with `repo: local` without `additional_dependencies`. + - #3597 PR by @asottile. + 4.5.0 - 2025-11-22 ================== diff --git a/setup.cfg b/setup.cfg index 00c71759a..a95ee4473 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.5.0 +version = 4.5.1 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown From 37a879e65ee00d8375d12f053ef76e0024a0ed55 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 20:26:26 +0000 Subject: [PATCH 340/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/setup-cfg-fmt: v3.1.0 → v3.2.0](https://github.com/asottile/setup-cfg-fmt/compare/v3.1.0...v3.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 83ff03f3d..3654066f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v3.1.0 + rev: v3.2.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports From 8603fe3079c1f0ae9115038d6ea713f06bb8aad6 Mon Sep 17 00:00:00 2001 From: Max R Date: Mon, 23 Mar 2026 10:05:04 -0400 Subject: [PATCH 341/342] py310+ --- .github/actions/pre-test/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml index b70c942fe..f5272c847 100644 --- a/.github/actions/pre-test/action.yml +++ b/.github/actions/pre-test/action.yml @@ -6,4 +6,4 @@ runs: using: composite steps: - uses: asottile/workflows/.github/actions/latest-git@v1.4.0 - if: inputs.env == 'py39' && runner.os == 'Linux' + if: inputs.env == 'py310' && runner.os == 'Linux' From a5d91142676630f8130020b35e166e0c0e92b8f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:10:05 +0000 Subject: [PATCH 342/342] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.19.1 → v1.20.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.1...v1.20.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3654066f4..7e0e1e028 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 + rev: v1.20.0 hooks: - id: mypy additional_dependencies: [types-pyyaml]