1
0
Fork 0

Adding upstream version 3.1.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 21:37:46 +01:00
parent 4066ef5157
commit 897eb1bb2a
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
88 changed files with 1083 additions and 974 deletions

View file

@ -16,6 +16,12 @@ body:
placeholder: ... placeholder: ...
validations: validations:
required: true 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 - type: textarea
id: freeform id: freeform
attributes: attributes:

38
.github/ISSUE_TEMPLATE/01_feature.yaml vendored Normal file
View file

@ -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

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -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

View file

@ -5,36 +5,5 @@ inputs:
runs: runs:
using: composite using: composite
steps: 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 - uses: asottile/workflows/.github/actions/latest-git@v1.4.0
if: inputs.env == 'py38' && runner.os == 'Linux' if: inputs.env == 'py38' && runner.os == 'Linux'

82
.github/workflows/languages.yaml vendored Normal file
View file

@ -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

View file

@ -38,7 +38,7 @@ repos:
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991 rev: v1.0.1
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-all] additional_dependencies: [types-all]

View file

@ -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 3.0.4 - 2023-02-03
================== ==================

View file

@ -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 - 0th class - pre-commit does not require any dependencies for these languages
as they're not actually languages (current examples: fail, pygrep) as they're not actually languages (current examples: fail, pygrep)
- 1st class - pre-commit will bootstrap a full interpreter requiring nothing to - 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 - 2nd class - pre-commit requires the user to install the language globally but
will install tools in an isolated fashion (current examples: python, go, will install tools in an isolated fashion (current examples: python, swift,
swift, docker). docker).
- 3rd class - pre-commit requires the user to install both the tool and the - 3rd class - pre-commit requires the user to install both the tool and the
language globally (current examples: script, system) language globally (current examples: script, system)

View file

@ -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)

View file

