summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-02-27 10:32:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-02-27 10:32:22 +0000
commitd7784aa0c412c80cfcb33a29fd1b2ea69dbe8ab8 (patch)
treef7d340787b36afcb3b78f2a0875c2c9c8419cdec
parentReleasing debian version 3.0.4-1. (diff)
downloadpre-commit-d7784aa0c412c80cfcb33a29fd1b2ea69dbe8ab8.tar.xz
pre-commit-d7784aa0c412c80cfcb33a29fd1b2ea69dbe8ab8.zip
Merging upstream version 3.1.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/ISSUE_TEMPLATE/00_bug.yaml (renamed from .github/ISSUE_TEMPLATE/bug.yaml)6
-rw-r--r--.github/ISSUE_TEMPLATE/01_feature.yaml38
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml8
-rw-r--r--.github/actions/pre-test/action.yml31
-rw-r--r--.github/workflows/languages.yaml82
-rw-r--r--.pre-commit-config.yaml2
-rw-r--r--CHANGELOG.md18
-rw-r--r--CONTRIBUTING.md6
-rw-r--r--pre_commit/all_languages.py48
-rw-r--r--pre_commit/clientlib.py8
-rw-r--r--pre_commit/commands/migrate_config.py9
-rw-r--r--pre_commit/commands/run.py2
-rw-r--r--pre_commit/lang_base.py (renamed from pre_commit/languages/helpers.py)40
-rw-r--r--pre_commit/languages/all.py99
-rw-r--r--pre_commit/languages/conda.py14
-rw-r--r--pre_commit/languages/coursier.py18
-rw-r--r--pre_commit/languages/dart.py20
-rw-r--r--pre_commit/languages/docker.py23
-rw-r--r--pre_commit/languages/docker_image.py14
-rw-r--r--pre_commit/languages/dotnet.py28
-rw-r--r--pre_commit/languages/fail.py10
-rw-r--r--pre_commit/languages/golang.py16
-rw-r--r--pre_commit/languages/lua.py18
-rw-r--r--pre_commit/languages/node.py18
-rw-r--r--pre_commit/languages/perl.py14
-rw-r--r--pre_commit/languages/pygrep.py10
-rw-r--r--pre_commit/languages/python.py14
-rw-r--r--pre_commit/languages/r.py12
-rw-r--r--pre_commit/languages/ruby.py33
-rw-r--r--pre_commit/languages/rust.py12
-rw-r--r--pre_commit/languages/script.py14
-rw-r--r--pre_commit/languages/swift.py16
-rw-r--r--pre_commit/languages/system.py12
-rw-r--r--pre_commit/repository.py15
-rw-r--r--pre_commit/staged_files_only.py23
-rw-r--r--pre_commit/store.py2
-rw-r--r--pre_commit/util.py7
-rw-r--r--setup.cfg2
-rw-r--r--testing/language_helpers.py15
-rwxr-xr-xtesting/languages79
-rw-r--r--testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml17
-rw-r--r--testing/resources/docker_hooks_repo/Dockerfile3
-rw-r--r--testing/resources/docker_image_hooks_repo/.pre-commit-hooks.yaml8
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/.pre-commit-hooks.yaml12
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/dotnet_hooks_combo_repo.sln28
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/proj1/Program.cs12
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj12
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/proj2/Program.cs12
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj12
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore3
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml5
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs12
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj9
-rw-r--r--testing/resources/dotnet_hooks_csproj_repo/.gitignore3
-rw-r--r--testing/resources/dotnet_hooks_csproj_repo/.pre-commit-hooks.yaml5
-rw-r--r--testing/resources/dotnet_hooks_csproj_repo/Program.cs12
-rw-r--r--testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj9
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/.gitignore3
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/.pre-commit-hooks.yaml5
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/Program.cs12
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj9
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.sln34
-rw-r--r--testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml5
-rw-r--r--testing/resources/golang_hooks_repo/go.mod5
-rw-r--r--testing/resources/golang_hooks_repo/go.sum2
-rw-r--r--testing/resources/golang_hooks_repo/golang-hello-world/main.go23
-rw-r--r--testing/resources/python_venv_hooks_repo/.pre-commit-hooks.yaml5
-rw-r--r--testing/resources/python_venv_hooks_repo/foo.py9
-rw-r--r--testing/resources/python_venv_hooks_repo/setup.py10
-rw-r--r--testing/util.py15
-rw-r--r--tests/all_languages_test.py7
-rw-r--r--tests/commands/migrate_config_test.py33
-rw-r--r--tests/lang_base_test.py (renamed from tests/languages/helpers_test.py)72
-rw-r--r--tests/languages/docker_image_test.py27
-rw-r--r--tests/languages/docker_test.py14
-rw-r--r--tests/languages/dotnet_test.py154
-rw-r--r--tests/languages/fail_test.py14
-rw-r--r--tests/languages/golang_test.py97
-rw-r--r--tests/languages/pygrep_test.py17
-rw-r--r--tests/languages/python_test.py23
-rw-r--r--tests/languages/ruby_test.py4
-rw-r--r--tests/languages/script_test.py14
-rw-r--r--tests/languages/system_test.py9
-rw-r--r--tests/repository_test.py331
-rw-r--r--tests/staged_files_only_test.py50
-rw-r--r--tests/store_test.py26
-rw-r--r--tests/util_test.py2
-rw-r--r--tox.ini4
88 files changed, 1111 insertions, 954 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/00_bug.yaml
index 96cd6c7..980f7af 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 0000000..c7ddc84
--- /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.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..4179f47
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+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
diff --git a/.github/actions/pre-test/action.yml b/.github/actions/pre-test/action.yml
index 42bbf00..9d1eb2d 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 0000000..8bc8e71
--- /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/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b7d7f1f..ad8ffba 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.1
hooks:
- id: mypy
additional_dependencies: [types-all]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0998da9..8a42781 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/CONTRIBUTING.md b/CONTRIBUTING.md
index a9bcb79..ab3a929 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)
diff --git a/pre_commit/all_languages.py b/pre_commit/all_languages.py
new file mode 100644
index 0000000..2bed706
--- /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 e191d3a..9ff38c6 100644
--- a/pre_commit/clientlib.py
+++ b/pre_commit/clientlib.py
@@ -12,8 +12,8 @@ import cfgv
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 @@ MANIFEST_HOOK_DICT = cfgv.Map(
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 @@ CONFIG_REPO_DICT = cfgv.Map(
)
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/migrate_config.py b/pre_commit/commands/migrate_config.py
index 6f7af4e..842fb3a 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/commands/run.py b/pre_commit/commands/run.py
index a7eb4f4..c9bc55b 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -19,9 +19,9 @@ from identify.identify import tags_from_path
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
index d1be409..9480c55 100644
--- a/pre_commit/languages/helpers.py
+++ b/pre_commit/lang_base.py
@@ -7,8 +7,10 @@ import random
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,42 @@ FIXED_RANDOM_SEED = 1542676187
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 +83,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 d952ae1..0000000
--- 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 e2fb019..05f1d29 100644
--- a/pre_commit/languages/conda.py
+++ b/pre_commit/languages/conda.py
@@ -5,19 +5,19 @@ import os
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 6075758..9c5fbfe 100644
--- a/pre_commit/languages/coursier.py
+++ b/pre_commit/languages/coursier.py
@@ -5,19 +5,19 @@ 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.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 e3c1c58..e8539ca 100644
--- a/pre_commit/languages/dart.py
+++ b/pre_commit/languages/dart.py
@@ -7,19 +7,19 @@ import tempfile
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_environment(
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_environment(
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 e80c959..8e53ca9 100644
--- a/pre_commit/languages/docker.py
+++ b/pre_commit/languages/docker.py
@@ -5,16 +5,16 @@ import json
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,12 +135,11 @@ 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))
- cmd = (*docker_cmd(), *entry_tag, *cmd_rest)
- return helpers.run_xargs(
- cmd,
+ return lang_base.run_xargs(
+ (*docker_cmd(), *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 8e5f2c0..26f006e 100644
--- a/pre_commit/languages/docker_image.py
+++ b/pre_commit/languages/docker_image.py
@@ -2,15 +2,15 @@ from __future__ import annotations
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 4c3955e..e9568f2 100644
--- a/pre_commit/languages/dotnet.py
+++ b/pre_commit/languages/dotnet.py
@@ -9,18 +9,18 @@ import zipfile
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,19 +57,19 @@ 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)
- build_dir = 'pre-commit-build'
+ 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',
'--configuration', 'Release',
- '--output', build_dir,
+ '--property', f'PackageOutputPath={build_dir}',
),
)
@@ -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',
@@ -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/pre_commit/languages/fail.py b/pre_commit/languages/fail.py
index 33df067..a8ec6a5 100644
--- a/pre_commit/languages/fail.py
+++ b/pre_commit/languages/fail.py
@@ -2,14 +2,14 @@ from __future__ import annotations
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 3c4b652..bea91e9 100644
--- a/pre_commit/languages/golang.py
+++ b/pre_commit/languages/golang.py
@@ -19,17 +19,17 @@ from typing import Protocol
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 @@ else: # pragma: win32 no cover
@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 ffc40b5..12d0661 100644
--- a/pre_commit/languages/lua.py
+++ b/pre_commit/languages/lua.py
@@ -6,17 +6,17 @@ import sys
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 9688da3..66d6136 100644
--- a/pre_commit/languages/node.py
+++ b/pre_commit/languages/node.py
@@ -8,11 +8,11 @@ from typing import Generator
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 cmd_output_b
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 2530c0e..2a7f162 100644
--- a/pre_commit/languages/perl.py
+++ b/pre_commit/languages/perl.py
@@ -6,16 +6,16 @@ import shlex
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 f0eb9a9..ec55560 100644
--- a/pre_commit/languages/pygrep.py
+++ b/pre_commit/languages/pygrep.py
@@ -7,16 +7,16 @@ from typing import NamedTuple
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 c373646..976674e 100644
--- a/pre_commit/languages/python.py
+++ b/pre_commit/languages/python.py
@@ -8,11 +8,11 @@ from typing import Generator
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 cmd_output_b
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 e238365..138a26e 100644
--- a/pre_commit/languages/r.py
+++ b/pre_commit/languages/r.py
@@ -7,18 +7,18 @@ import shutil
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 4416f72..76631f2 100644
--- a/pre_commit/languages/ruby.py
+++ b/pre_commit/languages/ruby.py
@@ -2,30 +2,35 @@ from __future__ import annotations
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
+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
+
+
+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(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,13 +73,13 @@ 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
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)
@@ -83,7 +88,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 +105,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 +119,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 391fd86..e98e0d0 100644
--- a/pre_commit/languages/rust.py
+++ b/pre_commit/languages/rust.py
@@ -11,19 +11,19 @@ from typing import Generator
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 08325f4..89a3ab2 100644
--- a/pre_commit/languages/script.py
+++ b/pre_commit/languages/script.py
@@ -2,14 +2,14 @@ from __future__ import annotations
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 c66ad5f..8250ab7 100644
--- a/pre_commit/languages/swift.py
+++ b/pre_commit/languages/swift.py
@@ -5,10 +5,10 @@ import os
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_DIR = '.build'
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 204cad7..f6ad688 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 616faf5..040f238 100644
--- a/pre_commit/repository.py
+++ b/pre_commit/repository.py
@@ -3,17 +3,18 @@ from __future__ import annotations
import json
import logging
import os
+import shlex
from typing import Any
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
@@ -32,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:
@@ -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/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py
index 172fb20..8812356 100644
--- a/pre_commit/staged_files_only.py
+++ b/pre_commit/staged_files_only.py
@@ -7,6 +7,7 @@ import time
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/pre_commit/store.py b/pre_commit/store.py
index 6ddc7c4..487e3e7 100644
--- a/pre_commit/store.py
+++ b/pre_commit/store.py
@@ -125,7 +125,7 @@ class Store:
@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/pre_commit/util.py b/pre_commit/util.py
index 8ea4844..ea0d4f5 100644
--- a/pre_commit/util.py
+++ b/pre_commit/util.py
@@ -12,7 +12,6 @@ from types import TracebackType
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)
@@ -67,7 +62,7 @@ class CalledProcessError(RuntimeError):
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/setup.cfg b/setup.cfg
index 56b856c..d1f649f 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
diff --git a/testing/language_helpers.py b/testing/language_helpers.py
index b9c5384..ead8dae 100644
--- a/testing/language_helpers.py
+++ b/testing/language_helpers.py
@@ -3,7 +3,7 @@ from __future__ import annotations
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
@@ -16,13 +16,16 @@ 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()
- 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,
@@ -30,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/testing/languages b/testing/languages
new file mode 100755
index 0000000..5e8fc9e
--- /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/testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/docker_hooks_repo/.pre-commit-hooks.yaml
deleted file mode 100644
index 5295739..0000000
--- 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 0bd1de0..0000000
--- 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 e9fb245..0000000
--- 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/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 f221854..0000000
--- 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 edb0fcb..0000000
--- 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 03876f5..0000000
--- 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 861ced6..0000000
--- a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj
+++ /dev/null
@@ -1,12 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <OutputType>Exe</OutputType>
- <TargetFramework>net6</TargetFramework>
-
- <PackAsTool>true</PackAsTool>
- <ToolCommandName>proj1</ToolCommandName>
- <PackageOutputPath>./nupkg</PackageOutputPath>
- </PropertyGroup>
-
-</Project>
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 47a99a3..0000000
--- 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 dfce2ca..0000000
--- a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj
+++ /dev/null
@@ -1,12 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <OutputType>Exe</OutputType>
- <TargetFramework>net6</TargetFramework>
-
- <PackAsTool>true</PackAsTool>
- <ToolCommandName>proj2</ToolCommandName>
- <PackageOutputPath>./nupkg</PackageOutputPath>
- </PropertyGroup>
-
-</Project>
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 edcd28f..0000000
--- 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 6626627..0000000
--- 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 1456e8e..0000000
--- 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 754b760..0000000
--- a/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
- <PropertyGroup>
- <OutputType>Exe</OutputType>
- <TargetFramework>net7.0</TargetFramework>
- <PackAsTool>true</PackAsTool>
- <ToolCommandName>testeroni.tool</ToolCommandName>
- <PackageOutputPath>./nupkg</PackageOutputPath>
- </PropertyGroup>
-</Project>
diff --git a/testing/resources/dotnet_hooks_csproj_repo/.gitignore b/testing/resources/dotnet_hooks_csproj_repo/.gitignore
deleted file mode 100644
index edcd28f..0000000
--- 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 0f514c1..0000000
--- 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 1456e8e..0000000
--- 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 fa9879b..0000000
--- a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
- <PropertyGroup>
- <OutputType>Exe</OutputType>
- <TargetFramework>net6</TargetFramework>
- <PackAsTool>true</PackAsTool>
- <ToolCommandName>testeroni</ToolCommandName>
- <PackageOutputPath>./nupkg</PackageOutputPath>
- </PropertyGroup>
-</Project>
diff --git a/testing/resources/dotnet_hooks_sln_repo/.gitignore b/testing/resources/dotnet_hooks_sln_repo/.gitignore
deleted file mode 100644
index edcd28f..0000000
--- 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 0f514c1..0000000
--- 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 04ad4e0..0000000
--- 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 a4e2d00..0000000
--- a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
- <PropertyGroup>
- <OutputType>Exe</OutputType>
- <TargetFramework>net6</TargetFramework>
- <PackAsTool>true</PackAsTool>
- <ToolCommandName>testeroni</ToolCommandName>
- <PackageOutputPath>./nupkg</PackageOutputPath>
- </PropertyGroup>
-</Project>
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 87d2afb..0000000
--- 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/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/golang_hooks_repo/.pre-commit-hooks.yaml
deleted file mode 100644
index 206733b..0000000
--- 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 f37d4b6..0000000
--- 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 ec0c385..0000000
--- 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 1685743..0000000
--- 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/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 a666ed8..0000000
--- 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 40efde3..0000000
--- 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 cff6cad..0000000
--- 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/testing/util.py b/testing/util.py
index b6c3804..7c68d0e 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -6,24 +6,13 @@ import subprocess
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/all_languages_test.py b/tests/all_languages_test.py
new file mode 100644
index 0000000..98c9121
--- /dev/null
+++ b/tests/all_languages_test.py
@@ -0,0 +1,7 @@
+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/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py
index fca1ad9..ba18463 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/helpers_test.py b/tests/lang_base_test.py
index c209e7e..a532b6a 100644
--- a/tests/languages/helpers_test.py
+++ b/tests/lang_base_test.py
@@ -8,8 +8,8 @@ from unittest import mock
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 homedir_mck():
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,27 @@ 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_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:
- 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 '
@@ -93,22 +108,30 @@ 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):
- 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,20 +139,35 @@ 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,
)
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'
diff --git a/tests/languages/docker_image_test.py b/tests/languages/docker_image_test.py
new file mode 100644
index 0000000..7993c11
--- /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 5f7c85e..836382a 100644
--- a/tests/languages/docker_test.py
+++ b/tests/languages/docker_test.py
@@ -11,6 +11,8 @@ import pytest
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/languages/dotnet_test.py b/tests/languages/dotnet_test.py
index e69de29..470c03b 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'''\
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net6</TargetFramework>
+ <PackAsTool>true</PackAsTool>
+ <ToolCommandName>{tool_name}</ToolCommandName>
+ <PackageOutputPath>./nupkg</PackageOutputPath>
+ </PropertyGroup>
+</Project>
+'''
+
+
+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/languages/fail_test.py b/tests/languages/fail_test.py
new file mode 100644
index 0000000..7c74886
--- /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/languages/golang_test.py b/tests/languages/golang_test.py
index 7c04255..ec5a878 100644
--- a/tests/languages/golang_test.py
+++ b/tests/languages/golang_test.py
@@ -6,8 +6,11 @@ import pytest
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
ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__
@@ -15,7 +18,7 @@ ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__
@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
@@ -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/languages/pygrep_test.py b/tests/languages/pygrep_test.py
index 8420046..c6271c8 100644
--- a/tests/languages/pygrep_test.py
+++ b/tests/languages/pygrep_test.py
@@ -3,6 +3,7 @@ from __future__ import annotations
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/languages/python_test.py b/tests/languages/python_test.py
index 54fb98f..8bb284e 100644
--- a/tests/languages/python_test.py
+++ b/tests/languages/python_test.py
@@ -12,6 +12,7 @@ from pre_commit.languages import python
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/ruby_test.py b/tests/languages/ruby_test.py
index 9cfaad5..6397a43 100644
--- a/tests/languages/ruby_test.py
+++ b/tests/languages/ruby_test.py
@@ -9,8 +9,8 @@ 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.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'
diff --git a/tests/languages/script_test.py b/tests/languages/script_test.py
new file mode 100644
index 0000000..a02f615
--- /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 0000000..dcd9cf1
--- /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 b43b344..9e5d9d6 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -10,15 +10,12 @@ import pytest
import re_assert
import pre_commit.constants as C
-from pre_commit import git
+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.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
from pre_commit.prefix import Prefix
from pre_commit.repository import _hook_installed
from pre_commit.repository import all_hooks
@@ -28,26 +25,24 @@ 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
-from testing.util import skipif_cant_run_docker
-
-
-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):
@@ -81,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):
@@ -129,66 +124,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_language_versioned_python_hook(tempdir_factory, store):
- # we patch this force virtualenv executing with `-p` since we can't
- # reliably have multiple pythons available in CI
- with mock.patch.object(
- python,
- '_sys_executable_matches',
- return_value=False,
- ):
- _test_hook_repo(
- tempdir_factory, store, 'python3_hooks_repo',
- 'python3-hook',
- [os.devnull],
- f'3\n[{os.devnull!r}]\nHello World\n'.encode(),
- )
-
-
-@skipif_cant_run_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_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.'
)
@@ -199,92 +149,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',
@@ -345,52 +209,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
@@ -440,7 +258,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,
@@ -449,67 +267,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',
- 'hooks': [{
- 'id': 'fail',
- 'name': 'fail',
- 'language': 'fail',
- 'entry': 'make sure to name changelogs as .rst!',
- 'files': r'changelog/.*(?<!\.rst)$',
- }],
- }
- hook = _get_hook(config, store, 'fail')
- ret, out = _hook_run(
- hook, ('changelog/123.bugfix', 'changelog/wat'), color=False,
- )
- assert ret == 1
- assert out == (
- b'make sure to name changelogs as .rst!\n'
- b'\n'
- b'changelog/123.bugfix\n'
- b'changelog/wat\n'
- )
-
-
def test_unknown_keys(store, caplog):
config = {
'repo': 'local',
@@ -553,7 +310,7 @@ def test_control_c_control_c_on_install(tempdir_factory, store):
# 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,
@@ -562,7 +319,7 @@ def test_control_c_control_c_on_install(tempdir_factory, store):
# 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,
@@ -585,7 +342,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,
@@ -667,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):
@@ -781,22 +538,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',
diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py
index a91f315..58dbe5a 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.fixtures import git_dir
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,49 @@ 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$',
+ ).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'
diff --git a/tests/store_test.py b/tests/store_test.py
index c42ce65..eaab940 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():
@@ -246,3 +246,27 @@ def test_mark_config_as_used_readonly(tmpdir):
# 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'))
diff --git a/tests/util_test.py b/tests/util_test.py
index 310f8f5..5b26211 100644
--- a/tests/util_test.py
+++ b/tests/util_test.py
@@ -16,7 +16,7 @@ from pre_commit.util import rmtree
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'
diff --git a/tox.ini b/tox.ini
index a44f93d..602679a 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