summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug.yaml2
-rw-r--r--.pre-commit-config.yaml16
-rw-r--r--CHANGELOG.md40
-rw-r--r--CONTRIBUTING.md5
-rw-r--r--azure-pipelines.yml2
-rw-r--r--pre_commit/clientlib.py10
-rw-r--r--pre_commit/commands/run.py12
-rw-r--r--pre_commit/commands/sample_config.py4
-rw-r--r--pre_commit/error_handler.py2
-rw-r--r--pre_commit/git.py26
-rw-r--r--pre_commit/languages/dotnet.py67
-rw-r--r--pre_commit/languages/node.py2
-rw-r--r--pre_commit/languages/r.py5
-rw-r--r--pre_commit/languages/rust.py140
-rw-r--r--pre_commit/main.py8
-rw-r--r--pre_commit/resources/ruby-build.tar.gzbin72569 -> 74032 bytes
-rw-r--r--pre_commit/staged_files_only.py2
-rw-r--r--pre_commit/util.py15
-rw-r--r--pre_commit/xargs.py2
-rw-r--r--setup.cfg10
-rwxr-xr-xtesting/get-swift.sh6
-rwxr-xr-xtesting/make-archives2
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj2
-rw-r--r--testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj2
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore3
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml5
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs12
-rw-r--r--testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj9
-rw-r--r--testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj2
-rw-r--r--testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj2
-rw-r--r--testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml1
-rw-r--r--testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml2
-rw-r--r--tests/clientlib_test.py39
-rw-r--r--tests/commands/init_templatedir_test.py2
-rw-r--r--tests/commands/install_uninstall_test.py6
-rw-r--r--tests/commands/run_test.py37
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/error_handler_test.py5
-rw-r--r--tests/git_test.py2
-rw-r--r--tests/languages/docker_test.py2
-rw-r--r--tests/languages/r_test.py3
-rw-r--r--tests/languages/ruby_test.py4
-rw-r--r--tests/languages/rust_test.py90
-rw-r--r--tests/main_test.py55
-rw-r--r--tests/repository_test.py66
-rw-r--r--tests/store_test.py2
-rw-r--r--tests/util_test.py12
-rw-r--r--tox.ini3
48 files changed, 542 insertions, 206 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
index bfced0f..96cd6c7 100644
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -12,7 +12,7 @@ body:
- type: input
id: search
attributes:
- label: search tried in the issue tracker
+ label: search you tried in the issue tracker
placeholder: ...
validations:
required: true
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 94a35a7..7e58bdd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -10,35 +10,35 @@ repos:
- id: name-tests-test
- id: requirements-txt-fixer
- repo: https://github.com/asottile/setup-cfg-fmt
- rev: v1.20.1
+ rev: v2.2.0
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder_python_imports
- rev: v3.3.0
+ rev: v3.9.0
hooks:
- id: reorder-python-imports
exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/)
args: [--py37-plus, --add-import, 'from __future__ import annotations']
- repo: https://github.com/asottile/add-trailing-comma
- rev: v2.2.3
+ rev: v2.4.0
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/asottile/pyupgrade
- rev: v2.34.0
+ rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/pre-commit/mirrors-autopep8
- rev: v1.6.0
+ rev: v2.0.1
hooks:
- id: autopep8
- repo: https://github.com/PyCQA/flake8
- rev: 4.0.1
+ rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.961
+ rev: v0.991
hooks:
- id: mypy
additional_dependencies: [types-all]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03a7c80..cd0de5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,43 @@
+2.21.0 - 2022-12-25
+===================
+
+### Features
+- Require new-enough virtualenv to prevent 3.10 breakage
+ - #2467 PR by @asottile.
+- Respect aliases with `SKIP` for environment install.
+ - #2480 PR by @kmARC.
+ - #2478 issue by @kmARC.
+- Allow `pre-commit run --files` against unmerged paths.
+ - #2484 PR by @asottile.
+- Also apply regex warnings to `repo: local` hooks.
+ - #2524 PR by @chrisRedwine.
+ - #2521 issue by @asottile.
+- `rust` is now a "first class" language -- supporting `language_version` and
+ installation when not present.
+ - #2534 PR by @Holzhaus.
+- `r` now uses more-reliable binary installation.
+ - #2460 PR by @lorenzwalthert.
+- `GIT_ALLOW_PROTOCOL` is now passed through for git operations.
+ - #2555 PR by @asottile.
+- `GIT_ASKPASS` is now passed through for git operations.
+ - #2564 PR by @mattp-.
+- Remove `toml` dependency by using `cargo add` directly.
+ - #2568 PR by @m-rsha.
+- Support `dotnet` hooks which have dotted prefixes.
+ - #2641 PR by @rkm.
+ - #2629 issue by @rkm.
+
+### Fixes
+- Properly adjust `--commit-msg-filename` if run from a sub directory.
+ - #2459 PR by @asottile.
+- Simplify `--intent-to-add` detection by using `git diff`.
+ - #2580 PR by @m-rsha.
+- Fix `R.exe` selection on windows.
+ - #2605 PR by @lorenzwalthert.
+ - #2599 issue by @SInginc.
+- Skip default `nuget` source when installing `dotnet` packages.
+ - #2642 PR by @rkm.
+
2.20.0 - 2022-07-10
===================
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 310c17e..a9bcb79 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,7 +5,6 @@
- The complete test suite depends on having at least the following installed
(possibly not a complete list)
- git (Version 2.24.0 or above is required to run pre-merge-commit tests)
- - python2 (Required by a test which checks different python versions)
- python3 (Required by a test which checks different python versions)
- tox (or virtualenv)
- ruby + gem
@@ -65,9 +64,9 @@ to implement. The current implemented languages are at varying levels:
- 0th class - pre-commit does not require any dependencies for these languages
as they're not actually languages (current examples: fail, pygrep)
- 1st class - pre-commit will bootstrap a full interpreter requiring nothing to
- be installed globally (current examples: node, ruby)
+ be installed globally (current examples: node, ruby, rust)
- 2nd class - pre-commit requires the user to install the language globally but
- will install tools in an isolated fashion (current examples: python, go, rust,
+ will install tools in an isolated fashion (current examples: python, go,
swift, docker).
- 3rd class - pre-commit requires the user to install both the tool and the
language globally (current examples: script, system)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 454f6f1..34c94f5 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -17,6 +17,8 @@ jobs:
parameters:
toxenvs: [py37]
os: windows
+ additional_variables:
+ TEMP: C:\Temp
pre_test:
- task: UseRubyVersion@0
- powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts"
diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py
index 9b53e81..da6ca2b 100644
--- a/pre_commit/clientlib.py
+++ b/pre_commit/clientlib.py
@@ -298,6 +298,14 @@ CONFIG_HOOK_DICT = cfgv.Map(
OptionalSensibleRegexAtHook('files', cfgv.check_string),
OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
)
+LOCAL_HOOK_DICT = cfgv.Map(
+ 'Hook', 'id',
+
+ *MANIFEST_HOOK_DICT.items,
+
+ OptionalSensibleRegexAtHook('files', cfgv.check_string),
+ OptionalSensibleRegexAtHook('exclude', cfgv.check_string),
+)
CONFIG_REPO_DICT = cfgv.Map(
'Repository', 'repo',
@@ -308,7 +316,7 @@ CONFIG_REPO_DICT = cfgv.Map(
'repo', cfgv.NotIn(LOCAL, META),
),
cfgv.ConditionalRecurse(
- 'hooks', cfgv.Array(MANIFEST_HOOK_DICT),
+ 'hooks', cfgv.Array(LOCAL_HOOK_DICT),
'repo', LOCAL,
),
cfgv.ConditionalRecurse(
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index ad3d766..429e04c 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -263,7 +263,7 @@ def _all_filenames(args: argparse.Namespace) -> Collection[str]:
def _get_diff() -> bytes:
_, out, _ = cmd_output_b(
- 'git', 'diff', '--no-ext-diff', '--ignore-submodules', retcode=None,
+ 'git', 'diff', '--no-ext-diff', '--ignore-submodules', check=False,
)
return out
@@ -318,7 +318,7 @@ def _has_unmerged_paths() -> bool:
def _has_unstaged_config(config_file: str) -> bool:
retcode, _, _ = cmd_output_b(
'git', 'diff', '--no-ext-diff', '--exit-code', config_file,
- retcode=None,
+ check=False,
)
# be explicit, other git errors don't mean it has an unstaged config.
return retcode == 1
@@ -333,7 +333,7 @@ def run(
stash = not args.all_files and not args.files
# Check if we have unresolved merge conflict files and fail fast.
- if _has_unmerged_paths():
+ if stash and _has_unmerged_paths():
logger.error('Unmerged files. Resolve before committing.')
return 1
if bool(args.from_ref) != bool(args.to_ref):
@@ -420,7 +420,11 @@ def run(
return 1
skips = _get_skips(environ)
- to_install = [hook for hook in hooks if hook.id not in skips]
+ to_install = [
+ hook
+ for hook in hooks
+ if hook.id not in skips and hook.alias not in skips
+ ]
install_hook_envs(to_install, store)
return _run_hooks(config, hooks, skips, args)
diff --git a/pre_commit/commands/sample_config.py b/pre_commit/commands/sample_config.py
index 82a1617..ce22f65 100644
--- a/pre_commit/commands/sample_config.py
+++ b/pre_commit/commands/sample_config.py
@@ -1,7 +1,3 @@
-# TODO: maybe `git ls-remote git://github.com/pre-commit/pre-commit-hooks` to
-# determine the latest revision? This adds ~200ms from my tests (and is
-# significantly faster than https:// or http://). For now, periodically
-# manually updating the revision is fine.
from __future__ import annotations
SAMPLE_CONFIG = '''\
# See https://pre-commit.com for more information
diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py
index 992f5cd..d740ee3 100644
--- a/pre_commit/error_handler.py
+++ b/pre_commit/error_handler.py
@@ -25,7 +25,7 @@ def _log_and_exit(
error_msg = f'{msg}: {type(exc).__name__}: '.encode() + force_bytes(exc)
output.write_line_b(error_msg)
- _, git_version_b, _ = cmd_output_b('git', '--version', retcode=None)
+ _, git_version_b, _ = cmd_output_b('git', '--version', check=False)
git_version = git_version_b.decode(errors='backslashreplace').rstrip()
storedir = Store().directory
diff --git a/pre_commit/git.py b/pre_commit/git.py
index 35392b3..a76118f 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import logging
import os.path
import sys
-from typing import MutableMapping
+from typing import Mapping
from pre_commit.errors import FatalError
from pre_commit.util import CalledProcessError
@@ -24,9 +24,7 @@ def zsplit(s: str) -> list[str]:
return []
-def no_git_env(
- _env: MutableMapping[str, str] | None = None,
-) -> dict[str, str]:
+def no_git_env(_env: Mapping[str, str] | None = None) -> dict[str, str]:
# Too many bugs dealing with environment variables and GIT:
# https://github.com/pre-commit/pre-commit/issues/300
# In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
@@ -44,6 +42,8 @@ def no_git_env(
'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT',
'GIT_HTTP_PROXY_AUTHMETHOD',
+ 'GIT_ALLOW_PROTOCOL',
+ 'GIT_ASKPASS',
}
}
@@ -150,18 +150,10 @@ def get_staged_files(cwd: str | None = None) -> list[str]:
def intent_to_add_files() -> list[str]:
_, stdout, _ = cmd_output(
- 'git', 'status', '--ignore-submodules', '--porcelain', '-z',
+ 'git', 'diff', '--no-ext-diff', '--ignore-submodules',
+ '--diff-filter=A', '--name-only', '-z',
)
- parts = list(reversed(zsplit(stdout)))
- intent_to_add = []
- while parts:
- line = parts.pop()
- status, filename = line[:3], line[3:]
- if status[0] in {'C', 'R'}: # renames / moves have an additional arg
- parts.pop()
- if status[1] == 'A':
- intent_to_add.append(filename)
- return intent_to_add
+ return zsplit(stdout)
def get_all_files() -> list[str]:
@@ -187,11 +179,11 @@ def head_rev(remote: str) -> str:
def has_diff(*args: str, repo: str = '.') -> bool:
cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args)
- return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1
+ return cmd_output_b(*cmd, cwd=repo, check=False)[0] == 1
def has_core_hookpaths_set() -> bool:
- _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', retcode=None)
+ _, out, _ = cmd_output_b('git', 'config', 'core.hooksPath', check=False)
return bool(out.strip())
diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py
index 3983c6f..e26b45c 100644
--- a/pre_commit/languages/dotnet.py
+++ b/pre_commit/languages/dotnet.py
@@ -2,6 +2,10 @@ from __future__ import annotations
import contextlib
import os.path
+import re
+import tempfile
+import xml.etree.ElementTree
+import zipfile
from typing import Generator
from typing import Sequence
@@ -35,6 +39,22 @@ def in_env(prefix: Prefix) -> Generator[None, None, None]:
yield
+@contextlib.contextmanager
+def _nuget_config_no_sources() -> Generator[str, None, None]:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ nuget_config = os.path.join(tmpdir, 'nuget.config')
+ with open(nuget_config, 'w') as f:
+ f.write(
+ '<?xml version="1.0" encoding="utf-8"?>'
+ '<configuration>'
+ ' <packageSources>'
+ ' <clear />'
+ ' </packageSources>'
+ '</configuration>',
+ )
+ yield nuget_config
+
+
def install_environment(
prefix: Prefix,
version: str,
@@ -57,21 +77,42 @@ def install_environment(
),
)
- # Determine tool from the packaged file <tool_name>.<version>.nupkg
- build_outputs = os.listdir(os.path.join(prefix.prefix_dir, build_dir))
- for output in build_outputs:
- tool_name = output.split('.')[0]
+ nupkg_dir = prefix.path(build_dir)
+ nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')]
+
+ if not nupkgs:
+ raise AssertionError('could not find any build outputs to install')
+
+ for nupkg in nupkgs:
+ with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f:
+ nuspec, = (x for x in f.namelist() if x.endswith('.nuspec'))
+ with f.open(nuspec) as spec:
+ tree = xml.etree.ElementTree.parse(spec)
+
+ namespace = re.match(r'{.*}', tree.getroot().tag)
+ if not namespace:
+ raise AssertionError('could not parse namespace from nuspec')
+
+ tool_id_element = tree.find(f'.//{namespace[0]}id')
+ if tool_id_element is None:
+ raise AssertionError('expected to find an "id" element')
+
+ tool_id = tool_id_element.text
+ if not tool_id:
+ raise AssertionError('"id" element missing tool name')
# Install to bin dir
- helpers.run_setup_cmd(
- prefix,
- (
- 'dotnet', 'tool', 'install',
- '--tool-path', os.path.join(envdir, BIN_DIR),
- '--add-source', build_dir,
- tool_name,
- ),
- )
+ with _nuget_config_no_sources() as nuget_config:
+ helpers.run_setup_cmd(
+ prefix,
+ (
+ 'dotnet', 'tool', 'install',
+ '--configfile', nuget_config,
+ '--tool-path', os.path.join(envdir, BIN_DIR),
+ '--add-source', build_dir,
+ tool_id,
+ ),
+ )
# Clean the git dir, ignoring the environment dir
clean_cmd = ('git', 'clean', '-ffxd', '-e', f'{ENVIRONMENT_DIR}-*')
diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py
index 39f3000..37a5b63 100644
--- a/pre_commit/languages/node.py
+++ b/pre_commit/languages/node.py
@@ -75,7 +75,7 @@ def in_env(
def health_check(prefix: Prefix, language_version: str) -> str | None:
with in_env(prefix, language_version):
- retcode, _, _ = cmd_output_b('node', '--version', retcode=None)
+ retcode, _, _ = cmd_output_b('node', '--version', check=False)
if retcode != 0: # pragma: win32 no cover
return f'`node --version` returned {retcode}'
else:
diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py
index 40a001d..d281102 100644
--- a/pre_commit/languages/r.py
+++ b/pre_commit/languages/r.py
@@ -15,6 +15,7 @@ from pre_commit.languages import helpers
from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output_b
+from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'renv'
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
@@ -63,7 +64,7 @@ def _rscript_exec() -> str:
if r_home is None:
return 'Rscript'
else:
- return os.path.join(r_home, 'bin', 'Rscript')
+ return os.path.join(r_home, 'bin', win_exe('Rscript'))
def _entry_validate(entry: Sequence[str]) -> None:
@@ -158,7 +159,7 @@ def _inline_r_setup(code: str) -> str:
only be configured via R options once R has started. These are set here.
"""
with_option = f"""\
- options(install.packages.compile.from.source = "never")
+ options(install.packages.compile.from.source = "never", pkgType = "binary")
{code}
"""
return with_option
diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py
index 01c3730..204f2aa 100644
--- a/pre_commit/languages/rust.py
+++ b/pre_commit/languages/rust.py
@@ -1,13 +1,17 @@
from __future__ import annotations
import contextlib
+import functools
import os.path
+import shutil
+import sys
+import tempfile
+import urllib.request
from typing import Generator
from typing import Sequence
-import toml
-
import pre_commit.constants as C
+from pre_commit import parse_shebang
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
@@ -16,40 +20,105 @@ from pre_commit.languages import helpers
from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output_b
+from pre_commit.util import make_executable
+from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'rustenv'
-get_default_version = helpers.basic_get_default_version
health_check = helpers.basic_health_check
-def get_env_patch(target_dir: str) -> PatchesT:
+@functools.lru_cache(maxsize=1)
+def get_default_version() -> str:
+ # If rust is already installed, we can save a bunch of setup time by
+ # using the installed version.
+ #
+ # Just detecting the executable does not suffice, because if rustup is
+ # installed but no toolchain is available, then `cargo` exists but
+ # cannot be used without installing a toolchain first.
+ if cmd_output_b('cargo', '--version', check=False)[0] == 0:
+ return 'system'
+ else:
+ return C.DEFAULT
+
+
+def _rust_toolchain(language_version: str) -> str:
+ """Transform the language version into a rust toolchain version."""
+ if language_version == C.DEFAULT:
+ return 'stable'
+ else:
+ return language_version
+
+
+def _envdir(prefix: Prefix, version: str) -> str:
+ directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
+ return prefix.path(directory)
+
+
+def get_env_patch(target_dir: str, version: str) -> PatchesT:
return (
+ ('CARGO_HOME', target_dir),
('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))),
+ # Only set RUSTUP_TOOLCHAIN if we don't want use the system's default
+ # toolchain
+ *(
+ (('RUSTUP_TOOLCHAIN', _rust_toolchain(version)),)
+ if version != 'system' else ()
+ ),
)
@contextlib.contextmanager
-def in_env(prefix: Prefix) -> Generator[None, None, None]:
- target_dir = prefix.path(
- helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
- )
- with envcontext(get_env_patch(target_dir)):
+def in_env(
+ prefix: Prefix,
+ language_version: str,
+) -> Generator[None, None, None]:
+ with envcontext(
+ get_env_patch(_envdir(prefix, language_version), language_version),
+ ):
yield
def _add_dependencies(
- cargo_toml_path: str,
+ prefix: Prefix,
additional_dependencies: set[str],
) -> None:
- with open(cargo_toml_path, 'r+') as f:
- cargo_toml = toml.load(f)
- cargo_toml.setdefault('dependencies', {})
- for dep in additional_dependencies:
- name, _, spec = dep.partition(':')
- cargo_toml['dependencies'][name] = spec or '*'
- f.seek(0)
- toml.dump(cargo_toml, f)
- f.truncate()
+ crates = []
+ for dep in additional_dependencies:
+ name, _, spec = dep.partition(':')
+ crate = f'{name}@{spec or "*"}'
+ crates.append(crate)
+
+ helpers.run_setup_cmd(prefix, ('cargo', 'add', *crates))
+
+
+def install_rust_with_toolchain(toolchain: str) -> None:
+ with tempfile.TemporaryDirectory() as rustup_dir:
+ with envcontext((('RUSTUP_HOME', rustup_dir),)):
+ # acquire `rustup` if not present
+ if parse_shebang.find_executable('rustup') is None:
+ # We did not detect rustup and need to download it first.
+ if sys.platform == 'win32': # pragma: win32 cover
+ url = 'https://win.rustup.rs/x86_64'
+ else: # pragma: win32 no cover
+ url = 'https://sh.rustup.rs'
+
+ resp = urllib.request.urlopen(url)
+
+ rustup_init = os.path.join(rustup_dir, win_exe('rustup-init'))
+ with open(rustup_init, 'wb') as f:
+ shutil.copyfileobj(resp, f)
+ make_executable(rustup_init)
+
+ # install rustup into `$CARGO_HOME/bin`
+ cmd_output_b(
+ rustup_init, '-y', '--quiet', '--no-modify-path',
+ '--default-toolchain', 'none',
+ )
+
+ cmd_output_b(
+ 'rustup', 'toolchain', 'install', '--no-self-update',
+ toolchain,
+ )
def install_environment(
@@ -57,10 +126,7 @@ def install_environment(
version: str,
additional_dependencies: Sequence[str],
) -> None:
- helpers.assert_version_default('rust', version)
- directory = prefix.path(
- helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
- )
+ directory = _envdir(prefix, version)
# There are two cases where we might want to specify more dependencies:
# as dependencies for the library being built, and as binary packages
@@ -77,24 +143,28 @@ def install_environment(
}
lib_deps = set(additional_dependencies) - cli_deps
- if len(lib_deps) > 0:
- _add_dependencies(prefix.path('Cargo.toml'), lib_deps)
-
with clean_path_on_failure(directory):
packages_to_install: set[tuple[str, ...]] = {('--path', '.')}
for cli_dep in cli_deps:
cli_dep = cli_dep[len('cli:'):]
- package, _, version = cli_dep.partition(':')
- if version != '':
- packages_to_install.add((package, '--version', version))
+ package, _, crate_version = cli_dep.partition(':')
+ if crate_version != '':
+ packages_to_install.add((package, '--version', crate_version))
else:
packages_to_install.add((package,))
- for args in packages_to_install:
- cmd_output_b(
- 'cargo', 'install', '--bins', '--root', directory, *args,
- cwd=prefix.prefix_dir,
- )
+ with in_env(prefix, version):
+ if version != 'system':
+ install_rust_with_toolchain(_rust_toolchain(version))
+
+ if len(lib_deps) > 0:
+ _add_dependencies(prefix, lib_deps)
+
+ for args in packages_to_install:
+ cmd_output_b(
+ 'cargo', 'install', '--bins', '--root', directory, *args,
+ cwd=prefix.prefix_dir,
+ )
def run_hook(
@@ -102,5 +172,5 @@ def run_hook(
file_args: Sequence[str],
color: bool,
) -> tuple[int, bytes]:
- with in_env(hook.prefix):
+ with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/main.py b/pre_commit/main.py
index b4fa966..3915993 100644
--- a/pre_commit/main.py
+++ b/pre_commit/main.py
@@ -155,6 +155,10 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None:
args.config = os.path.abspath(args.config)
if args.command in {'run', 'try-repo'}:
args.files = [os.path.abspath(filename) for filename in args.files]
+ if args.commit_msg_filename is not None:
+ args.commit_msg_filename = os.path.abspath(
+ args.commit_msg_filename,
+ )
if args.command == 'try-repo' and os.path.exists(args.repo):
args.repo = os.path.abspath(args.repo)
@@ -164,6 +168,10 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None:
args.config = os.path.relpath(args.config)
if args.command in {'run', 'try-repo'}:
args.files = [os.path.relpath(filename) for filename in args.files]
+ if args.commit_msg_filename is not None:
+ args.commit_msg_filename = os.path.relpath(
+ args.commit_msg_filename,
+ )
if args.command == 'try-repo' and os.path.exists(args.repo):
args.repo = os.path.relpath(args.repo)
diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz
index 8edb3ca..35419f6 100644
--- a/pre_commit/resources/ruby-build.tar.gz
+++ b/pre_commit/resources/ruby-build.tar.gz
Binary files differ
diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py
index 83d8a03..172fb20 100644
--- a/pre_commit/staged_files_only.py
+++ b/pre_commit/staged_files_only.py
@@ -52,7 +52,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
retcode, diff_stdout_binary, _ = cmd_output_b(
'git', 'diff-index', '--ignore-submodules', '--binary',
'--exit-code', '--no-color', '--no-ext-diff', tree, '--',
- retcode=None,
+ check=False,
)
if retcode and diff_stdout_binary.strip():
patch_filename = f'patch{int(time.time())}-{os.getpid()}'
diff --git a/pre_commit/util.py b/pre_commit/util.py
index 8c296f4..b850768 100644
--- a/pre_commit/util.py
+++ b/pre_commit/util.py
@@ -83,14 +83,12 @@ class CalledProcessError(RuntimeError):
self,
returncode: int,
cmd: tuple[str, ...],
- expected_returncode: int,
stdout: bytes,
stderr: bytes | None,
) -> None:
- super().__init__(returncode, cmd, expected_returncode, stdout, stderr)
+ super().__init__(returncode, cmd, stdout, stderr)
self.returncode = returncode
self.cmd = cmd
- self.expected_returncode = expected_returncode
self.stdout = stdout
self.stderr = stderr
@@ -104,7 +102,6 @@ class CalledProcessError(RuntimeError):
return b''.join((
f'command: {self.cmd!r}\n'.encode(),
f'return code: {self.returncode}\n'.encode(),
- f'expected return code: {self.expected_returncode}\n'.encode(),
b'stdout:', _indent_or_none(self.stdout), b'\n',
b'stderr:', _indent_or_none(self.stderr),
))
@@ -124,7 +121,7 @@ def _oserror_to_output(e: OSError) -> tuple[int, bytes, None]:
def cmd_output_b(
*cmd: str,
- retcode: int | None = 0,
+ check: bool = True,
**kwargs: Any,
) -> tuple[int, bytes, bytes | None]:
_setdefault_kwargs(kwargs)
@@ -142,8 +139,8 @@ def cmd_output_b(
stdout_b, stderr_b = proc.communicate()
returncode = proc.returncode
- if retcode is not None and retcode != returncode:
- raise CalledProcessError(returncode, cmd, retcode, stdout_b, stderr_b)
+ if check and returncode:
+ raise CalledProcessError(returncode, cmd, stdout_b, stderr_b)
return returncode, stdout_b, stderr_b
@@ -196,10 +193,10 @@ if os.name != 'nt': # pragma: win32 no cover
def cmd_output_p(
*cmd: str,
- retcode: int | None = 0,
+ check: bool = True,
**kwargs: Any,
) -> tuple[int, bytes, bytes | None]:
- assert retcode is None
+ assert check is False
assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr']
_setdefault_kwargs(kwargs)
diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py
index f2b3421..e3af90e 100644
--- a/pre_commit/xargs.py
+++ b/pre_commit/xargs.py
@@ -154,7 +154,7 @@ def xargs(
run_cmd: tuple[str, ...],
) -> tuple[int, bytes, bytes | None]:
return cmd_fn(
- *run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs,
+ *run_cmd, check=False, stderr=subprocess.STDOUT, **kwargs,
)
threads = min(len(partitions), target_concurrency)
diff --git a/setup.cfg b/setup.cfg
index ae214f6..a898892 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pre_commit
-version = 2.20.0
+version = 2.21.0
description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md
long_description_content_type = text/markdown
@@ -13,10 +13,6 @@ classifiers =
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.7
- Programming Language :: Python :: 3.8
- Programming Language :: Python :: 3.9
- Programming Language :: Python :: 3.10
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
@@ -27,8 +23,7 @@ install_requires =
identify>=1.0.0
nodeenv>=0.11.1
pyyaml>=5.1
- toml
- virtualenv>=20.0.8
+ virtualenv>=20.10.0
importlib-metadata;python_version<"3.8"
python_requires = >=3.7
@@ -61,7 +56,6 @@ check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
-no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
diff --git a/testing/get-swift.sh b/testing/get-swift.sh
index b77e18c..3e78082 100755
--- a/testing/get-swift.sh
+++ b/testing/get-swift.sh
@@ -3,9 +3,9 @@
set -euo pipefail
. /etc/lsb-release
-if [ "$DISTRIB_CODENAME" = "focal" ]; then
- SWIFT_URL='https://download.swift.org/swift-5.6.1-release/ubuntu2004/swift-5.6.1-RELEASE/swift-5.6.1-RELEASE-ubuntu20.04.tar.gz'
- SWIFT_HASH='2b4f22d4a8b59fe8e050f0b7f020f8d8f12553cbda56709b2340a4a3bb90cfea'
+if [ "$DISTRIB_CODENAME" = "jammy" ]; then
+ SWIFT_URL='https://download.swift.org/swift-5.7.1-release/ubuntu2204/swift-5.7.1-RELEASE/swift-5.7.1-RELEASE-ubuntu22.04.tar.gz'
+ SWIFT_HASH='7f60291f5088d3e77b0c2364beaabd29616ee7b37260b7b06bdbeb891a7fe161'
else
echo "unknown dist: ${DISTRIB_CODENAME}" 1>&2
exit 1
diff --git a/testing/make-archives b/testing/make-archives
index 04b42dd..704101f 100755
--- a/testing/make-archives
+++ b/testing/make-archives
@@ -17,7 +17,7 @@ from typing import Sequence
REPOS = (
('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'),
- ('ruby-build', 'https://github.com/rbenv/ruby-build', '2004fd7'),
+ ('ruby-build', 'https://github.com/rbenv/ruby-build', '98c0337'),
(
'ruby-download',
'https://github.com/garnieretienne/rvm-download',
diff --git a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj
index 4f714d3..861ced6 100644
--- a/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj
+++ b/testing/resources/dotnet_hooks_combo_repo/proj1/proj1.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6</TargetFramework>
<PackAsTool>true</PackAsTool>
<ToolCommandName>proj1</ToolCommandName>
diff --git a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj
index da451f7..dfce2ca 100644
--- a/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj
+++ b/testing/resources/dotnet_hooks_combo_repo/proj2/proj2.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>net5.0</TargetFramework>
+ <TargetFramework>net6</TargetFramework>
<PackAsTool>true</PackAsTool>
<ToolCommandName>proj2</ToolCommandName>
diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore b/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore
new file mode 100644
index 0000000..edcd28f
--- /dev/null
+++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/.gitignore
@@ -0,0 +1,3 @@
+bin/
+obj/
+nupkg/
diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml b/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml
new file mode 100644
index 0000000..6626627
--- /dev/null
+++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/.pre-commit-hooks.yaml
@@ -0,0 +1,5 @@
+- id: dotnet-example-hook
+ name: dotnet example hook
+ entry: testeroni.tool
+ language: dotnet
+ files: ''
diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs b/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs
new file mode 100644
index 0000000..1456e8e
--- /dev/null
+++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/Program.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace dotnet_hooks_repo
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ Console.WriteLine("Hello from dotnet!");
+ }
+ }
+}
diff --git a/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj b/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj
new file mode 100644
index 0000000..754b760
--- /dev/null
+++ b/testing/resources/dotnet_hooks_csproj_prefix_repo/dotnet_hooks_csproj_prefix_repo.csproj
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net7.0</TargetFramework>
+ <PackAsTool>true</PackAsTool>
+ <ToolCommandName>testeroni.tool</ToolCommandName>
+ <PackageOutputPath>./nupkg</PackageOutputPath>
+ </PropertyGroup>
+</Project>
diff --git a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj b/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj
index d2e556a..fa9879b 100644
--- a/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj
+++ b/testing/resources/dotnet_hooks_csproj_repo/dotnet_hooks_csproj_repo.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>netcoreapp3.1</TargetFramework>
+ <TargetFramework>net6</TargetFramework>
<PackAsTool>true</PackAsTool>
<ToolCommandName>testeroni</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
diff --git a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj b/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj
index e372964..a4e2d00 100644
--- a/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj
+++ b/testing/resources/dotnet_hooks_sln_repo/dotnet_hooks_sln_repo.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>netcoreapp3.1</TargetFramework>
+ <TargetFramework>net6</TargetFramework>
<PackAsTool>true</PackAsTool>
<ToolCommandName>testeroni</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
diff --git a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml
index 964cf83..2c23700 100644
--- a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml
+++ b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml
@@ -2,4 +2,5 @@
name: Python 3 Hook
entry: python3-hook
language: python
+ language_version: python3
files: \.py$
diff --git a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml
index 63e1dd4..364d47d 100644
--- a/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml
+++ b/testing/resources/ruby_versioned_hooks_repo/.pre-commit-hooks.yaml
@@ -2,5 +2,5 @@
name: Ruby Hook
entry: ruby_hook
language: ruby
- language_version: 2.5.1
+ language_version: 3.1.0
files: \.rb$
diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py
index fb36bb5..b4c3c4e 100644
--- a/tests/clientlib_test.py
+++ b/tests/clientlib_test.py
@@ -15,6 +15,8 @@ from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION
from pre_commit.clientlib import MANIFEST_SCHEMA
from pre_commit.clientlib import META_HOOK_DICT
from pre_commit.clientlib import MigrateShaToRev
+from pre_commit.clientlib import OptionalSensibleRegexAtHook
+from pre_commit.clientlib import OptionalSensibleRegexAtTop
from pre_commit.clientlib import validate_config_main
from pre_commit.clientlib import validate_manifest_main
from testing.fixtures import sample_local_config
@@ -262,6 +264,27 @@ def test_warn_mutable_rev_conditional():
@pytest.mark.parametrize(
+ 'validator_cls',
+ (
+ OptionalSensibleRegexAtHook,
+ OptionalSensibleRegexAtTop,
+ ),
+)
+def test_sensible_regex_validators_dont_pass_none(validator_cls):
+ key = 'files'
+ with pytest.raises(cfgv.ValidationError) as excinfo:
+ validator = validator_cls(key, cfgv.check_string)
+ validator.check({key: None})
+
+ assert str(excinfo.value) == (
+ '\n'
+ f'==> At key: {key}'
+ '\n'
+ '=====> Expected string got NoneType'
+ )
+
+
+@pytest.mark.parametrize(
('regex', 'warning'),
(
(
@@ -296,6 +319,22 @@ def test_validate_optional_sensible_regex_at_hook(caplog, regex, warning):
assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
+def test_validate_optional_sensible_regex_at_local_hook(caplog):
+ config_obj = sample_local_config()
+ config_obj['hooks'][0]['files'] = 'dir/*.py'
+
+ cfgv.validate(config_obj, CONFIG_REPO_DICT)
+
+ assert caplog.record_tuples == [
+ (
+ 'pre_commit',
+ logging.WARNING,
+ "The 'files' field in hook 'do_not_commit' is a regex, not a glob "
+ "-- matching '/*' probably isn't what you want here",
+ ),
+ ]
+
+
@pytest.mark.parametrize(
('regex', 'warning'),
(
diff --git a/tests/commands/init_templatedir_test.py b/tests/commands/init_templatedir_test.py
index 64bfc8b..28f29b7 100644
--- a/tests/commands/init_templatedir_test.py
+++ b/tests/commands/init_templatedir_test.py
@@ -135,7 +135,7 @@ def test_init_templatedir_skip_on_missing_config(
retcode, output = git_commit(
fn=cmd_output_mocked_pre_commit_home,
tempdir_factory=tempdir_factory,
- retcode=None,
+ check=False,
)
assert retcode == commit_retcode
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index ae668ac..379c03a 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -126,7 +126,7 @@ def _get_commit_output(tempdir_factory, touch_file='foo', **kwargs):
cmd_output('git', 'add', touch_file)
return git_commit(
fn=cmd_output_mocked_pre_commit_home,
- retcode=None,
+ check=False,
tempdir_factory=tempdir_factory,
**kwargs,
)
@@ -286,7 +286,7 @@ def test_environment_not_sourced(tempdir_factory, store):
'GIT_AUTHOR_EMAIL': os.environ['GIT_AUTHOR_EMAIL'],
'GIT_COMMITTER_EMAIL': os.environ['GIT_COMMITTER_EMAIL'],
},
- retcode=None,
+ check=False,
)
assert ret == 1
assert out == (
@@ -551,7 +551,7 @@ def _get_push_output(tempdir_factory, remote='origin', opts=()):
return cmd_output_mocked_pre_commit_home(
'git', 'push', remote, 'HEAD:new_branch', *opts,
tempdir_factory=tempdir_factory,
- retcode=None,
+ check=False,
)[:2]
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index 2634c0c..03d741e 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -536,6 +536,13 @@ def test_merge_conflict(cap_out, store, in_merge_conflict):
assert b'Unmerged files. Resolve before committing.' in printed
+def test_files_during_merge_conflict(cap_out, store, in_merge_conflict):
+ opts = run_opts(files=['placeholder'])
+ ret, printed = _do_run(cap_out, store, in_merge_conflict, opts)
+ assert ret == 0
+ assert b'Bash hook' in printed
+
+
def test_merge_conflict_modified(cap_out, store, in_merge_conflict):
# Touch another file so we have unstaged non-conflicting things
assert os.path.exists('placeholder')
@@ -635,6 +642,32 @@ def test_skip_bypasses_installation(cap_out, store, repo_with_passing_hook):
assert ret == 0
+def test_skip_alias_bypasses_installation(
+ cap_out, store, repo_with_passing_hook,
+):
+ config = {
+ 'repo': 'local',
+ 'hooks': [
+ {
+ 'id': 'skipme',
+ 'name': 'skipme-1',
+ 'alias': 'skipme-1',
+ 'entry': 'skipme',
+ 'language': 'python',
+ 'additional_dependencies': ['/pre-commit-does-not-exist'],
+ },
+ ],
+ }
+ add_config_to_repo(repo_with_passing_hook, config)
+
+ ret, printed = _do_run(
+ cap_out, store, repo_with_passing_hook,
+ run_opts(all_files=True),
+ {'SKIP': 'skipme-1'},
+ )
+ assert ret == 0
+
+
def test_hook_id_not_in_non_verbose_output(
cap_out, store, repo_with_passing_hook,
):
@@ -685,7 +718,7 @@ def test_non_ascii_hook_id(repo_with_passing_hook, tempdir_factory):
with cwd(repo_with_passing_hook):
_, stdout, _ = cmd_output_mocked_pre_commit_home(
sys.executable, '-m', 'pre_commit.main', 'run', '☃',
- retcode=None, tempdir_factory=tempdir_factory,
+ check=False, tempdir_factory=tempdir_factory,
)
assert 'UnicodeDecodeError' not in stdout
# Doesn't actually happen, but a reasonable assertion
@@ -704,7 +737,7 @@ def test_stdout_write_bug_py26(repo_with_failing_hook, store, tempdir_factory):
_, out = git_commit(
fn=cmd_output_mocked_pre_commit_home,
tempdir_factory=tempdir_factory,
- retcode=None,
+ check=False,
)
assert 'UnicodeEncodeError' not in out
# Doesn't actually happen, but a reasonable assertion
diff --git a/tests/conftest.py b/tests/conftest.py
index 40c0c05..3076171 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -68,7 +68,7 @@ def _make_conflict():
bar_only_file.write('bar')
cmd_output('git', 'add', 'bar_only_file')
git_commit(msg=_make_conflict.__name__)
- cmd_output('git', 'merge', 'foo', retcode=None)
+ cmd_output('git', 'merge', 'foo', check=False)
@pytest.fixture
diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py
index 47e2afa..a79d9c1 100644
--- a/tests/error_handler_test.py
+++ b/tests/error_handler_test.py
@@ -162,7 +162,7 @@ def test_error_handler_non_ascii_exception(mock_store_dir):
def test_error_handler_non_utf8_exception(mock_store_dir):
with pytest.raises(SystemExit):
with error_handler.error_handler():
- raise CalledProcessError(1, ('exe',), 0, b'error: \xa0\xe1', b'')
+ raise CalledProcessError(1, ('exe',), b'error: \xa0\xe1', b'')
def test_error_handler_non_stringable_exception(mock_store_dir):
@@ -183,10 +183,11 @@ def test_error_handler_no_tty(tempdir_factory):
'from pre_commit.error_handler import error_handler\n'
'with error_handler():\n'
' raise ValueError("\\u2603")\n',
- retcode=3,
+ check=False,
tempdir_factory=tempdir_factory,
pre_commit_home=pre_commit_home,
)
+ assert ret == 3
log_file = os.path.join(pre_commit_home, 'pre-commit.log')
out_lines = out.splitlines()
assert out_lines[-2] == 'An unexpected error has occurred: ValueError: ☃'
diff --git a/tests/git_test.py b/tests/git_test.py
index b9f524a..93f5a1c 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -104,7 +104,7 @@ def test_is_in_merge_conflict_submodule(in_conflicting_submodule):
def test_cherry_pick_conflict(in_merge_conflict):
cmd_output('git', 'merge', '--abort')
foo_ref = cmd_output('git', 'rev-parse', 'foo')[1].strip()
- cmd_output('git', 'cherry-pick', foo_ref, retcode=None)
+ cmd_output('git', 'cherry-pick', foo_ref, check=False)
assert git.is_in_merge_conflict() is False
diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py
index 5838761..5f7c85e 100644
--- a/tests/languages/docker_test.py
+++ b/tests/languages/docker_test.py
@@ -178,6 +178,6 @@ def test_get_docker_path_in_docker_windows(in_docker):
def test_get_docker_path_in_docker_docker_in_docker(in_docker):
# won't be able to discover "self" container in true docker-in-docker
- err = CalledProcessError(1, (), 0, b'', b'')
+ err = CalledProcessError(1, (), b'', b'')
with mock.patch.object(docker, 'cmd_output_b', side_effect=err):
assert docker._get_docker_path('/project') == '/project'
diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py
index 5bc63b2..c52d5ac 100644
--- a/tests/languages/r_test.py
+++ b/tests/languages/r_test.py
@@ -6,6 +6,7 @@ import pytest
from pre_commit import envcontext
from pre_commit.languages import r
+from pre_commit.util import win_exe
from testing.fixtures import make_config_from_repo
from testing.fixtures import make_repo
from tests.repository_test import _get_hook_no_install
@@ -133,7 +134,7 @@ def test_r_parsing_file_local(tempdir_factory, store):
def test_rscript_exec_relative_to_r_home():
- expected = os.path.join('r_home_dir', 'bin', 'Rscript')
+ expected = os.path.join('r_home_dir', 'bin', win_exe('Rscript'))
with envcontext.envcontext((('R_HOME', 'r_home_dir'),)):
assert r._rscript_exec() == expected
diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py
index dc55456..29f3c80 100644
--- a/tests/languages/ruby_test.py
+++ b/tests/languages/ruby_test.py
@@ -71,10 +71,10 @@ def test_install_ruby_default(fake_gem_prefix):
@xfailif_windows # pragma: win32 no cover
def test_install_ruby_with_version(fake_gem_prefix):
- ruby.install_environment(fake_gem_prefix, '2.7.2', ())
+ ruby.install_environment(fake_gem_prefix, '3.1.0', ())
# Should be able to activate and use rbenv install
- with ruby.in_env(fake_gem_prefix, '2.7.2'):
+ with ruby.in_env(fake_gem_prefix, '3.1.0'):
cmd_output('rbenv', 'install', '--help')
diff --git a/tests/languages/rust_test.py b/tests/languages/rust_test.py
new file mode 100644
index 0000000..f011e71
--- /dev/null
+++ b/tests/languages/rust_test.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+import pre_commit.constants as C
+from pre_commit import parse_shebang
+from pre_commit.languages import rust
+from pre_commit.prefix import Prefix
+from pre_commit.util import cmd_output
+
+ACTUAL_GET_DEFAULT_VERSION = rust.get_default_version.__wrapped__
+
+
+@pytest.fixture
+def cmd_output_b_mck():
+ with mock.patch.object(rust, 'cmd_output_b') as mck:
+ yield mck
+
+
+def test_sets_system_when_rust_is_available(cmd_output_b_mck):
+ cmd_output_b_mck.return_value = (0, b'', b'')
+ assert ACTUAL_GET_DEFAULT_VERSION() == 'system'
+
+
+def test_uses_default_when_rust_is_not_available(cmd_output_b_mck):
+ cmd_output_b_mck.return_value = (127, b'', b'error: not found')
+ assert ACTUAL_GET_DEFAULT_VERSION() == C.DEFAULT
+
+
+@pytest.mark.parametrize('language_version', (C.DEFAULT, '1.56.0'))
+def test_installs_with_bootstrapped_rustup(tmpdir, language_version):
+ tmpdir.join('src', 'main.rs').ensure().write(
+ 'fn main() {\n'
+ ' println!("Hello, world!");\n'
+ '}\n',
+ )
+ tmpdir.join('Cargo.toml').ensure().write(
+ '[package]\n'
+ 'name = "hello_world"\n'
+ 'version = "0.1.0"\n'
+ 'edition = "2021"\n',
+ )
+ prefix = Prefix(str(tmpdir))
+
+ find_executable_exes = []
+
+ original_find_executable = parse_shebang.find_executable
+
+ def mocked_find_executable(exe: str) -> str | None:
+ """
+ Return `None` the first time `find_executable` is called to ensure
+ that the bootstrapping code is executed, then just let the function
+ work as normal.
+
+ Also log the arguments to ensure that everything works as expected.
+ """
+ find_executable_exes.append(exe)
+ if len(find_executable_exes) == 1:
+ return None
+ return original_find_executable(exe)
+
+ with mock.patch.object(parse_shebang, 'find_executable') as find_exe_mck:
+ find_exe_mck.side_effect = mocked_find_executable
+ rust.install_environment(prefix, language_version, ())
+ assert find_executable_exes == ['rustup', 'rustup', 'cargo']
+
+ with rust.in_env(prefix, language_version):
+ assert cmd_output('hello_world')[1] == 'Hello, world!\n'
+
+
+def test_installs_with_existing_rustup(tmpdir):
+ tmpdir.join('src', 'main.rs').ensure().write(
+ 'fn main() {\n'
+ ' println!("Hello, world!");\n'
+ '}\n',
+ )
+ tmpdir.join('Cargo.toml').ensure().write(
+ '[package]\n'
+ 'name = "hello_world"\n'
+ 'version = "0.1.0"\n'
+ 'edition = "2021"\n',
+ )
+ prefix = Prefix(str(tmpdir))
+
+ assert parse_shebang.find_executable('rustup') is not None
+ rust.install_environment(prefix, '1.56.0', ())
+ with rust.in_env(prefix, '1.56.0'):
+ assert cmd_output('hello_world')[1] == 'Hello, world!\n'
diff --git a/tests/main_test.py b/tests/main_test.py
index a7afd6d..5115926 100644
--- a/tests/main_test.py
+++ b/tests/main_test.py
@@ -17,6 +17,8 @@ from testing.util import cwd
def _args(**kwargs):
kwargs.setdefault('command', 'help')
kwargs.setdefault('config', C.CONFIG_FILE)
+ if kwargs['command'] in {'run', 'try-repo'}:
+ kwargs.setdefault('commit_msg_filename', None)
return argparse.Namespace(**kwargs)
@@ -35,13 +37,24 @@ def test_adjust_args_and_chdir_noop(in_git_dir):
def test_adjust_args_and_chdir_relative_things(in_git_dir):
in_git_dir.join('foo/cfg.yaml').ensure()
- in_git_dir.join('foo').chdir()
-
- args = _args(command='run', files=['f1', 'f2'], config='cfg.yaml')
- main._adjust_args_and_chdir(args)
- assert os.getcwd() == in_git_dir
- assert args.config == os.path.join('foo', 'cfg.yaml')
- assert args.files == [os.path.join('foo', 'f1'), os.path.join('foo', 'f2')]
+ with in_git_dir.join('foo').as_cwd():
+ args = _args(command='run', files=['f1', 'f2'], config='cfg.yaml')
+ main._adjust_args_and_chdir(args)
+ assert os.getcwd() == in_git_dir
+ assert args.config == os.path.join('foo', 'cfg.yaml')
+ assert args.files == [
+ os.path.join('foo', 'f1'),
+ os.path.join('foo', 'f2'),
+ ]
+
+
+def test_adjust_args_and_chdir_relative_commit_msg(in_git_dir):
+ in_git_dir.join('foo/cfg.yaml').ensure()
+ with in_git_dir.join('foo').as_cwd():
+ args = _args(command='run', files=[], commit_msg_filename='t.txt')
+ main._adjust_args_and_chdir(args)
+ assert os.getcwd() == in_git_dir
+ assert args.commit_msg_filename == os.path.join('foo', 't.txt')
@pytest.mark.skipif(os.name != 'nt', reason='windows feature')
@@ -56,24 +69,22 @@ def test_install_on_subst(in_git_dir, store): # pragma: posix no cover
def test_adjust_args_and_chdir_non_relative_config(in_git_dir):
- in_git_dir.join('foo').ensure_dir().chdir()
-
- args = _args()
- main._adjust_args_and_chdir(args)
- assert os.getcwd() == in_git_dir
- assert args.config == C.CONFIG_FILE
+ with in_git_dir.join('foo').ensure_dir().as_cwd():
+ args = _args()
+ main._adjust_args_and_chdir(args)
+ assert os.getcwd() == in_git_dir
+ assert args.config == C.CONFIG_FILE
def test_adjust_args_try_repo_repo_relative(in_git_dir):
- in_git_dir.join('foo').ensure_dir().chdir()
-
- args = _args(command='try-repo', repo='../foo', files=[])
- assert args.repo is not None
- assert os.path.exists(args.repo)
- main._adjust_args_and_chdir(args)
- assert os.getcwd() == in_git_dir
- assert os.path.exists(args.repo)
- assert args.repo == 'foo'
+ with in_git_dir.join('foo').ensure_dir().as_cwd():
+ args = _args(command='try-repo', repo='../foo', files=[])
+ assert args.repo is not None
+ assert os.path.exists(args.repo)
+ main._adjust_args_and_chdir(args)
+ assert os.getcwd() == in_git_dir
+ assert os.path.exists(args.repo)
+ assert args.repo == 'foo'
FNS = (
diff --git a/tests/repository_test.py b/tests/repository_test.py
index 11d452c..c3936bf 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -173,30 +173,20 @@ def test_python_venv(tempdir_factory, store):
)
-@xfailif_windows # pragma: win32 no cover # no python 2 in GHA
-def test_switch_language_versions_doesnt_clobber(tempdir_factory, store):
- # We're using the python3 repo because it prints the python version
- path = make_repo(tempdir_factory, 'python3_hooks_repo')
-
- def run_on_version(version, expected_output):
- config = make_config_from_repo(path)
- config['hooks'][0]['language_version'] = version
- hook = _get_hook(config, store, 'python3-hook')
- ret, out = _hook_run(hook, [], color=False)
- assert ret == 0
- assert _norm_out(out) == expected_output
-
- run_on_version('python2', b'2\n[]\nHello World\n')
- run_on_version('python3', b'3\n[]\nHello World\n')
-
-
-def test_versioned_python_hook(tempdir_factory, store):
- _test_hook_repo(
- tempdir_factory, store, 'python3_hooks_repo',
- 'python3-hook',
- [os.devnull],
- f'3\n[{os.devnull!r}]\nHello World\n'.encode(),
- )
+def test_language_versioned_python_hook(tempdir_factory, store):
+ # we patch this force virtualenv executing with `-p` since we can't
+ # reliably have multiple pythons available in CI
+ with mock.patch.object(
+ python,
+ '_sys_executable_matches',
+ return_value=False,
+ ):
+ _test_hook_repo(
+ tempdir_factory, store, 'python3_hooks_repo',
+ 'python3-hook',
+ [os.devnull],
+ f'3\n[{os.devnull!r}]\nHello World\n'.encode(),
+ )
@skipif_cant_run_coursier # pragma: win32 no cover
@@ -345,7 +335,7 @@ def test_run_versioned_ruby_hook(tempdir_factory, store):
tempdir_factory, store, 'ruby_versioned_hooks_repo',
'ruby_hook',
[os.devnull],
- b'2.5.1\nHello world from a ruby hook\n',
+ b'3.1.0\nHello world from a ruby hook\n',
)
@@ -367,7 +357,7 @@ def test_run_ruby_hook_with_disable_shared_gems(
tempdir_factory, store, 'ruby_versioned_hooks_repo',
'ruby_hook',
[os.devnull],
- b'2.5.1\nHello world from a ruby hook\n',
+ b'3.1.0\nHello world from a ruby hook\n',
)
@@ -471,7 +461,7 @@ def test_additional_rust_cli_dependencies_installed(
hook = _get_hook(config, store, 'rust-hook')
binaries = os.listdir(
hook.prefix.path(
- helpers.environment_dir(rust.ENVIRONMENT_DIR, C.DEFAULT), 'bin',
+ helpers.environment_dir(rust.ENVIRONMENT_DIR, 'system'), 'bin',
),
)
# normalize for windows
@@ -485,12 +475,12 @@ def test_additional_rust_lib_dependencies_installed(
path = make_repo(tempdir_factory, 'rust_hooks_repo')
config = make_config_from_repo(path)
# A small rust package with no dependencies.
- deps = ['shellharden:3.1.0']
+ deps = ['shellharden:3.1.0', 'git-version']
config['hooks'][0]['additional_dependencies'] = deps
hook = _get_hook(config, store, 'rust-hook')
binaries = os.listdir(
hook.prefix.path(
- helpers.environment_dir(rust.ENVIRONMENT_DIR, C.DEFAULT), 'bin',
+ helpers.environment_dir(rust.ENVIRONMENT_DIR, 'system'), 'bin',
),
)
# normalize for windows
@@ -883,7 +873,7 @@ def test_tags_on_repositories(in_tmpdir, tempdir_factory, store):
@pytest.fixture
def local_python_config():
# Make a "local" hooks repo that just installs our other hooks repo
- repo_path = get_resource_path('python3_hooks_repo')
+ repo_path = get_resource_path('python_hooks_repo')
manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE))
hooks = [
dict(hook, additional_dependencies=[repo_path]) for hook in manifest
@@ -892,23 +882,12 @@ def local_python_config():
def test_local_python_repo(store, local_python_config):
- hook = _get_hook(local_python_config, store, 'python3-hook')
- # language_version should have been adjusted to the interpreter version
- assert hook.language_version != C.DEFAULT
- ret, out = _hook_run(hook, ('filename',), color=False)
- assert ret == 0
- assert _norm_out(out) == b"3\n['filename']\nHello World\n"
-
-
-@xfailif_windows # pragma: win32 no cover # no python2 in GHA
-def test_local_python_repo_python2(store, local_python_config):
- local_python_config['hooks'][0]['language_version'] = 'python2'
- hook = _get_hook(local_python_config, store, 'python3-hook')
+ hook = _get_hook(local_python_config, store, 'foo')
# language_version should have been adjusted to the interpreter version
assert hook.language_version != C.DEFAULT
ret, out = _hook_run(hook, ('filename',), color=False)
assert ret == 0
- assert _norm_out(out) == b"2\n['filename']\nHello World\n"
+ assert _norm_out(out) == b"['filename']\nHello World\n"
def test_default_language_version(store, local_python_config):
@@ -1052,6 +1031,7 @@ def test_local_perl_additional_dependencies(store):
'dotnet_hooks_csproj_repo',
'dotnet_hooks_sln_repo',
'dotnet_hooks_combo_repo',
+ 'dotnet_hooks_csproj_prefix_repo',
),
)
def test_dotnet_hook(tempdir_factory, store, repo):
diff --git a/tests/store_test.py b/tests/store_test.py
index ff671a8..8187766 100644
--- a/tests/store_test.py
+++ b/tests/store_test.py
@@ -127,7 +127,7 @@ def test_clone_shallow_failure_fallback_to_complete(
# Force shallow clone failure
def fake_shallow_clone(self, *args, **kwargs):
- raise CalledProcessError(1, (), 0, b'', None)
+ raise CalledProcessError(1, (), b'', None)
store._shallow_clone = fake_shallow_clone
ret = store.clone(path, rev)
diff --git a/tests/util_test.py b/tests/util_test.py
index 6b00f9f..b3f18b4 100644
--- a/tests/util_test.py
+++ b/tests/util_test.py
@@ -18,11 +18,10 @@ from pre_commit.util import tmpdir
def test_CalledProcessError_str():
- error = CalledProcessError(1, ('exe',), 0, b'output', b'errors')
+ error = CalledProcessError(1, ('exe',), b'output', b'errors')
assert str(error) == (
"command: ('exe',)\n"
'return code: 1\n'
- 'expected return code: 0\n'
'stdout:\n'
' output\n'
'stderr:\n'
@@ -31,11 +30,10 @@ def test_CalledProcessError_str():
def test_CalledProcessError_str_nooutput():
- error = CalledProcessError(1, ('exe',), 0, b'', b'')
+ error = CalledProcessError(1, ('exe',), b'', b'')
assert str(error) == (
"command: ('exe',)\n"
'return code: 1\n'
- 'expected return code: 0\n'
'stdout: (none)\n'
'stderr: (none)'
)
@@ -83,14 +81,14 @@ def test_tmpdir():
def test_cmd_output_exe_not_found():
- ret, out, _ = cmd_output('dne', retcode=None)
+ ret, out, _ = cmd_output('dne', check=False)
assert ret == 1
assert out == 'Executable `dne` not found'
@pytest.mark.parametrize('fn', (cmd_output_b, cmd_output_p))
def test_cmd_output_exe_not_found_bytes(fn):
- ret, out, _ = fn('dne', retcode=None, stderr=subprocess.STDOUT)
+ ret, out, _ = fn('dne', check=False, stderr=subprocess.STDOUT)
assert ret == 1
assert out == b'Executable `dne` not found'
@@ -101,7 +99,7 @@ def test_cmd_output_no_shebang(tmpdir, fn):
make_executable(f)
# previously this raised `OSError` -- the output is platform specific
- ret, out, _ = fn(str(f), retcode=None, stderr=subprocess.STDOUT)
+ ret, out, _ = fn(str(f), check=False, stderr=subprocess.STDOUT)
assert ret == 1
assert isinstance(out, bytes)
assert out.endswith(b'\n')
diff --git a/tox.ini b/tox.ini
index 7f43e41..e06be11 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,7 +3,7 @@ envlist = py37,py38,pypy3,pre-commit
[testenv]
deps = -rrequirements-dev.txt
-passenv = APPDATA HOME LOCALAPPDATA PROGRAMFILES RUSTUP_HOME
+passenv = *
commands =
coverage erase
coverage run -m pytest {posargs:tests}
@@ -23,5 +23,6 @@ env =
GIT_COMMITTER_NAME=test
GIT_AUTHOR_EMAIL=test@example.com
GIT_COMMITTER_EMAIL=test@example.com
+ GIT_ALLOW_PROTOCOL=file
VIRTUALENV_NO_DOWNLOAD=1
PRE_COMMIT_NO_CONCURRENCY=1