@ -12,8 +12,8 @@ import cfgv
from identify.identify import ALL_TAGS from identify.identify import ALL_TAGS
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.all_languages import language_names
from pre_commit.errors import FatalError from pre_commit.errors import FatalError
from pre_commit.languages.all import all_languages
from pre_commit.yaml import yaml_load from pre_commit.yaml import yaml_load
logger = logging.getLogger('pre_commit') logger = logging.getLogger('pre_commit')
@ -49,7 +49,7 @@ MANIFEST_HOOK_DICT = cfgv.Map(
cfgv.Required('id', cfgv.check_string), cfgv.Required('id', cfgv.check_string),
cfgv.Required('name', cfgv.check_string), cfgv.Required('name', cfgv.check_string),
cfgv.Required('entry', 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('alias', cfgv.check_string, ''),
cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('files', check_string_regex, ''),
@ -281,8 +281,8 @@ CONFIG_REPO_DICT = cfgv.Map(
) )
DEFAULT_LANGUAGE_VERSION = cfgv.Map( DEFAULT_LANGUAGE_VERSION = cfgv.Map(
'DefaultLanguageVersion', None, 'DefaultLanguageVersion', None,
cfgv.NoAdditionalKeys(all_languages), cfgv.NoAdditionalKeys(language_names),
*(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in all_languages), *(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in language_names),
) )
CONFIG_SCHEMA = cfgv.Map( CONFIG_SCHEMA = cfgv.Map(
'Config', None, 'Config', None,

View file

@ -42,6 +42,14 @@ def _migrate_sha_to_rev(contents: str) -> str:
return re.sub(r'(\n\s+)sha:', r'\1rev:', contents) 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: def migrate_config(config_file: str, quiet: bool = False) -> int:
with open(config_file) as f: with open(config_file) as f:
orig_contents = contents = f.read() 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_map(contents)
contents = _migrate_sha_to_rev(contents) contents = _migrate_sha_to_rev(contents)
contents = _migrate_python_venv(contents)
if contents != orig_contents: if contents != orig_contents:
with open(config_file, 'w') as f: with open(config_file, 'w') as f:

View file

@ -19,9 +19,9 @@ from identify.identify import tags_from_path
from pre_commit import color from pre_commit import color
from pre_commit import git from pre_commit import git
from pre_commit import output from pre_commit import output
from pre_commit.all_languages import languages
from pre_commit.clientlib import load_config from pre_commit.clientlib import load_config
from pre_commit.hook import Hook 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 all_hooks
from pre_commit.repository import install_hook_envs from pre_commit.repository import install_hook_envs
from pre_commit.staged_files_only import staged_files_only from pre_commit.staged_files_only import staged_files_only

View file

@ -7,8 +7,10 @@ import random
import re import re
import shlex import shlex
from typing import Any from typing import Any
from typing import ContextManager
from typing import Generator from typing import Generator
from typing import NoReturn from typing import NoReturn
from typing import Protocol
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
@ -22,6 +24,42 @@ FIXED_RANDOM_SEED = 1542676187
SHIMS_RE = re.compile(r'[/\\]shims[/\\]') 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: def exe_exists(exe: str) -> bool:
found = parse_shebang.find_executable(exe) found = parse_shebang.find_executable(exe)
if found is None: # exe exists 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) cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs)

View file

@ -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)

View file

@ -5,19 +5,19 @@ import os
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import SubstitutionT from pre_commit.envcontext import SubstitutionT
from pre_commit.envcontext import UNSET from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_b
ENVIRONMENT_DIR = 'conda' ENVIRONMENT_DIR = 'conda'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def get_env_patch(env: str) -> PatchesT: def get_env_patch(env: str) -> PatchesT:
@ -41,7 +41,7 @@ def get_env_patch(env: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -60,11 +60,11 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> None:
helpers.assert_version_default('conda', version) lang_base.assert_version_default('conda', version)
conda_exe = _conda_exe() 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( cmd_output_b(
conda_exe, 'env', 'create', '-p', env_dir, '--file', conda_exe, 'env', 'create', '-p', env_dir, '--file',
'environment.yml', cwd=prefix.prefix_dir, 'environment.yml', cwd=prefix.prefix_dir,

View file

@ -5,19 +5,19 @@ import os.path
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.errors import FatalError from pre_commit.errors import FatalError
from pre_commit.languages import helpers
from pre_commit.parse_shebang import find_executable from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
ENVIRONMENT_DIR = 'coursier' ENVIRONMENT_DIR = 'coursier'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def install_environment( def install_environment(
@ -25,7 +25,7 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> None:
helpers.assert_version_default('coursier', version) lang_base.assert_version_default('coursier', version)
# Support both possible executable names (either "cs" or "coursier") # Support both possible executable names (either "cs" or "coursier")
cs = find_executable('cs') or find_executable('coursier') cs = find_executable('cs') or find_executable('coursier')
@ -35,12 +35,12 @@ def install_environment(
'executables in the application search path', '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: def _install(*opts: str) -> None:
assert cs is not None assert cs is not None
helpers.run_setup_cmd(prefix, (cs, 'fetch', *opts)) lang_base.setup_cmd(prefix, (cs, 'fetch', *opts))
helpers.run_setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts)) lang_base.setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts))
with in_env(prefix, version): with in_env(prefix, version):
channel = prefix.path('.pre-commit-channel') channel = prefix.path('.pre-commit-channel')
@ -71,6 +71,6 @@ def get_env_patch(target_dir: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield

View file

@ -7,19 +7,19 @@ import tempfile
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import win_exe from pre_commit.util import win_exe
from pre_commit.yaml import yaml_load from pre_commit.yaml import yaml_load
ENVIRONMENT_DIR = 'dartenv' ENVIRONMENT_DIR = 'dartenv'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def get_env_patch(venv: str) -> PatchesT: def get_env_patch(venv: str) -> PatchesT:
@ -30,7 +30,7 @@ def get_env_patch(venv: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -40,9 +40,9 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> 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') bin_dir = os.path.join(envdir, 'bin')
def _install_dir(prefix_p: Prefix, pub_cache: str) -> None: 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: with open(prefix_p.path('pubspec.yaml')) as f:
pubspec_contents = yaml_load(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']: for executable in pubspec_contents['executables']:
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix_p, prefix_p,
( (
'dart', 'compile', 'exe', 'dart', 'compile', 'exe',
@ -77,7 +77,7 @@ def install_environment(
else: else:
dep_cmd = (dep,) dep_cmd = (dep,)
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix, prefix,
('dart', 'pub', 'cache', 'add', *dep_cmd), ('dart', 'pub', 'cache', 'add', *dep_cmd),
env={**os.environ, 'PUB_CACHE': dep_tmp}, env={**os.environ, 'PUB_CACHE': dep_tmp},

View file

@ -5,16 +5,16 @@ import json
import os import os
from typing import Sequence 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.prefix import Prefix
from pre_commit.util import CalledProcessError from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_b
ENVIRONMENT_DIR = 'docker' ENVIRONMENT_DIR = 'docker'
PRE_COMMIT_LABEL = 'PRE_COMMIT' PRE_COMMIT_LABEL = 'PRE_COMMIT'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
in_env = helpers.no_env # no special environment for docker in_env = lang_base.no_env # no special environment for docker
def _is_in_docker() -> bool: def _is_in_docker() -> bool:
@ -84,16 +84,16 @@ def build_docker_image(
cmd += ('--pull',) cmd += ('--pull',)
# This must come last for old versions of docker. See #477 # This must come last for old versions of docker. See #477
cmd += ('.',) cmd += ('.',)
helpers.run_setup_cmd(prefix, cmd) lang_base.setup_cmd(prefix, cmd)
def install_environment( def install_environment(
prefix: Prefix, version: str, additional_dependencies: Sequence[str], prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None: # pragma: win32 no cover ) -> None: # pragma: win32 no cover
helpers.assert_version_default('docker', version) lang_base.assert_version_default('docker', version)
helpers.assert_no_additional_deps('docker', additional_dependencies) 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 # Docker doesn't really have relevant disk environment, but pre-commit
# still needs to cleanup its state files on failure # still needs to cleanup its state files on failure
@ -135,12 +135,11 @@ def run_hook(
# automated cleanup of docker images. # automated cleanup of docker images.
build_docker_image(prefix, pull=False) 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)) entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix))
cmd = (*docker_cmd(), *entry_tag, *cmd_rest) return lang_base.run_xargs(
return helpers.run_xargs( (*docker_cmd(), *entry_tag, *cmd_rest),
cmd,
file_args, file_args,
require_serial=require_serial, require_serial=require_serial,
color=color, color=color,

View file

@ -2,15 +2,15 @@ from __future__ import annotations
from typing import Sequence 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.languages.docker import docker_cmd
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
ENVIRONMENT_DIR = None ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
install_environment = helpers.no_install install_environment = lang_base.no_install
in_env = helpers.no_env in_env = lang_base.no_env
def run_hook( def run_hook(
@ -23,8 +23,8 @@ def run_hook(
require_serial: bool, require_serial: bool,
color: bool, color: bool,
) -> tuple[int, bytes]: # pragma: win32 no cover ) -> tuple[int, bytes]: # pragma: win32 no cover
cmd = docker_cmd() + helpers.hook_cmd(entry, args) cmd = docker_cmd() + lang_base.hook_cmd(entry, args)
return helpers.run_xargs( return lang_base.run_xargs(
cmd, cmd,
file_args, file_args,
require_serial=require_serial, require_serial=require_serial,

View file

@ -9,18 +9,18 @@ import zipfile
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
ENVIRONMENT_DIR = 'dotnetenv' ENVIRONMENT_DIR = 'dotnetenv'
BIN_DIR = 'bin' BIN_DIR = 'bin'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def get_env_patch(venv: str) -> PatchesT: def get_env_patch(venv: str) -> PatchesT:
@ -31,7 +31,7 @@ def get_env_patch(venv: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -57,19 +57,19 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> None:
helpers.assert_version_default('dotnet', version) lang_base.assert_version_default('dotnet', version)
helpers.assert_no_additional_deps('dotnet', additional_dependencies) lang_base.assert_no_additional_deps('dotnet', additional_dependencies)
envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
build_dir = 'pre-commit-build' build_dir = prefix.path('pre-commit-build')
# Build & pack nupkg file # Build & pack nupkg file
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix, prefix,
( (
'dotnet', 'pack', 'dotnet', 'pack',
'--configuration', 'Release', '--configuration', 'Release',
'--output', build_dir, '--property', f'PackageOutputPath={build_dir}',
), ),
) )
@ -99,7 +99,7 @@ def install_environment(
# Install to bin dir # Install to bin dir
with _nuget_config_no_sources() as nuget_config: with _nuget_config_no_sources() as nuget_config:
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix, prefix,
( (
'dotnet', 'tool', 'install', 'dotnet', 'tool', 'install',
@ -109,7 +109,3 @@ def install_environment(
tool_id, 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)

View file

@ -2,14 +2,14 @@ from __future__ import annotations
from typing import Sequence 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.prefix import Prefix
ENVIRONMENT_DIR = None ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
install_environment = helpers.no_install install_environment = lang_base.no_install
in_env = helpers.no_env in_env = lang_base.no_env
def run_hook( def run_hook(

View file

@ -19,17 +19,17 @@ from typing import Protocol
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from pre_commit.util import rmtree from pre_commit.util import rmtree
ENVIRONMENT_DIR = 'golangenv' ENVIRONMENT_DIR = 'golangenv'
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
_ARCH_ALIASES = { _ARCH_ALIASES = {
'x86_64': 'amd64', 'x86_64': 'amd64',
@ -60,7 +60,7 @@ else: # pragma: win32 no cover
@functools.lru_cache(maxsize=1) @functools.lru_cache(maxsize=1)
def get_default_version() -> str: def get_default_version() -> str:
if helpers.exe_exists('go'): if lang_base.exe_exists('go'):
return 'system' return 'system'
else: else:
return C.DEFAULT return C.DEFAULT
@ -121,7 +121,7 @@ def _install_go(version: str, dest: str) -> None:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir, version)):
yield yield
@ -131,7 +131,7 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> None:
env_dir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
if version != 'system': if version != 'system':
_install_go(version, env_dir) _install_go(version, env_dir)
@ -149,9 +149,9 @@ def install_environment(
os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'], 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: 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 # save some disk space -- we don't need this after installation
pkgdir = os.path.join(env_dir, 'pkg') pkgdir = os.path.join(env_dir, 'pkg')

View file

@ -6,17 +6,17 @@ import sys
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
ENVIRONMENT_DIR = 'lua_env' ENVIRONMENT_DIR = 'lua_env'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def _get_lua_version() -> str: # pragma: win32 no cover 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 @contextlib.contextmanager # pragma: win32 no cover
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: def in_env(prefix: Prefix, version: str) -> Generator[None, 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -55,9 +55,9 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: # pragma: win32 no cover ) -> 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): with in_env(prefix, version):
# luarocks doesn't bootstrap a tree prior to installing # luarocks doesn't bootstrap a tree prior to installing
# so ensure the directory exists. # 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 # Older luarocks (e.g., 2.4.2) expect the rockspec as an arg
for rockspec in prefix.star('.rockspec'): for rockspec in prefix.star('.rockspec'):
make_cmd = ('luarocks', '--tree', envdir, 'make', 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 # luarocks can't install multiple packages at once
# so install them individually. # so install them individually.
for dependency in additional_dependencies: for dependency in additional_dependencies:
cmd = ('luarocks', '--tree', envdir, 'install', dependency) cmd = ('luarocks', '--tree', envdir, 'install', dependency)
helpers.run_setup_cmd(prefix, cmd) lang_base.setup_cmd(prefix, cmd)

View file

@ -8,11 +8,11 @@ from typing import Generator
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.languages.python import bin_dir from pre_commit.languages.python import bin_dir
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output 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 from pre_commit.util import rmtree
ENVIRONMENT_DIR = 'node_env' ENVIRONMENT_DIR = 'node_env'
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
@functools.lru_cache(maxsize=1) @functools.lru_cache(maxsize=1)
@ -30,7 +30,7 @@ def get_default_version() -> str:
return C.DEFAULT return C.DEFAULT
# if node is already installed, we can save a bunch of setup time by # if node is already installed, we can save a bunch of setup time by
# using the installed version # 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' return 'system'
else: else:
return C.DEFAULT return C.DEFAULT
@ -60,13 +60,13 @@ def get_env_patch(venv: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
def health_check(prefix: Prefix, language_version: str) -> str | None: def health_check(prefix: Prefix, version: str) -> str | None:
with in_env(prefix, language_version): with in_env(prefix, version):
retcode, _, _ = cmd_output_b('node', '--version', check=False) retcode, _, _ = cmd_output_b('node', '--version', check=False)
if retcode != 0: # pragma: win32 no cover if retcode != 0: # pragma: win32 no cover
return f'`node --version` returned {retcode}' return f'`node --version` returned {retcode}'
@ -78,7 +78,7 @@ def install_environment(
prefix: Prefix, version: str, additional_dependencies: Sequence[str], prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None: ) -> None:
assert prefix.exists('package.json') 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 # 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 if sys.platform == 'win32': # pragma: no cover
@ -96,13 +96,13 @@ def install_environment(
'npm', 'install', '--dev', '--prod', 'npm', 'install', '--dev', '--prod',
'--ignore-prepublish', '--no-progress', '--no-save', '--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, _ = cmd_output('npm', 'pack', cwd=prefix.prefix_dir)
pkg = prefix.path(pkg.strip()) pkg = prefix.path(pkg.strip())
install = ('npm', 'install', '-g', pkg, *additional_dependencies) install = ('npm', 'install', '-g', pkg, *additional_dependencies)
helpers.run_setup_cmd(prefix, install) lang_base.setup_cmd(prefix, install)
# clean these up after installation # clean these up after installation
if prefix.exists('node_modules'): # pragma: win32 no cover if prefix.exists('node_modules'): # pragma: win32 no cover

View file

@ -6,16 +6,16 @@ import shlex
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
ENVIRONMENT_DIR = 'perl_env' ENVIRONMENT_DIR = 'perl_env'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def get_env_patch(venv: str) -> PatchesT: def get_env_patch(venv: str) -> PatchesT:
@ -34,7 +34,7 @@ def get_env_patch(venv: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -42,9 +42,9 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
def install_environment( def install_environment(
prefix: Prefix, version: str, additional_dependencies: Sequence[str], prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None: ) -> None:
helpers.assert_version_default('perl', version) lang_base.assert_version_default('perl', version)
with in_env(prefix, version): with in_env(prefix, version):
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix, ('cpan', '-T', '.', *additional_dependencies), prefix, ('cpan', '-T', '.', *additional_dependencies),
) )

View file

@ -7,16 +7,16 @@ from typing import NamedTuple
from typing import Pattern from typing import Pattern
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit import output from pre_commit import output
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.xargs import xargs from pre_commit.xargs import xargs
ENVIRONMENT_DIR = None ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
install_environment = helpers.no_install install_environment = lang_base.no_install
in_env = helpers.no_env in_env = lang_base.no_env
def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int: def _process_filename_by_line(pattern: Pattern[bytes], filename: str) -> int:

View file

@ -8,11 +8,11 @@ from typing import Generator
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.parse_shebang import find_executable from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import CalledProcessError 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 from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'py_env' ENVIRONMENT_DIR = 'py_env'
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
@functools.lru_cache(maxsize=None) @functools.lru_cache(maxsize=None)
@ -153,13 +153,13 @@ def norm_version(version: str) -> str | None:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
def health_check(prefix: Prefix, language_version: str) -> str | None: def health_check(prefix: Prefix, version: str) -> str | None:
envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, language_version) envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg')
# created with "old" virtualenv # created with "old" virtualenv
@ -202,7 +202,7 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> None:
envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
venv_cmd = [sys.executable, '-mvirtualenv', envdir] venv_cmd = [sys.executable, '-mvirtualenv', envdir]
python = norm_version(version) python = norm_version(version)
if python is not None: if python is not None:
@ -211,4 +211,4 @@ def install_environment(
cmd_output_b(*venv_cmd, cwd='/') cmd_output_b(*venv_cmd, cwd='/')
with in_env(prefix, version): with in_env(prefix, version):
helpers.run_setup_cmd(prefix, install_cmd) lang_base.setup_cmd(prefix, install_cmd)

View file

@ -7,18 +7,18 @@ import shutil
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET from pre_commit.envcontext import UNSET
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_b
from pre_commit.util import win_exe from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'renv' ENVIRONMENT_DIR = 'renv'
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ') RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
def get_env_patch(venv: str) -> PatchesT: def get_env_patch(venv: str) -> PatchesT:
@ -30,7 +30,7 @@ def get_env_patch(venv: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -93,7 +93,7 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> 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) os.makedirs(env_dir, exist_ok=True)
shutil.copy(prefix.path('renv.lock'), env_dir) shutil.copy(prefix.path('renv.lock'), env_dir)
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
@ -166,7 +166,7 @@ def run_hook(
color: bool, color: bool,
) -> tuple[int, bytes]: ) -> tuple[int, bytes]:
cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local) cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local)
return helpers.run_xargs( return lang_base.run_xargs(
cmd, cmd,
file_args, file_args,
require_serial=require_serial, require_serial=require_serial,

View file

@ -2,30 +2,35 @@ from __future__ import annotations
import contextlib import contextlib
import functools import functools
import importlib.resources
import os.path import os.path
import shutil import shutil
import tarfile import tarfile
from typing import Generator from typing import Generator
from typing import IO
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import CalledProcessError from pre_commit.util import CalledProcessError
from pre_commit.util import resource_bytesio
ENVIRONMENT_DIR = 'rbenv' ENVIRONMENT_DIR = 'rbenv'
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook 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) @functools.lru_cache(maxsize=1)
def get_default_version() -> str: 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' return 'system'
else: else:
return C.DEFAULT return C.DEFAULT
@ -68,13 +73,13 @@ def get_env_patch(
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir, version)):
yield yield
def _extract_resource(filename: str, dest: str) -> None: def _extract_resource(filename: str, dest: str) -> None:
with resource_bytesio(filename) as bio: with _resource_bytesio(filename) as bio:
with tarfile.open(fileobj=bio) as tf: with tarfile.open(fileobj=bio) as tf:
tf.extractall(dest) tf.extractall(dest)
@ -83,7 +88,7 @@ def _install_rbenv(
prefix: Prefix, prefix: Prefix,
version: str, version: str,
) -> None: # pragma: win32 no cover ) -> 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('.')) _extract_resource('rbenv.tar.gz', prefix.path('.'))
shutil.move(prefix.path('rbenv'), envdir) shutil.move(prefix.path('rbenv'), envdir)
@ -100,10 +105,10 @@ def _install_ruby(
version: str, version: str,
) -> None: # pragma: win32 no cover ) -> None: # pragma: win32 no cover
try: 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) except CalledProcessError: # pragma: no cover (usually find with download)
# Failed to download from mirror for some reason, build it instead # 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( def install_environment(
@ -114,17 +119,17 @@ def install_environment(
with in_env(prefix, version): with in_env(prefix, version):
# Need to call this before installing so rbenv's directories # Need to call this before installing so rbenv's directories
# are set up # are set up
helpers.run_setup_cmd(prefix, ('rbenv', 'init', '-')) lang_base.setup_cmd(prefix, ('rbenv', 'init', '-'))
if version != C.DEFAULT: if version != C.DEFAULT:
_install_ruby(prefix, version) _install_ruby(prefix, version)
# Need to call this after installing to set up the shims # 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): with in_env(prefix, version):
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix, ('gem', 'build', *prefix.star('.gemspec')), prefix, ('gem', 'build', *prefix.star('.gemspec')),
) )
helpers.run_setup_cmd( lang_base.setup_cmd(
prefix, prefix,
( (
'gem', 'install', 'gem', 'install',

View file

@ -11,19 +11,19 @@ from typing import Generator
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit import parse_shebang from pre_commit import parse_shebang
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_b
from pre_commit.util import make_executable from pre_commit.util import make_executable
from pre_commit.util import win_exe from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'rustenv' ENVIRONMENT_DIR = 'rustenv'
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
@functools.lru_cache(maxsize=1) @functools.lru_cache(maxsize=1)
@ -63,7 +63,7 @@ def get_env_patch(target_dir: str, version: str) -> PatchesT:
@contextlib.contextmanager @contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: 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)): with envcontext(get_env_patch(envdir, version)):
yield yield
@ -78,7 +78,7 @@ def _add_dependencies(
crate = f'{name}@{spec or "*"}' crate = f'{name}@{spec or "*"}'
crates.append(crate) 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: def install_rust_with_toolchain(toolchain: str) -> None:
@ -116,7 +116,7 @@ def install_environment(
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> None: ) -> 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: # There are two cases where we might want to specify more dependencies:
# as dependencies for the library being built, and as binary packages # as dependencies for the library being built, and as binary packages

View file

@ -2,14 +2,14 @@ from __future__ import annotations
from typing import Sequence 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.prefix import Prefix
ENVIRONMENT_DIR = None ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
install_environment = helpers.no_install install_environment = lang_base.no_install
in_env = helpers.no_env in_env = lang_base.no_env
def run_hook( def run_hook(
@ -22,9 +22,9 @@ def run_hook(
require_serial: bool, require_serial: bool,
color: bool, color: bool,
) -> tuple[int, bytes]: ) -> tuple[int, bytes]:
cmd = helpers.hook_cmd(entry, args) cmd = lang_base.hook_cmd(entry, args)
cmd = (prefix.path(cmd[0]), *cmd[1:]) cmd = (prefix.path(cmd[0]), *cmd[1:])
return helpers.run_xargs( return lang_base.run_xargs(
cmd, cmd,
file_args, file_args,
require_serial=require_serial, require_serial=require_serial,

View file

@ -5,10 +5,10 @@ import os
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_b
@ -16,9 +16,9 @@ BUILD_DIR = '.build'
BUILD_CONFIG = 'release' BUILD_CONFIG = 'release'
ENVIRONMENT_DIR = 'swift_env' ENVIRONMENT_DIR = 'swift_env'
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook
def get_env_patch(venv: str) -> PatchesT: # pragma: win32 no cover 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 @contextlib.contextmanager # pragma: win32 no cover
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]: def in_env(prefix: Prefix, version: str) -> Generator[None, 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)): with envcontext(get_env_patch(envdir)):
yield yield
@ -36,9 +36,9 @@ def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
def install_environment( def install_environment(
prefix: Prefix, version: str, additional_dependencies: Sequence[str], prefix: Prefix, version: str, additional_dependencies: Sequence[str],
) -> None: # pragma: win32 no cover ) -> None: # pragma: win32 no cover
helpers.assert_version_default('swift', version) lang_base.assert_version_default('swift', version)
helpers.assert_no_additional_deps('swift', additional_dependencies) lang_base.assert_no_additional_deps('swift', additional_dependencies)
envdir = helpers.environment_dir(prefix, ENVIRONMENT_DIR, version) envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
# Build the swift package # Build the swift package
os.mkdir(envdir) os.mkdir(envdir)

View file

@ -1,10 +1,10 @@
from __future__ import annotations from __future__ import annotations
from pre_commit.languages import helpers from pre_commit import lang_base
ENVIRONMENT_DIR = None ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version get_default_version = lang_base.basic_get_default_version
health_check = helpers.basic_health_check health_check = lang_base.basic_health_check
install_environment = helpers.no_install install_environment = lang_base.no_install
in_env = helpers.no_env in_env = lang_base.no_env
run_hook = helpers.basic_run_hook run_hook = lang_base.basic_run_hook

View file

@ -3,17 +3,18 @@ from __future__ import annotations
import json import json
import logging import logging
import os import os
import shlex
from typing import Any from typing import Any
from typing import Sequence from typing import Sequence
import pre_commit.constants as C 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 load_manifest
from pre_commit.clientlib import LOCAL from pre_commit.clientlib import LOCAL
from pre_commit.clientlib import META from pre_commit.clientlib import META
from pre_commit.clientlib import parse_version from pre_commit.clientlib import parse_version
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages.all import languages from pre_commit.lang_base import environment_dir
from pre_commit.languages.helpers import environment_dir
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.store import Store from pre_commit.store import Store
from pre_commit.util import clean_path_on_failure 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: 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: 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('Once installed this environment will be reused.')
logger.info('This may take a few minutes...') 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] lang = languages[hook.language]
assert lang.ENVIRONMENT_DIR is not None assert lang.ENVIRONMENT_DIR is not None

View file

@ -7,6 +7,7 @@ import time
from typing import Generator from typing import Generator
from pre_commit import git from pre_commit import git
from pre_commit.errors import FatalError
from pre_commit.util import CalledProcessError from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b from pre_commit.util import cmd_output_b
@ -49,12 +50,16 @@ def _intent_to_add_cleared() -> Generator[None, None, None]:
@contextlib.contextmanager @contextlib.contextmanager
def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
tree = cmd_output('git', 'write-tree')[1].strip() tree = cmd_output('git', 'write-tree')[1].strip()
retcode, diff_stdout_binary, _ = cmd_output_b( diff_cmd = (
'git', 'diff-index', '--ignore-submodules', '--binary', 'git', 'diff-index', '--ignore-submodules', '--binary',
'--exit-code', '--no-color', '--no-ext-diff', tree, '--', '--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 = f'patch{int(time.time())}-{os.getpid()}'
patch_filename = os.path.join(patch_dir, patch_filename) patch_filename = os.path.join(patch_dir, patch_filename)
logger.warning('Unstaged files detected.') 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 # Save the current unstaged changes as a patch
os.makedirs(patch_dir, exist_ok=True) os.makedirs(patch_dir, exist_ok=True)
with open(patch_filename, 'wb') as patch_file: 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) # prevent recursive post-checkout hooks (#1418)
no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1') 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) _git_apply(patch_filename)
logger.info(f'Restored changes from {patch_filename}.') logger.info(f'Restored changes from {patch_filename}.')
else: else: # pragma: win32 no cover
# There weren't any staged files so we don't need to do anything # some error occurred while requesting the diff
# special e = CalledProcessError(retcode, diff_cmd, b'', diff_stderr)
yield raise FatalError(
f'pre-commit failed to diff -- perhaps due to permissions?\n\n{e}',
)
@contextlib.contextmanager @contextlib.contextmanager

View file

@ -125,7 +125,7 @@ class Store:
@classmethod @classmethod
def db_repo_name(cls, repo: str, deps: Sequence[str]) -> str: def db_repo_name(cls, repo: str, deps: Sequence[str]) -> str:
if deps: if deps:
return f'{repo}:{",".join(sorted(deps))}' return f'{repo}:{",".join(deps)}'
else: else:
return repo return repo

View file

@ -12,7 +12,6 @@ from types import TracebackType
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import Generator from typing import Generator
from typing import IO
from pre_commit import parse_shebang from pre_commit import parse_shebang
@ -36,10 +35,6 @@ def clean_path_on_failure(path: str) -> Generator[None, None, None]:
raise raise
def resource_bytesio(filename: str) -> IO[bytes]:
return importlib.resources.open_binary('pre_commit.resources', filename)
def resource_text(filename: str) -> str: def resource_text(filename: str) -> str:
return importlib.resources.read_text('pre_commit.resources', filename) return importlib.resources.read_text('pre_commit.resources', filename)
@ -67,7 +62,7 @@ class CalledProcessError(RuntimeError):
def __bytes__(self) -> bytes: def __bytes__(self) -> bytes:
def _indent_or_none(part: bytes | None) -> bytes: def _indent_or_none(part: bytes | None) -> bytes:
if part: if part:
return b'\n ' + part.replace(b'\n', b'\n ') return b'\n ' + part.replace(b'\n', b'\n ').rstrip()
else: else:
return b' (none)' return b' (none)'

View file

@ -1,6 +1,6 @@
[metadata] [metadata]
name = pre_commit name = pre_commit
version = 3.0.4 version = 3.1.0
description = A framework for managing and maintaining multi-language pre-commit hooks. description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md long_description = file: README.md
long_description_content_type = text/markdown long_description_content_type = text/markdown

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import os import os
from typing import Sequence from typing import Sequence
from pre_commit.languages.all import Language from pre_commit.lang_base import Language
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
@ -16,10 +16,13 @@ def run_language(
version: str | None = None, version: str | None = None,
deps: Sequence[str] = (), deps: Sequence[str] = (),
is_local: bool = False, is_local: bool = False,
require_serial: bool = True,
color: bool = False,
) -> tuple[int, bytes]: ) -> tuple[int, bytes]:
prefix = Prefix(str(path)) prefix = Prefix(str(path))
version = version or language.get_default_version() version = version or language.get_default_version()
if language.ENVIRONMENT_DIR is not None:
language.install_environment(prefix, version, deps) language.install_environment(prefix, version, deps)
health_error = language.health_check(prefix, version) health_error = language.health_check(prefix, version)
assert health_error is None, health_error assert health_error is None, health_error
@ -30,8 +33,8 @@ def run_language(
args, args,
file_args, file_args,
is_local=is_local, is_local=is_local,
require_serial=True, require_serial=require_serial,
color=False, color=color,
) )
out = out.replace(b'\r\n', b'\n') out = out.replace(b'\r\n', b'\n')
return ret, out return ret, out

79
testing/languages Executable file
View file

@ -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())

View file

@ -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$

View file

@ -1,3 +0,0 @@
FROM ubuntu:focal
CMD ["echo", "This is overwritten by the .pre-commit-hooks.yaml 'entry'"]

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -1,12 +0,0 @@
using System;
namespace proj1
{
class Program
{
static void Main(string[] args)
{
Console.Write("Hello from dotnet!\n");
}
}
}

View file

@ -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>

View file

@ -1,12 +0,0 @@
using System;
namespace proj2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View file

@ -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>

View file

@ -1,3 +0,0 @@
bin/
obj/
nupkg/

View file

@ -1,5 +0,0 @@
- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni.tool
language: dotnet
files: ''

View file

@ -1,12 +0,0 @@
using System;
namespace dotnet_hooks_repo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello from dotnet!");
}
}
}

View file

@ -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>

View file

@ -1,3 +0,0 @@
bin/
obj/
nupkg/

View file

@ -1,5 +0,0 @@
- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni
language: dotnet
files: ''

View file

@ -1,12 +0,0 @@
using System;
namespace dotnet_hooks_repo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello from dotnet!");
}
}
}

View file

@ -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>

View file

@ -1,3 +0,0 @@
bin/
obj/
nupkg/

View file

@ -1,5 +0,0 @@
- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni
language: dotnet
files: ''

View file

@ -1,12 +0,0 @@
using System;
namespace dotnet_hooks_sln_repo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello from dotnet!");
}
}
}

View file

@ -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>

View file

@ -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

View file

@ -1,5 +0,0 @@
- id: golang-hook
name: golang example hook
entry: golang-hello-world
language: golang
files: ''

View file

@ -1,5 +0,0 @@
module golang-hello-world
go 1.18
require github.com/BurntSushi/toml v1.1.0

View file

@ -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=

View file

@ -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)
}

View file

@ -1,5 +0,0 @@
- id: foo
name: Foo
entry: foo
language: python_venv
files: \.py$

View file

@ -1,9 +0,0 @@
from __future__ import annotations
import sys
def main():
print(repr(sys.argv[1:]))
print('Hello World')
return 0

View file

@ -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']},
)

View file

@ -6,24 +6,13 @@ import subprocess
import pytest import pytest
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from testing.auto_namedtuple import auto_namedtuple from testing.auto_namedtuple import auto_namedtuple
TESTING_DIR = os.path.abspath(os.path.dirname(__file__)) 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): def get_resource_path(path):
return os.path.join(TESTING_DIR, 'resources', 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 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') xfailif_windows = pytest.mark.xfail(os.name == 'nt', reason='windows')

View file

@ -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']

View file

@ -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): def test_migrate_config_invalid_yaml(tmpdir):
contents = '[' contents = '['
cfg = tmpdir.join(C.CONFIG_FILE) cfg = tmpdir.join(C.CONFIG_FILE)

View file

@ -8,8 +8,8 @@ from unittest import mock
import pytest import pytest
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import lang_base
from pre_commit import parse_shebang from pre_commit import parse_shebang
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import CalledProcessError 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): def test_exe_exists_does_not_exist(find_exe_mck, homedir_mck):
find_exe_mck.return_value = None 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): def test_exe_exists_exists(find_exe_mck, homedir_mck):
find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby') 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): def test_exe_exists_false_if_shim(find_exe_mck, homedir_mck):
find_exe_mck.return_value = os.path.normpath('/foo/shims/ruby') 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): def test_exe_exists_false_if_homedir(find_exe_mck, homedir_mck):
find_exe_mck.return_value = os.path.normpath('/home/me/somedir/ruby') 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): def test_exe_exists_commonpath_raises_ValueError(find_exe_mck, homedir_mck):
find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby') find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby')
with mock.patch.object(os.path, 'commonpath', side_effect=ValueError): 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): def test_exe_exists_true_when_homedir_is_slash(find_exe_mck):
find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby') find_exe_mck.return_value = os.path.normpath('/usr/bin/ruby')
with mock.patch.object(os.path, 'expanduser', return_value=os.sep): 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(): 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(): 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(): 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` # an assertion that this does not raise `UnicodeError`
with pytest.raises(CalledProcessError): 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(): def test_assert_no_additional_deps():
with pytest.raises(AssertionError) as excinfo: 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 msg, = excinfo.value.args
assert msg == ( assert msg == (
'for now, pre-commit does not support additional_dependencies for ' '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(): def test_target_concurrency_normal():
with mock.patch.object(multiprocessing, 'cpu_count', return_value=123): with mock.patch.object(multiprocessing, 'cpu_count', return_value=123):
with mock.patch.dict(os.environ, {}, clear=True): 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(): def test_target_concurrency_testing_env_var():
with mock.patch.dict( with mock.patch.dict(
os.environ, {'PRE_COMMIT_NO_CONCURRENCY': '1'}, clear=True, 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(): def test_target_concurrency_on_travis():
with mock.patch.dict(os.environ, {'TRAVIS': '1'}, clear=True): 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(): 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, multiprocessing, 'cpu_count', side_effect=NotImplementedError,
): ):
with mock.patch.dict(os.environ, {}, clear=True): with mock.patch.dict(os.environ, {}, clear=True):
assert helpers.target_concurrency() == 1 assert lang_base.target_concurrency() == 1
def test_shuffled_is_deterministic(): def test_shuffled_is_deterministic():
seq = [str(i) for i in range(10)] seq = [str(i) for i in range(10)]
expected = ['4', '0', '5', '1', '8', '6', '2', '3', '7', '9'] 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(): 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)], ('echo',), [str(i) for i in range(10)],
require_serial=True, require_serial=True,
color=False, color=False,
) )
assert ret == 0 assert ret == 0
assert out.strip() == b'0 1 2 3 4 5 6 7 8 9' 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'

View file

@ -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')

View file

@ -11,6 +11,8 @@ import pytest
from pre_commit.languages import docker from pre_commit.languages import docker
from pre_commit.util import CalledProcessError from pre_commit.util import CalledProcessError
from testing.language_helpers import run_language
from testing.util import xfailif_windows
DOCKER_CGROUP_EXAMPLE = b'''\ DOCKER_CGROUP_EXAMPLE = b'''\
12:hugetlb:/docker/c33988ec7651ebc867cb24755eaf637a6734088bc7eef59d5799293a9e5450f7 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'') err = CalledProcessError(1, (), b'', b'')
with mock.patch.object(docker, 'cmd_output_b', side_effect=err): with mock.patch.object(docker, 'cmd_output_b', side_effect=err):
assert docker._get_docker_path('/project') == '/project' 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')

View file

@ -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')

View file

@ -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')

View file

@ -6,8 +6,11 @@ import pytest
import re_assert import re_assert
import pre_commit.constants as C 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 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__ ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__
@ -15,7 +18,7 @@ ACTUAL_GET_DEFAULT_VERSION = golang.get_default_version.__wrapped__
@pytest.fixture @pytest.fixture
def exe_exists_mck(): 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 yield mck
@ -41,3 +44,93 @@ def test_golang_infer_go_version_default():
assert version != C.DEFAULT assert version != C.DEFAULT
re_assert.Matches(r'^\d+\.\d+(?:\.\d+)?$').assert_matches(version) 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)

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import pytest import pytest
from pre_commit.languages import pygrep from pre_commit.languages import pygrep
from testing.language_helpers import run_language
@pytest.fixture @pytest.fixture
@ -13,6 +14,9 @@ def some_files(tmpdir):
tmpdir.join('f4').write_binary(b'foo\npattern\nbar\n') tmpdir.join('f4').write_binary(b'foo\npattern\nbar\n')
tmpdir.join('f5').write_binary(b'[INFO] hi\npattern\nbar') tmpdir.join('f5').write_binary(b'[INFO] hi\npattern\nbar')
tmpdir.join('f6').write_binary(b"pattern\nbarwith'foo\n") 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(): with tmpdir.as_cwd():
yield yield
@ -125,3 +129,16 @@ def test_multiline_multiline_flag_is_enabled(cap_out):
out = cap_out.get() out = cap_out.get()
assert ret == 1 assert ret == 1
assert out == 'f1:1:foo\nbar\n' 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'')

View file

@ -12,6 +12,7 @@ from pre_commit.languages import python
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import make_executable from pre_commit.util import make_executable
from pre_commit.util import win_exe from pre_commit.util import win_exe
from testing.language_helpers import run_language
def test_read_pyvenv_cfg(tmpdir): 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) os.replace(f'{py_exe}.tmp', py_exe)
assert python.health_check(prefix, C.DEFAULT) is None 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')

View file

@ -9,8 +9,8 @@ import pre_commit.constants as C
from pre_commit import parse_shebang from pre_commit import parse_shebang
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.languages import ruby 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.store import _make_local_repo
from pre_commit.util import resource_bytesio
from testing.language_helpers import run_language from testing.language_helpers import run_language
from testing.util import cwd from testing.util import cwd
from testing.util import xfailif_windows 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'), ('rbenv.tar.gz', 'ruby-build.tar.gz', 'ruby-download.tar.gz'),
) )
def test_archive_root_stat(filename): 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: with tarfile.open(fileobj=f) as tarf:
root, _, _ = filename.partition('.') root, _, _ = filename.partition('.')
assert oct(tarf.getmember(root).mode) == '0o755' assert oct(tarf.getmember(root).mode) == '0o755'

View file

@ -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

View file

@ -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

View file

@ -10,15 +10,12 @@ import pytest
import re_assert import re_assert
import pre_commit.constants as C 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 CONFIG_SCHEMA
from pre_commit.clientlib import load_manifest from pre_commit.clientlib import load_manifest
from pre_commit.envcontext import envcontext
from pre_commit.hook import Hook 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 import python
from pre_commit.languages.all import languages
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.repository import _hook_installed from pre_commit.repository import _hook_installed
from pre_commit.repository import all_hooks from pre_commit.repository import all_hooks
@ -28,22 +25,20 @@ from pre_commit.util import cmd_output_b
from testing.fixtures import make_config_from_repo from testing.fixtures import make_config_from_repo
from testing.fixtures import make_repo from testing.fixtures import make_repo
from testing.fixtures import modify_manifest from testing.fixtures import modify_manifest
from testing.language_helpers import run_language
from testing.util import cwd from testing.util import cwd
from testing.util import get_resource_path 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): def _hook_run(hook, filenames, color):
with languages[hook.language].in_env(hook.prefix, hook.language_version): return run_language(
return languages[hook.language].run_hook( path=hook.prefix.prefix_dir,
hook.prefix, language=languages[hook.language],
hook.entry, exe=hook.entry,
hook.args, args=hook.args,
filenames, file_args=filenames,
version=hook.language_version,
deps=hook.additional_dependencies,
is_local=hook.src == 'local', is_local=hook.src == 'local',
require_serial=hook.require_serial, require_serial=hook.require_serial,
color=color, color=color,
@ -81,7 +76,7 @@ def _test_hook_repo(
hook = _get_hook(config, store, hook_id) hook = _get_hook(config, store, hook_id)
ret, out = _hook_run(hook, args, color=color) ret, out = _hook_run(hook, args, color=color)
assert ret == expected_return_code assert ret == expected_return_code
assert _norm_out(out) == expected assert out == expected
def test_python_hook(tempdir_factory, store): 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): def test_python_venv_deprecation(store, caplog):
_test_hook_repo( config = {
tempdir_factory, store, 'python_venv_hooks_repo', 'repo': 'local',
'foo', [os.devnull], 'hooks': [{
f'[{os.devnull!r}]\nHello World\n'.encode(), 'id': 'example',
) 'name': 'example',
'language': 'python_venv',
'entry': 'echo hi',
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 _get_hook(config, store, 'example')
with mock.patch.object( assert caplog.messages[-1] == (
python, '`repo: local` uses deprecated `language: python_venv`. '
'_sys_executable_matches', 'This is an alias for `language: python`. '
return_value=False, 'Often `pre-commit autoupdate --repo local` will fix this.'
):
_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',
) )
@ -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): def test_missing_executable(tempdir_factory, store):
_test_hook_repo( _test_hook_repo(
tempdir_factory, store, 'not_found_exe', 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): def _norm_pwd(path):
# Under windows bash's temp and windows temp is different. # Under windows bash's temp and windows temp is different.
# This normalizes to the bash /tmp # 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) config = make_config_from_repo(path)
hook = _get_hook(config, store, 'foo') hook = _get_hook(config, store, 'foo')
envdir = helpers.environment_dir( envdir = lang_base.environment_dir(
hook.prefix, hook.prefix,
python.ENVIRONMENT_DIR, python.ENVIRONMENT_DIR,
hook.language_version, hook.language_version,
@ -449,67 +267,6 @@ def test_repository_state_compatibility(tempdir_factory, store, v):
assert _hook_installed(hook) is True 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): def test_unknown_keys(store, caplog):
config = { config = {
'repo': 'local', 'repo': 'local',
@ -553,7 +310,7 @@ def test_control_c_control_c_on_install(tempdir_factory, store):
# raise as well. # raise as well.
with pytest.raises(MyKeyboardInterrupt): with pytest.raises(MyKeyboardInterrupt):
with mock.patch.object( with mock.patch.object(
helpers, 'run_setup_cmd', side_effect=MyKeyboardInterrupt, lang_base, 'setup_cmd', side_effect=MyKeyboardInterrupt,
): ):
with mock.patch.object( with mock.patch.object(
shutil, 'rmtree', side_effect=MyKeyboardInterrupt, 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! # Should have made an environment, however this environment is broken!
hook, = hooks hook, = hooks
envdir = helpers.environment_dir( envdir = lang_base.environment_dir(
hook.prefix, hook.prefix,
python.ENVIRONMENT_DIR, python.ENVIRONMENT_DIR,
hook.language_version, hook.language_version,
@ -585,7 +342,7 @@ def test_invalidated_virtualenv(tempdir_factory, store):
hook = _get_hook(config, store, 'foo') hook = _get_hook(config, store, 'foo')
# Simulate breaking of the virtualenv # Simulate breaking of the virtualenv
envdir = helpers.environment_dir( envdir = lang_base.environment_dir(
hook.prefix, hook.prefix,
python.ENVIRONMENT_DIR, python.ENVIRONMENT_DIR,
hook.language_version, hook.language_version,
@ -667,7 +424,7 @@ def test_local_python_repo(store, local_python_config):
assert hook.language_version != C.DEFAULT assert hook.language_version != C.DEFAULT
ret, out = _hook_run(hook, ('filename',), color=False) ret, out = _hook_run(hook, ('filename',), color=False)
assert ret == 0 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): 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): def test_non_installable_hook_error_for_language_version(store, caplog):
config = { config = {
'repo': 'local', 'repo': 'local',

View file

@ -1,12 +1,15 @@
from __future__ import annotations from __future__ import annotations
import contextlib
import itertools import itertools
import os.path import os.path
import shutil import shutil
import pytest import pytest
import re_assert
from pre_commit import git from pre_commit import git
from pre_commit.errors import FatalError
from pre_commit.staged_files_only import staged_files_only from pre_commit.staged_files_only import staged_files_only
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from testing.auto_namedtuple import auto_namedtuple 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 cwd
from testing.util import get_resource_path from testing.util import get_resource_path
from testing.util import git_commit from testing.util import git_commit
from testing.util import xfailif_windows
FOO_CONTENTS = '\n'.join(('1', '2', '3', '4', '5', '6', '7', '8', '')) 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): with staged_files_only(patch_dir):
assert_no_diff() assert_no_diff()
assert git.intent_to_add_files() == ['foo'] 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'

View file

@ -180,7 +180,7 @@ def test_create_when_store_already_exists(store):
def test_db_repo_name(store): def test_db_repo_name(store):
assert store.db_repo_name('repo', ()) == 'repo' 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(): def test_local_resources_reflects_reality():
@ -246,3 +246,27 @@ def test_mark_config_as_used_readonly(tmpdir):
# should be skipped due to readonly # should be skipped due to readonly
store.mark_config_used(str(cfg)) store.mark_config_used(str(cfg))
assert store.select_all_configs() == [] 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'))

View file

@ -16,7 +16,7 @@ from pre_commit.util import rmtree
def test_CalledProcessError_str(): 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) == ( assert str(error) == (
"command: ('exe',)\n" "command: ('exe',)\n"
'return code: 1\n' 'return code: 1\n'

View file

@ -6,8 +6,8 @@ deps = -rrequirements-dev.txt
passenv = * passenv = *
commands = commands =
coverage erase coverage erase
coverage run -m pytest {posargs:tests} coverage run -m pytest {posargs:tests} --ignore=tests/languages
coverage report coverage report --omit=pre_commit/languages/*,tests/languages/*
[testenv:pre-commit] [testenv:pre-commit]
skip_install = true skip_install = true