From 83e05e607e6b8cfde97c05e067d156be09a298a9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 25 Jan 2023 14:03:39 -0500 Subject: [PATCH 001/172] 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 002/172] 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 003/172] 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 004/172] 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 005/172] 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 006/172] 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 007/172] 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 008/172] 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 009/172] 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 010/172] 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 011/172] 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 012/172] 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 013/172] 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 014/172] 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 015/172] 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 016/172] 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 017/172] 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 018/172] 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 019/172] 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 020/172] 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 021/172] 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 022/172] 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 023/172] 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 024/172] 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 025/172] 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 026/172] 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 027/172] [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 028/172] 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 029/172] 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 030/172] 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 031/172] 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 032/172] [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 033/172] 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 034/172] 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 035/172] 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 036/172] 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 037/172] 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 038/172] 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 039/172] 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 040/172] 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 041/172] 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 042/172] 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 043/172] 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 044/172] 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 045/172] [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 046/172] 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 047/172] 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 048/172] 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 049/172] 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 050/172] 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 051/172] [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 052/172] 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 053/172] 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 054/172] 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 055/172] 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 056/172] 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 057/172] 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 058/172] [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 059/172] 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 060/172] [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 061/172] 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 062/172] 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 063/172] 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 064/172] 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 065/172] 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 066/172] 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 067/172] 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 068/172] [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 069/172] 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 070/172] 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 071/172] 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 072/172] 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 073/172] [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 074/172] 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 075/172] 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 076/172] 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 077/172] 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 078/172] [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 079/172] [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 080/172] [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 081/172] 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 082/172] [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 083/172] 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 084/172] [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 085/172] [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 086/172] 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 087/172] 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 088/172] [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 089/172] 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 090/172] `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 091/172] 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 092/172] [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 093/172] 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 094/172] 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 095/172] [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 096/172] 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 097/172] [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 098/172] [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 099/172] 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 100/172] 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 101/172] [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 102/172] 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 103/172] 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 104/172] 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 105/172] [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 106/172] 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 107/172] [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 108/172] [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 109/172] 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 110/172] [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 111/172] 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 112/172] 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 113/172] [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 114/172] [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 115/172] 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 116/172] 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 117/172] [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 118/172] [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 119/172] 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 120/172] 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 121/172] 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 122/172] 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 123/172] [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 124/172] [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 125/172] 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 126/172] [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 127/172] 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 128/172] 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 129/172] 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 130/172] 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 131/172] [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 132/172] 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 133/172] [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 134/172] [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 135/172] 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 136/172] 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 137/172] [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 138/172] [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 139/172] 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 140/172] 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 141/172] [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 142/172] 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 143/172] 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 144/172] [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 145/172] [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 146/172] [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 147/172] [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 148/172] [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 149/172] [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 150/172] [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 151/172] 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 152/172] 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 153/172] 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 154/172] [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 155/172] [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 156/172] [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 157/172] [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 158/172] 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 159/172] 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 160/172] 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 161/172] 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 162/172] 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` 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%0AtVc+vZ{$*a=M$ni`+p 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 Date: Mon, 30 Sep 2024 18:09:04 -0400 Subject: [PATCH 163/172] 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 164/172] 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 165/172] 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 166/172] 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 167/172] 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 168/172] 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 169/172] 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 170/172] [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 171/172] 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 172/172] 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