From 9cd27eb99d759b87344072feb469eb3e3ec39f78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:08:53 +0000 Subject: [PATCH 01/11] [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.20.1 → v1.20.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.20.1...v1.20.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 7ea4a444f..8b5347dfc 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.20.1 + rev: v1.20.2 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 998bb5f01d8c228119bd7f96718e340ab7959ab6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 20:24:39 +0000 Subject: [PATCH 02/11] [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.20.2 → v2.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.20.2...v2.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 8b5347dfc..dcbe195e8 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.20.2 + rev: v2.0.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From 422708f47d44ebe6b24b9033d43c33138ebf2454 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Tue, 12 May 2026 14:33:48 -0400 Subject: [PATCH 03/11] bash on windows-latest now reports cygwin --- testing/get-coursier.sh | 2 +- testing/get-dart.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/get-coursier.sh b/testing/get-coursier.sh index 958e73b24..0087feb82 100755 --- a/testing/get-coursier.sh +++ b/testing/get-coursier.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -if [ "$OSTYPE" = msys ]; then +if [ "$OSTYPE" = cygwin ]; then URL='https://github.com/coursier/coursier/releases/download/v2.1.0-RC4/cs-x86_64-pc-win32.zip' SHA256='0d07386ff0f337e3e6264f7dde29d137dda6eaa2385f29741435e0b93ccdb49d' TARGET='/tmp/coursier/cs.zip' diff --git a/testing/get-dart.sh b/testing/get-dart.sh index b4545e71e..ad7f8608a 100755 --- a/testing/get-dart.sh +++ b/testing/get-dart.sh @@ -3,7 +3,7 @@ set -euo pipefail VERSION=2.19.6 -if [ "$OSTYPE" = msys ]; then +if [ "$OSTYPE" = cygwin ]; then URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip" cygpath -w /tmp/dart-sdk/bin >> "$GITHUB_PATH" else From 3a8d054841aba7376deeffc3463b584945b7f1a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:13:32 +0000 Subject: [PATCH 04/11] [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: v2.0.0 → v2.1.0](https://github.com/pre-commit/mirrors-mypy/compare/v2.0.0...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 dcbe195e8..9466b7bc0 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: v2.0.0 + rev: v2.1.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From fae89cec6325c8b41717c4ad76a32e400b72f4f1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:23:06 +0000 Subject: [PATCH 05/11] [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.16.0 → v3.17.0](https://github.com/asottile/reorder-python-imports/compare/v3.16.0...v3.17.0) - [github.com/pre-commit/mirrors-mypy: v2.1.0 → v2.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v2.1.0...v2.3.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 9466b7bc0..a7dc806ca 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.16.0 + rev: v3.17.0 hooks: - id: reorder-python-imports exclude: ^pre_commit/resources/ @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.1.0 + rev: v2.3.0 hooks: - id: mypy additional_dependencies: [types-pyyaml] From b1cc920ebb5ac01a86a32149dfa0644e496b44ec Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Wed, 15 Jul 2026 16:28:08 -0400 Subject: [PATCH 06/11] install via git for `language: node` this avoids trying to play catch-up with whatever preparation commands npm does when installing from `npm i git+...` (which differs from `npm i a/dir`) the latter does not run any preparation commands leading to broken installs the previous command we were using was suggested by the npm devs, however some of the options that it was using are no longer part of npm itself and npm 12.x errors on unknown options --- pre_commit/languages/node.py | 33 ++++++++----------- .../resources/empty_template_.npmignore | 1 - .../resources/empty_template_package.json | 4 --- pre_commit/store.py | 4 +-- tests/languages/node_test.py | 12 +++++++ 5 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 pre_commit/resources/empty_template_.npmignore delete mode 100644 pre_commit/resources/empty_template_package.json diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index af7dc6f87..304fdcdd8 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -17,7 +17,6 @@ from pre_commit.prefix import Prefix from pre_commit.util import cmd_output from pre_commit.util import cmd_output_b -from pre_commit.util import rmtree ENVIRONMENT_DIR = 'node_env' run_hook = lang_base.basic_run_hook @@ -77,7 +76,18 @@ def health_check(prefix: Prefix, version: str) -> str | None: def install_environment( prefix: Prefix, version: str, additional_dependencies: Sequence[str], ) -> None: - assert prefix.exists('package.json') + if prefix.exists('.git') and prefix.exists('package.json'): + # this requires a new-enough npm (2019ish?) + pkgs = (f'git+file://{prefix.prefix_dir}', *additional_dependencies) + else: + pkgs = (*additional_dependencies,) + + if not pkgs: + raise AssertionError( + '`language: node` must have package.json or ' + 'additional_dependencies', + ) + 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 @@ -89,22 +99,5 @@ def install_environment( cmd_output_b(*cmd) with in_env(prefix, version): - # https://npm.community/t/npm-install-g-git-vs-git-clone-cd-npm-install-g/5449 - # install as if we installed from git - - local_install_cmd = ( - 'npm', 'install', '--include=dev', '--include=prod', - '--ignore-prepublish', '--no-progress', '--no-save', - ) - 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) + install = ('npm', 'install', '--allow-git=root', '-g', *pkgs) lang_base.setup_cmd(prefix, install) - - # clean these up after installation - if prefix.exists('node_modules'): # pragma: win32 no cover - rmtree(prefix.path('node_modules')) - os.remove(pkg) diff --git a/pre_commit/resources/empty_template_.npmignore b/pre_commit/resources/empty_template_.npmignore deleted file mode 100644 index 72e8ffc0d..000000000 --- a/pre_commit/resources/empty_template_.npmignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/pre_commit/resources/empty_template_package.json b/pre_commit/resources/empty_template_package.json deleted file mode 100644 index 042e9583c..000000000 --- a/pre_commit/resources/empty_template_package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "pre_commit_placeholder_package", - "version": "0.0.0" -} diff --git a/pre_commit/store.py b/pre_commit/store.py index dc90c0519..8bd1e1955 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -37,8 +37,8 @@ def _get_default_directory() -> str: _LOCAL_RESOURCES = ( - 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', - 'package.json', 'pre-commit-package-dev-1.rockspec', + 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', + 'pre-commit-package-dev-1.rockspec', 'pre_commit_placeholder_package.gemspec', 'setup.py', 'environment.yml', 'Makefile.PL', 'pubspec.yaml', 'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv', diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py index 055cb1e92..0fb84a916 100644 --- a/tests/languages/node_test.py +++ b/tests/languages/node_test.py @@ -15,7 +15,9 @@ from pre_commit.prefix import Prefix from pre_commit.store import _make_local_repo from pre_commit.util import cmd_output +from pre_commit.util import cmd_output_b from testing.language_helpers import run_language +from testing.util import git_commit from testing.util import xfailif_windows @@ -40,6 +42,12 @@ def find_exe_mck(): yield mck +def _make_repo(r): + cmd_output_b('git', 'init', r) + cmd_output_b('git', 'add', '.', cwd=r) + git_commit(cwd=r) + + @pytest.mark.usefixtures('is_linux') def test_sets_system_when_node_and_npm_are_available(find_exe_mck): find_exe_mck.return_value = '/path/to/exe' @@ -61,6 +69,7 @@ def test_sets_default_on_windows(find_exe_mck): @xfailif_windows # pragma: win32 no cover def test_healthy_system_node(tmpdir): tmpdir.join('package.json').write('{"name": "t", "version": "1.0.0"}') + _make_repo(str(tmpdir)) prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) @@ -75,6 +84,7 @@ def test_unhealthy_if_system_node_goes_missing(tmpdir): prefix_dir = tmpdir.join('prefix').ensure_dir() prefix_dir.join('package.json').write('{"name": "t", "version": "1.0.0"}') + _make_repo(str(prefix_dir)) path = ('PATH', (str(bin_dir), os.pathsep, envcontext.Var('PATH'))) with envcontext.envcontext((path,)): @@ -101,6 +111,7 @@ def test_installs_without_links_outside_env(tmpdir): 'dependencies': {'lodash': '*'}, }), ) + _make_repo(str(tmpdir)) prefix = Prefix(str(tmpdir)) node.install_environment(prefix, 'system', ()) @@ -124,6 +135,7 @@ def _make_hello_world(tmp_path): '#!/usr/bin/env node\n' 'console.log("Hello World");\n', ) + _make_repo(str(tmp_path)) def test_node_hook_system(tmp_path): From 1e7994fb1fddd31fb1998aa98f84fc43818d9485 Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Mon, 15 Jun 2026 19:03:11 -0500 Subject: [PATCH 07/11] set JULIA_DEPOT_PATH --- pre_commit/languages/julia.py | 7 +++++++ tests/languages/julia_test.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pre_commit/languages/julia.py b/pre_commit/languages/julia.py index 7559b5ba6..5122c3fc2 100644 --- a/pre_commit/languages/julia.py +++ b/pre_commit/languages/julia.py @@ -51,6 +51,13 @@ def get_env_patch(target_dir: str, version: str) -> PatchesT: ('JULIA_LOAD_PATH', target_dir), # May be set, remove it to not interfer with LOAD_PATH ('JULIA_PROJECT', UNSET), + # Keep the package depot inside the hook environment so installed + # packages and precompile caches persist with the env instead of + # leaking into a shared depot. The trailing separator leaves an empty + # entry, which julia expands to its default depots. This keeps the + # bundled stdlib resources (and their precompile caches) available so + # hooks don't recompile from scratch on first run. + ('JULIA_DEPOT_PATH', os.path.join(target_dir, 'depot') + os.pathsep), ) diff --git a/tests/languages/julia_test.py b/tests/languages/julia_test.py index 175622d65..aa7098cfb 100644 --- a/tests/languages/julia_test.py +++ b/tests/languages/julia_test.py @@ -3,7 +3,9 @@ import os from unittest import mock +from pre_commit import constants as C from pre_commit.languages import julia +from pre_commit.prefix import Prefix from testing.language_helpers import run_language from testing.util import cwd @@ -42,6 +44,19 @@ def test_julia_hook_with_startup(tmp_path): test_julia_hook(tmp_path) +def test_julia_hook_installs_into_env_local_depot(tmp_path): + code = """ + using Example + println("Hello, world!") + """ + _make_hook(tmp_path, code) + + julia.install_environment(Prefix(str(tmp_path)), C.DEFAULT, ()) + + d = tmp_path.joinpath('juliaenv-default', 'depot', 'packages', 'Example') + assert d.is_dir() + + def test_julia_hook_manifest(tmp_path): code = """ using Example From 1d811d9b0b655fa648001ca479a0b7da9ebd7b8c Mon Sep 17 00:00:00 2001 From: Max R Date: Thu, 28 May 2026 18:14:31 -0400 Subject: [PATCH 08/11] Return an error for invalid --repo --- pre_commit/commands/autoupdate.py | 7 +++++++ tests/commands/autoupdate_test.py | 12 +++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index aa0c5e25e..f07e54b80 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -175,6 +175,13 @@ def autoupdate( repo for repo in load_config(config_file)['repos'] if repo['repo'] not in {LOCAL, META} ] + missing_repos = set(repos) - {r['repo'] for r in config_repos} + if missing_repos: + output.write_line( + f'repos ({", ".join(sorted(missing_repos))}) were ' + f'not found in {config_file}', + ) + return 1 rev_infos: list[RevInfo | None] = [None] * len(config_repos) jobs = jobs or xargs.cpu_count() # 0 => number of cpus diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py index 71bd04446..cbf22f393 100644 --- a/tests/commands/autoupdate_test.py +++ b/tests/commands/autoupdate_test.py @@ -260,8 +260,8 @@ def test_autoupdate_out_of_date_repo_with_correct_repo_name( assert 'local' in after -def test_autoupdate_out_of_date_repo_with_wrong_repo_name( - out_of_date, in_tmpdir, +def test_autoupdate_missing_repo_name( + out_of_date, in_tmpdir, cap_out, ): config = make_config_from_repo( out_of_date.path, rev=out_of_date.original_rev, check=False, @@ -270,15 +270,17 @@ def test_autoupdate_out_of_date_repo_with_wrong_repo_name( with open(C.CONFIG_FILE) as f: before = f.read() - # It will not update it, because the name doesn't match ret = autoupdate( C.CONFIG_FILE, freeze=False, tags_only=False, - repos=('dne',), + repos=('dne', 'foo'), ) with open(C.CONFIG_FILE) as f: after = f.read() - assert ret == 0 + assert ret == 1 assert before == after + assert cap_out.get() == ( + f"repos (dne, foo) were not found in {C.CONFIG_FILE}\n" + ) def test_does_not_reformat(tmpdir, out_of_date): From 2e01c999b2466ac7e14e1e4a57a04504bd6c1b28 Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Tue, 21 Jul 2026 16:26:47 -0400 Subject: [PATCH 09/11] faster check of rev existing locally as a commit --- pre_commit/commands/hook_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 7b806f3b8..0ec303564 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -114,7 +114,7 @@ def _ns( def _rev_exists(rev: str) -> bool: - return not subprocess.call(('git', 'rev-list', '--quiet', rev)) + return not subprocess.call(('git', 'cat-file', '-e', f'{rev}^{{commit}}')) def _pre_push_ns( From 8a1c47a2528419e41aadfcf8135926d68f7ffbbb Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Tue, 21 Jul 2026 16:34:57 -0400 Subject: [PATCH 10/11] avoid duplicate files in --all-files during conflict --- pre_commit/git.py | 2 +- tests/git_test.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pre_commit/git.py b/pre_commit/git.py index ec1928f37..3d3f58016 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -152,7 +152,7 @@ def intent_to_add_files() -> list[str]: def get_all_files() -> list[str]: - return zsplit(cmd_output('git', 'ls-files', '-z')[1]) + return zsplit(cmd_output('git', 'ls-files', '-z', '--deduplicate')[1]) def get_changed_files(old: str, new: str) -> list[str]: diff --git a/tests/git_test.py b/tests/git_test.py index 02b6ce3ae..763aa0de5 100644 --- a/tests/git_test.py +++ b/tests/git_test.py @@ -225,6 +225,17 @@ def test_all_files_non_ascii(non_ascii_repo): assert ret == ['интервью'] +def test_all_files_merge_conflict(in_merge_conflict): + ret = git.get_all_files() + assert sorted(ret) == sorted([ + '.pre-commit-config.yaml', + 'bar_only_file', + 'conflict_file', + 'foo_only_file', + 'placeholder', + ]) + + def test_staged_files_non_ascii(non_ascii_repo): non_ascii_repo.join('интервью').write('hi') cmd_output('git', 'add', '.') From 242ce8a25657be59f2770b50de41fe0fd508820d Mon Sep 17 00:00:00 2001 From: anthony sottile Date: Tue, 21 Jul 2026 16:56:00 -0400 Subject: [PATCH 11/11] v4.6.1 --- CHANGELOG.md | 21 +++++++++++++++++++++ setup.cfg | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0620a8ae0..c31f6dbd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +4.6.1 - 2026-07-21 +================== + +### Fixes +- Install `language: node` hooks via `git`. + - Fixes npm 12.x compatibility + - #3719 PR by @asottile. + - #3517 issue by @ojob. +- Set `JULIA_DEPOT_PATH` for `language: julia`. + - #3711 PR by @damonbayer. + - pre-commit-ci/runner-image#335 issue by @damonbayer. +- Produce error on mistyped `--repo` for `pre-commit autoupdate`. + - #3701 PR by @mxr. + - #3695 issue by @mxr. +- Improve performance of commit existence check in `pre-push`. + - #3726 PR by @asottile. + - #3604 issue by @ptarjan. +- Avoid duplicating conflicted filenames during `pre-commit run --all-files`. + - #3727 PR by @asottile. + - #3706 issue by @RomanValov. + 4.6.0 - 2026-04-21 ================== diff --git a/setup.cfg b/setup.cfg index 072fbbb0f..5d4ef5b2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.6.0 +version = 4.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