summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2022-04-04 18:42:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2022-04-04 18:42:30 +0000
commit9d68e4e3da4ce68e28506d926c7de9fd6ffbf6a3 (patch)
treea6d016823a24941dd795d30ba84409db12aa41cb
parentReleasing debian version 2.17.0-1. (diff)
downloadpre-commit-9d68e4e3da4ce68e28506d926c7de9fd6ffbf6a3.tar.xz
pre-commit-9d68e4e3da4ce68e28506d926c7de9fd6ffbf6a3.zip
Merging upstream version 2.18.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.gitignore3
-rw-r--r--.pre-commit-config.yaml50
-rw-r--r--CHANGELOG.md50
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--README.md6
-rw-r--r--azure-pipelines.yml4
-rw-r--r--pre_commit/__main__.py2
-rw-r--r--pre_commit/clientlib.py32
-rw-r--r--pre_commit/color.py2
-rw-r--r--pre_commit/commands/autoupdate.py25
-rw-r--r--pre_commit/commands/clean.py2
-rw-r--r--pre_commit/commands/gc.py11
-rw-r--r--pre_commit/commands/hook_impl.py30
-rw-r--r--pre_commit/commands/init_templatedir.py5
-rw-r--r--pre_commit/commands/install_uninstall.py36
-rw-r--r--pre_commit/commands/migrate_config.py2
-rw-r--r--pre_commit/commands/run.py26
-rw-r--r--pre_commit/commands/sample_config.py1
-rw-r--r--pre_commit/commands/try_repo.py6
-rw-r--r--pre_commit/constants.py8
-rw-r--r--pre_commit/envcontext.py5
-rw-r--r--pre_commit/error_handler.py5
-rw-r--r--pre_commit/errors.py3
-rw-r--r--pre_commit/file_lock.py5
-rw-r--r--pre_commit/git.py66
-rw-r--r--pre_commit/hook.py10
-rw-r--r--pre_commit/languages/all.py8
-rw-r--r--pre_commit/languages/conda.py5
-rw-r--r--pre_commit/languages/coursier.py16
-rw-r--r--pre_commit/languages/dart.py7
-rw-r--r--pre_commit/languages/docker.py11
-rw-r--r--pre_commit/languages/docker_image.py5
-rw-r--r--pre_commit/languages/dotnet.py5
-rw-r--r--pre_commit/languages/fail.py5
-rw-r--r--pre_commit/languages/golang.py5
-rw-r--r--pre_commit/languages/helpers.py27
-rw-r--r--pre_commit/languages/lua.py5
-rw-r--r--pre_commit/languages/node.py5
-rw-r--r--pre_commit/languages/perl.py5
-rw-r--r--pre_commit/languages/pygrep.py8
-rw-r--r--pre_commit/languages/python.py17
-rw-r--r--pre_commit/languages/r.py38
-rw-r--r--pre_commit/languages/ruby.py5
-rw-r--r--pre_commit/languages/rust.py10
-rw-r--r--pre_commit/languages/script.py5
-rw-r--r--pre_commit/languages/swift.py5
-rw-r--r--pre_commit/languages/system.py5
-rw-r--r--pre_commit/logging_handler.py2
-rw-r--r--pre_commit/main.py46
-rw-r--r--pre_commit/meta_hooks/check_hooks_apply.py5
-rw-r--r--pre_commit/meta_hooks/check_useless_excludes.py5
-rw-r--r--pre_commit/meta_hooks/identity.py5
-rw-r--r--pre_commit/output.py9
-rw-r--r--pre_commit/parse_shebang.py16
-rw-r--r--pre_commit/prefix.py5
-rw-r--r--pre_commit/repository.py39
-rw-r--r--pre_commit/resources/ruby-build.tar.gzbin71151 -> 72271 bytes
-rw-r--r--pre_commit/staged_files_only.py4
-rw-r--r--pre_commit/store.py17
-rw-r--r--pre_commit/util.py54
-rw-r--r--pre_commit/xargs.py21
-rw-r--r--setup.cfg6
-rw-r--r--setup.py2
-rw-r--r--testing/auto_namedtuple.py2
-rw-r--r--testing/fixtures.py2
-rwxr-xr-xtesting/gen-languages-all2
-rwxr-xr-xtesting/make-archives7
-rw-r--r--testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml1
-rw-r--r--testing/resources/python_hooks_repo/foo.py2
-rw-r--r--testing/resources/python_hooks_repo/setup.py2
-rw-r--r--testing/resources/python_venv_hooks_repo/foo.py2
-rw-r--r--testing/resources/python_venv_hooks_repo/setup.py2
-rw-r--r--testing/util.py2
-rw-r--r--testing/zipapp/Dockerfile4
-rwxr-xr-xtesting/zipapp/entry7
-rwxr-xr-xtesting/zipapp/make2
-rwxr-xr-xtesting/zipapp/python7
-rw-r--r--tests/clientlib_test.py2
-rw-r--r--tests/color_test.py2
-rw-r--r--tests/commands/autoupdate_test.py20
-rw-r--r--tests/commands/clean_test.py2
-rw-r--r--tests/commands/gc_test.py2
-rw-r--r--tests/commands/hook_impl_test.py2
-rw-r--r--tests/commands/init_templatedir_test.py2
-rw-r--r--tests/commands/install_uninstall_test.py56
-rw-r--r--tests/commands/migrate_config_test.py2
-rw-r--r--tests/commands/run_test.py2
-rw-r--r--tests/commands/sample_config_test.py2
-rw-r--r--tests/commands/try_repo_test.py2
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/envcontext_test.py2
-rw-r--r--tests/error_handler_test.py2
-rw-r--r--tests/git_test.py32
-rw-r--r--tests/languages/conda_test.py2
-rw-r--r--tests/languages/docker_test.py2
-rw-r--r--tests/languages/golang_test.py2
-rw-r--r--tests/languages/helpers_test.py6
-rw-r--r--tests/languages/node_test.py2
-rw-r--r--tests/languages/pygrep_test.py2
-rw-r--r--tests/languages/python_test.py10
-rw-r--r--tests/languages/r_test.py14
-rw-r--r--tests/languages/ruby_test.py2
-rw-r--r--tests/logging_handler_test.py2
-rw-r--r--tests/main_test.py18
-rw-r--r--tests/meta_hooks/check_hooks_apply_test.py2
-rw-r--r--tests/meta_hooks/check_useless_excludes_test.py2
-rw-r--r--tests/meta_hooks/identity_test.py2
-rw-r--r--tests/output_test.py2
-rw-r--r--tests/parse_shebang_test.py2
-rw-r--r--tests/prefix_test.py2
-rw-r--r--tests/repository_test.py28
-rw-r--r--tests/staged_files_only_test.py2
-rw-r--r--tests/store_test.py2
-rw-r--r--tests/util_test.py2
-rw-r--r--tests/xargs_test.py5
-rw-r--r--tox.ini2
116 files changed, 718 insertions, 410 deletions
diff --git a/.gitignore b/.gitignore
index 4f4f6b9..c202181 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
*.egg-info
*.py[co]
/.coverage
-/.mypy_cache
-/.pytest_cache
/.tox
/dist
-/venv*
.vscode/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 66b50a4..1b93cff 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,52 +4,42 @@ repos:
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- - id: check-docstring-first
- - id: check-json
- id: check-yaml
- id: debug-statements
+ - id: double-quote-string-fixer
- id: name-tests-test
- id: requirements-txt-fixer
- - id: double-quote-string-fixer
-- repo: https://github.com/PyCQA/flake8
- rev: 4.0.1
- hooks:
- - id: flake8
- additional_dependencies: [flake8-typing-imports==1.10.0]
-- repo: https://github.com/pre-commit/mirrors-autopep8
- rev: v1.6.0
- hooks:
- - id: autopep8
-- repo: https://github.com/pre-commit/pre-commit
- rev: v2.17.0
- hooks:
- - id: validate_manifest
-- repo: https://github.com/asottile/pyupgrade
- rev: v2.31.0
+- repo: https://github.com/asottile/setup-cfg-fmt
+ rev: v1.20.0
hooks:
- - id: pyupgrade
- args: [--py36-plus]
+ - id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder_python_imports
- rev: v2.6.0
+ rev: v3.0.1
hooks:
- id: reorder-python-imports
- args: [--py3-plus]
+ 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.1
hooks:
- id: add-trailing-comma
args: [--py36-plus]
-- repo: https://github.com/asottile/setup-cfg-fmt
- rev: v1.20.0
+- repo: https://github.com/asottile/pyupgrade
+ rev: v2.31.1
hooks:
- - id: setup-cfg-fmt
+ - id: pyupgrade
+ args: [--py37-plus]
+- repo: https://github.com/pre-commit/mirrors-autopep8
+ rev: v1.6.0
+ hooks:
+ - id: autopep8
+- repo: https://github.com/PyCQA/flake8
+ rev: 4.0.1
+ hooks:
+ - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.931
+ rev: v0.942
hooks:
- id: mypy
additional_dependencies: [types-all]
exclude: ^testing/resources/
-- repo: meta
- hooks:
- - id: check-hooks-apply
- - id: check-useless-excludes
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d0cccc6..cd31c4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,53 @@
+2.18.1 - 2022-04-02
+===================
+
+### Fixes
+- Fix regression for `repo: local` hooks running `python<3.7`
+ - #2324 PR by @asottile.
+
+2.18.0 - 2022-04-02
+===================
+
+### Features
+- Keep `GIT_HTTP_PROXY_AUTHMETHOD` in git environ.
+ - #2272 PR by @VincentBerthier.
+ - #2271 issue by @VincentBerthier.
+- Support both `cs` and `coursier` executables for coursier hooks.
+ - #2293 PR by @Holzhaus.
+- Include more information in errors for `language_version` /
+ `additional_dependencies` for languages which do not support them.
+ - #2315 PR by @asottile.
+- Have autoupdate preferentially pick tags which look like versions when
+ there are multiple equivalent tags.
+ - #2312 PR by @mblayman.
+ - #2311 issue by @mblayman.
+- Upgrade `ruby-build`.
+ - #2319 PR by @jalessio.
+- Add top level `default_install_hook_types` which will be installed when
+ `--hook-types` is not specified in `pre-commit install`.
+ - #2322 PR by @asottile.
+
+### Fixes
+- Fix typo in help message for `--from-ref` and `--to-ref`.
+ - #2266 PR by @leetrout.
+- Prioritize binary builds for R dependencies.
+ - #2277 PR by @lorenzwalthert.
+- Fix handling of git worktrees.
+ - #2252 PR by @daschuer.
+- Fix handling of `$R_HOME` for R hooks.
+ - #2301 PR by @jeff-m-sullivan.
+ - #2300 issue by @jeff-m-sullivan.
+- Fix a rare race condition in change stashing.
+ - #2323 PR by @asottile.
+ - #2287 issue by @ian-h-chamberlain.
+
+### Updating
+- Remove python3.6 support. Note that pre-commit still supports running hooks
+ written in older versions, but pre-commit itself requires python 3.7+.
+ - #2215 PR by @asottile.
+- pre-commit has migrated from the `master` branch to `main`.
+ - #2302 PR by @asottile.
+
2.17.0 - 2022-01-18
===================
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 76df437..adce08f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -93,7 +93,7 @@ language, for example:
here are the apis that should be implemented for a language
-Note that these are also documented in [`pre_commit/languages/all.py`](https://github.com/pre-commit/pre-commit/blob/master/pre_commit/languages/all.py)
+Note that these are also documented in [`pre_commit/languages/all.py`](https://github.com/pre-commit/pre-commit/blob/main/pre_commit/languages/all.py)
#### `ENVIRONMENT_DIR`
diff --git a/README.md b/README.md
index de7032c..db1259c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/pre-commit.pre-commit?branchName=master)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master)
-[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master)
-[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit/master.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/master)
+[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/pre-commit.pre-commit?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main)
+[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main)
+[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit/main.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/main)
## pre-commit
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index d8cbd11..afb2982 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -1,6 +1,6 @@
trigger:
branches:
- include: [master, test-me-*]
+ include: [main, test-me-*]
tags:
include: ['*']
@@ -50,7 +50,7 @@ jobs:
displayName: install R
- template: job--python-tox.yml@asottile
parameters:
- toxenvs: [pypy3, py36, py37, py38, py39]
+ toxenvs: [py37, py38, py39]
os: linux
pre_test:
- task: UseRubyVersion@0
diff --git a/pre_commit/__main__.py b/pre_commit/__main__.py
index 83bd93c..bda61ee 100644
--- a/pre_commit/__main__.py
+++ b/pre_commit/__main__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit.main import main
diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py
index 47ebd54..bf4e2e4 100644
--- a/pre_commit/clientlib.py
+++ b/pre_commit/clientlib.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import argparse
import functools
import logging
@@ -5,8 +7,6 @@ import re
import shlex
import sys
from typing import Any
-from typing import Dict
-from typing import Optional
from typing import Sequence
import cfgv
@@ -95,7 +95,7 @@ load_manifest = functools.partial(
)
-def validate_manifest_main(argv: Optional[Sequence[str]] = None) -> int:
+def validate_manifest_main(argv: Sequence[str] | None = None) -> int:
parser = _make_argparser('Manifest filenames.')
args = parser.parse_args(argv)
@@ -116,7 +116,7 @@ META = 'meta'
# should inherit from cfgv.Conditional if sha support is dropped
class WarnMutableRev(cfgv.ConditionalOptional):
- def check(self, dct: Dict[str, Any]) -> None:
+ def check(self, dct: dict[str, Any]) -> None:
super().check(dct)
if self.key in dct:
@@ -135,7 +135,7 @@ class WarnMutableRev(cfgv.ConditionalOptional):
class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
- def check(self, dct: Dict[str, Any]) -> None:
+ def check(self, dct: dict[str, Any]) -> None:
super().check(dct)
if '/*' in dct.get(self.key, ''):
@@ -154,7 +154,7 @@ class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
- def check(self, dct: Dict[str, Any]) -> None:
+ def check(self, dct: dict[str, Any]) -> None:
super().check(dct)
if '/*' in dct.get(self.key, ''):
@@ -183,7 +183,7 @@ class MigrateShaToRev:
ensure_absent=True,
)
- def check(self, dct: Dict[str, Any]) -> None:
+ def check(self, dct: dict[str, Any]) -> None:
if dct.get('repo') in {LOCAL, META}:
self._cond('rev').check(dct)
self._cond('sha').check(dct)
@@ -194,7 +194,7 @@ class MigrateShaToRev:
else:
self._cond('rev').check(dct)
- def apply_default(self, dct: Dict[str, Any]) -> None:
+ def apply_default(self, dct: dict[str, Any]) -> None:
if 'sha' in dct:
dct['rev'] = dct.pop('sha')
@@ -212,7 +212,7 @@ def _entry(modname: str) -> str:
def warn_unknown_keys_root(
extra: Sequence[str],
orig_keys: Sequence[str],
- dct: Dict[str, str],
+ dct: dict[str, str],
) -> None:
logger.warning(f'Unexpected key(s) present at root: {", ".join(extra)}')
@@ -220,7 +220,7 @@ def warn_unknown_keys_root(
def warn_unknown_keys_repo(
extra: Sequence[str],
orig_keys: Sequence[str],
- dct: Dict[str, str],
+ dct: dict[str, str],
) -> None:
logger.warning(
f'Unexpected key(s) present on {dct["repo"]}: {", ".join(extra)}',
@@ -253,7 +253,7 @@ _meta = (
class NotAllowed(cfgv.OptionalNoDefault):
- def check(self, dct: Dict[str, Any]) -> None:
+ def check(self, dct: dict[str, Any]) -> None:
if self.key in dct:
raise cfgv.ValidationError(f'{self.key!r} cannot be overridden')
@@ -336,6 +336,11 @@ CONFIG_SCHEMA = cfgv.Map(
'Config', None,
cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)),
+ cfgv.Optional(
+ 'default_install_hook_types',
+ cfgv.check_array(cfgv.check_one_of(C.HOOK_TYPES)),
+ ['pre-commit'],
+ ),
cfgv.OptionalRecurse(
'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
),
@@ -355,6 +360,7 @@ CONFIG_SCHEMA = cfgv.Map(
cfgv.WarnAdditionalKeys(
(
'repos',
+ 'default_install_hook_types',
'default_language_version',
'default_stages',
'files',
@@ -377,7 +383,7 @@ class InvalidConfigError(FatalError):
pass
-def ordered_load_normalize_legacy_config(contents: str) -> Dict[str, Any]:
+def ordered_load_normalize_legacy_config(contents: str) -> dict[str, Any]:
data = yaml_load(contents)
if isinstance(data, list):
logger.warning(
@@ -398,7 +404,7 @@ load_config = functools.partial(
)
-def validate_config_main(argv: Optional[Sequence[str]] = None) -> int:
+def validate_config_main(argv: Sequence[str] | None = None) -> int:
parser = _make_argparser('Config filenames.')
args = parser.parse_args(argv)
diff --git a/pre_commit/color.py b/pre_commit/color.py
index 4ddfdf5..2d6f248 100644
--- a/pre_commit/color.py
+++ b/pre_commit/color.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import argparse
import os
import sys
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py
index 5cb978e..d5352e5 100644
--- a/pre_commit/commands/autoupdate.py
+++ b/pre_commit/commands/autoupdate.py
@@ -1,12 +1,10 @@
+from __future__ import annotations
+
import os.path
import re
from typing import Any
-from typing import Dict
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit import git
@@ -29,13 +27,13 @@ from pre_commit.util import yaml_load
class RevInfo(NamedTuple):
repo: str
rev: str
- frozen: Optional[str]
+ frozen: str | None
@classmethod
- def from_config(cls, config: Dict[str, Any]) -> 'RevInfo':
+ def from_config(cls, config: dict[str, Any]) -> RevInfo:
return cls(config['repo'], config['rev'], None)
- def update(self, tags_only: bool, freeze: bool) -> 'RevInfo':
+ def update(self, tags_only: bool, freeze: bool) -> RevInfo:
git_cmd = ('git', *git.NO_FS_MONITOR)
if tags_only:
@@ -61,6 +59,9 @@ class RevInfo(NamedTuple):
except CalledProcessError:
cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD')
rev = cmd_output(*cmd, cwd=tmp)[1].strip()
+ else:
+ if tags_only:
+ rev = git.get_best_candidate_tag(rev, tmp)
frozen = None
if freeze:
@@ -76,7 +77,7 @@ class RepositoryCannotBeUpdatedError(RuntimeError):
def _check_hooks_still_exist_at_rev(
- repo_config: Dict[str, Any],
+ repo_config: dict[str, Any],
info: RevInfo,
store: Store,
) -> None:
@@ -101,9 +102,9 @@ REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$')
def _original_lines(
path: str,
- rev_infos: List[Optional[RevInfo]],
+ rev_infos: list[RevInfo | None],
retry: bool = False,
-) -> Tuple[List[str], List[int]]:
+) -> tuple[list[str], list[int]]:
"""detect `rev:` lines or reformat the file"""
with open(path, newline='') as f:
original = f.read()
@@ -120,7 +121,7 @@ def _original_lines(
return _original_lines(path, rev_infos, retry=True)
-def _write_new_config(path: str, rev_infos: List[Optional[RevInfo]]) -> None:
+def _write_new_config(path: str, rev_infos: list[RevInfo | None]) -> None:
lines, idxs = _original_lines(path, rev_infos)
for idx, rev_info in zip(idxs, rev_infos):
@@ -152,7 +153,7 @@ def autoupdate(
"""Auto-update the pre-commit config to the latest versions of repos."""
migrate_config(config_file, quiet=True)
retv = 0
- rev_infos: List[Optional[RevInfo]] = []
+ rev_infos: list[RevInfo | None] = []
changed = False
config = load_config(config_file)
diff --git a/pre_commit/commands/clean.py b/pre_commit/commands/clean.py
index 2be6c16..5119f64 100644
--- a/pre_commit/commands/clean.py
+++ b/pre_commit/commands/clean.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
from pre_commit import output
diff --git a/pre_commit/commands/gc.py b/pre_commit/commands/gc.py
index 7f6d311..6892e09 100644
--- a/pre_commit/commands/gc.py
+++ b/pre_commit/commands/gc.py
@@ -1,8 +1,7 @@
+from __future__ import annotations
+
import os.path
from typing import Any
-from typing import Dict
-from typing import Set
-from typing import Tuple
import pre_commit.constants as C
from pre_commit import output
@@ -17,9 +16,9 @@ from pre_commit.store import Store
def _mark_used_repos(
store: Store,
- all_repos: Dict[Tuple[str, str], str],
- unused_repos: Set[Tuple[str, str]],
- repo: Dict[str, Any],
+ all_repos: dict[tuple[str, str], str],
+ unused_repos: set[tuple[str, str]],
+ repo: dict[str, Any],
) -> None:
if repo['repo'] == META:
return
diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py
index 90bb33b..18e5e9f 100644
--- a/pre_commit/commands/hook_impl.py
+++ b/pre_commit/commands/hook_impl.py
@@ -1,10 +1,10 @@
+from __future__ import annotations
+
import argparse
import os.path
import subprocess
import sys
-from typing import Optional
from typing import Sequence
-from typing import Tuple
from pre_commit.commands.run import run
from pre_commit.envcontext import envcontext
@@ -18,7 +18,7 @@ def _run_legacy(
hook_type: str,
hook_dir: str,
args: Sequence[str],
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'):
raise SystemExit(
f"bug: pre-commit's script is installed in migration mode\n"
@@ -69,16 +69,16 @@ def _ns(
color: bool,
*,
all_files: bool = False,
- remote_branch: Optional[str] = None,
- local_branch: Optional[str] = None,
- from_ref: Optional[str] = None,
- to_ref: Optional[str] = None,
- remote_name: Optional[str] = None,
- remote_url: Optional[str] = None,
- commit_msg_filename: Optional[str] = None,
- checkout_type: Optional[str] = None,
- is_squash_merge: Optional[str] = None,
- rewrite_command: Optional[str] = None,
+ remote_branch: str | None = None,
+ local_branch: str | None = None,
+ from_ref: str | None = None,
+ to_ref: str | None = None,
+ remote_name: str | None = None,
+ remote_url: str | None = None,
+ commit_msg_filename: str | None = None,
+ checkout_type: str | None = None,
+ is_squash_merge: str | None = None,
+ rewrite_command: str | None = None,
) -> argparse.Namespace:
return argparse.Namespace(
color=color,
@@ -109,7 +109,7 @@ def _pre_push_ns(
color: bool,
args: Sequence[str],
stdin: bytes,
-) -> Optional[argparse.Namespace]:
+) -> argparse.Namespace | None:
remote_name = args[0]
remote_url = args[1]
@@ -197,7 +197,7 @@ def _run_ns(
color: bool,
args: Sequence[str],
stdin: bytes,
-) -> Optional[argparse.Namespace]:
+) -> argparse.Namespace | None:
_check_args_length(hook_type, args)
if hook_type == 'pre-push':
return _pre_push_ns(color, args, stdin)
diff --git a/pre_commit/commands/init_templatedir.py b/pre_commit/commands/init_templatedir.py
index 5f17d9c..08af656 100644
--- a/pre_commit/commands/init_templatedir.py
+++ b/pre_commit/commands/init_templatedir.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import logging
import os.path
-from typing import Sequence
from pre_commit.commands.install_uninstall import install
from pre_commit.store import Store
@@ -14,7 +15,7 @@ def init_templatedir(
config_file: str,
store: Store,
directory: str,
- hook_types: Sequence[str],
+ hook_types: list[str] | None,
skip_on_missing_config: bool = True,
) -> int:
install(
diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py
index 50c6443..5ff6cba 100644
--- a/pre_commit/commands/install_uninstall.py
+++ b/pre_commit/commands/install_uninstall.py
@@ -1,14 +1,14 @@
+from __future__ import annotations
+
import logging
import os.path
import shlex
import shutil
import sys
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
from pre_commit import git
from pre_commit import output
+from pre_commit.clientlib import InvalidConfigError
from pre_commit.clientlib import load_config
from pre_commit.repository import all_hooks
from pre_commit.repository import install_hook_envs
@@ -32,11 +32,23 @@ TEMPLATE_START = '# start templated\n'
TEMPLATE_END = '# end templated\n'
+def _hook_types(cfg_filename: str, hook_types: list[str] | None) -> list[str]:
+ if hook_types is not None:
+ return hook_types
+ else:
+ try:
+ cfg = load_config(cfg_filename)
+ except InvalidConfigError:
+ return ['pre-commit']
+ else:
+ return cfg['default_install_hook_types']
+
+
def _hook_paths(
hook_type: str,
- git_dir: Optional[str] = None,
-) -> Tuple[str, str]:
- git_dir = git_dir if git_dir is not None else git.get_git_dir()
+ git_dir: str | None = None,
+) -> tuple[str, str]:
+ git_dir = git_dir if git_dir is not None else git.get_git_common_dir()
pth = os.path.join(git_dir, 'hooks', hook_type)
return pth, f'{pth}.legacy'
@@ -54,7 +66,7 @@ def _install_hook_script(
hook_type: str,
overwrite: bool = False,
skip_on_missing_config: bool = False,
- git_dir: Optional[str] = None,
+ git_dir: str | None = None,
) -> None:
hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir)
@@ -103,11 +115,11 @@ def _install_hook_script(
def install(
config_file: str,
store: Store,
- hook_types: Sequence[str],
+ hook_types: list[str] | None,
overwrite: bool = False,
hooks: bool = False,
skip_on_missing_config: bool = False,
- git_dir: Optional[str] = None,
+ git_dir: str | None = None,
) -> int:
if git_dir is None and git.has_core_hookpaths_set():
logger.error(
@@ -116,7 +128,7 @@ def install(
)
return 1
- for hook_type in hook_types:
+ for hook_type in _hook_types(config_file, hook_types):
_install_hook_script(
config_file, hook_type,
overwrite=overwrite,
@@ -150,7 +162,7 @@ def _uninstall_hook_script(hook_type: str) -> None:
output.write_line(f'Restored previous hooks to {hook_path}')
-def uninstall(hook_types: Sequence[str]) -> int:
- for hook_type in hook_types:
+def uninstall(config_file: str, hook_types: list[str] | None) -> int:
+ for hook_type in _hook_types(config_file, hook_types):
_uninstall_hook_script(hook_type)
return 0
diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py
index fef14cd..c3d0a50 100644
--- a/pre_commit/commands/migrate_config.py
+++ b/pre_commit/commands/migrate_config.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import re
import textwrap
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index f8ced0f..37f989b 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import argparse
import contextlib
import functools
@@ -9,12 +11,8 @@ import time
import unicodedata
from typing import Any
from typing import Collection
-from typing import Dict
-from typing import List
from typing import MutableMapping
from typing import Sequence
-from typing import Set
-from typing import Tuple
from identify.identify import tags_from_path
@@ -62,7 +60,7 @@ def filter_by_include_exclude(
names: Collection[str],
include: str,
exclude: str,
-) -> List[str]:
+) -> list[str]:
include_re, exclude_re = re.compile(include), re.compile(exclude)
return [
filename for filename in names
@@ -76,7 +74,7 @@ class Classifier:
self.filenames = [f for f in filenames if os.path.lexists(f)]
@functools.lru_cache(maxsize=None)
- def _types_for_file(self, filename: str) -> Set[str]:
+ def _types_for_file(self, filename: str) -> set[str]:
return tags_from_path(filename)
def by_types(
@@ -85,7 +83,7 @@ class Classifier:
types: Collection[str],
types_or: Collection[str],
exclude_types: Collection[str],
- ) -> List[str]:
+ ) -> list[str]:
types = frozenset(types)
types_or = frozenset(types_or)
exclude_types = frozenset(exclude_types)
@@ -100,7 +98,7 @@ class Classifier:
ret.append(filename)
return ret
- def filenames_for_hook(self, hook: Hook) -> Tuple[str, ...]:
+ def filenames_for_hook(self, hook: Hook) -> tuple[str, ...]:
names = self.filenames
names = filter_by_include_exclude(names, hook.files, hook.exclude)
names = self.by_types(
@@ -117,7 +115,7 @@ class Classifier:
filenames: Collection[str],
include: str,
exclude: str,
- ) -> 'Classifier':
+ ) -> Classifier:
# on windows we normalize all filenames to use forward slashes
# this makes it easier to filter using the `files:` regex
# this also makes improperly quoted shell-based hooks work better
@@ -128,7 +126,7 @@ class Classifier:
return Classifier(filenames)
-def _get_skips(environ: MutableMapping[str, str]) -> Set[str]:
+def _get_skips(environ: MutableMapping[str, str]) -> set[str]:
skips = environ.get('SKIP', '')
return {skip.strip() for skip in skips.split(',') if skip.strip()}
@@ -144,12 +142,12 @@ def _subtle_line(s: str, use_color: bool) -> None:
def _run_single_hook(
classifier: Classifier,
hook: Hook,
- skips: Set[str],
+ skips: set[str],
cols: int,
diff_before: bytes,
verbose: bool,
use_color: bool,
-) -> Tuple[bool, bytes]:
+) -> tuple[bool, bytes]:
filenames = classifier.filenames_for_hook(hook)
if hook.id in skips or hook.alias in skips:
@@ -271,9 +269,9 @@ def _get_diff() -> bytes:
def _run_hooks(
- config: Dict[str, Any],
+ config: dict[str, Any],
hooks: Sequence[Hook],
- skips: Set[str],
+ skips: set[str],
args: argparse.Namespace,
) -> int:
"""Actually run the hooks."""
diff --git a/pre_commit/commands/sample_config.py b/pre_commit/commands/sample_config.py
index 64617c3..82a1617 100644
--- a/pre_commit/commands/sample_config.py
+++ b/pre_commit/commands/sample_config.py
@@ -2,6 +2,7 @@
# 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
# See https://pre-commit.com/hooks.html for more hooks
diff --git a/pre_commit/commands/try_repo.py b/pre_commit/commands/try_repo.py
index 4aee209..ef099f5 100644
--- a/pre_commit/commands/try_repo.py
+++ b/pre_commit/commands/try_repo.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import logging
import os.path
-from typing import Optional
-from typing import Tuple
import pre_commit.constants as C
from pre_commit import git
@@ -18,7 +18,7 @@ from pre_commit.xargs import xargs
logger = logging.getLogger(__name__)
-def _repo_ref(tmpdir: str, repo: str, ref: Optional[str]) -> Tuple[str, str]:
+def _repo_ref(tmpdir: str, repo: str, ref: str | None) -> tuple[str, str]:
# if `ref` is explicitly passed, use it
if ref is not None:
return repo, ref
diff --git a/pre_commit/constants.py b/pre_commit/constants.py
index d2f9363..5bc4ae9 100644
--- a/pre_commit/constants.py
+++ b/pre_commit/constants.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import sys
if sys.version_info >= (3, 8): # pragma: >=3.8 cover
@@ -22,4 +24,10 @@ STAGES = (
'post-rewrite',
)
+HOOK_TYPES = (
+ 'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
+ 'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
+ 'post-rewrite',
+)
+
DEFAULT = 'default'
diff --git a/pre_commit/envcontext.py b/pre_commit/envcontext.py
index 92d975d..4f59560 100644
--- a/pre_commit/envcontext.py
+++ b/pre_commit/envcontext.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import contextlib
import enum
import os
from typing import Generator
from typing import MutableMapping
from typing import NamedTuple
-from typing import Optional
from typing import Tuple
from typing import Union
@@ -32,7 +33,7 @@ def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str:
@contextlib.contextmanager
def envcontext(
patch: PatchesT,
- _env: Optional[MutableMapping[str, str]] = None,
+ _env: MutableMapping[str, str] | None = None,
) -> Generator[None, None, None]:
"""In this context, `os.environ` is modified according to `patch`.
diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py
index 7e74b95..992f5cd 100644
--- a/pre_commit/error_handler.py
+++ b/pre_commit/error_handler.py
@@ -1,9 +1,12 @@
+from __future__ import annotations
+
import contextlib
import functools
import os.path
import sys
import traceback
from typing import Generator
+from typing import IO
import pre_commit.constants as C
from pre_commit import output
@@ -30,7 +33,7 @@ def _log_and_exit(
with contextlib.ExitStack() as ctx:
if os.access(storedir, os.W_OK):
output.write_line(f'Check the log at {log_path}')
- log = ctx.enter_context(open(log_path, 'wb'))
+ log: IO[bytes] = ctx.enter_context(open(log_path, 'wb'))
else: # pragma: win32 no cover
output.write_line(f'Failed to write to log at {log_path}')
log = sys.stdout.buffer
diff --git a/pre_commit/errors.py b/pre_commit/errors.py
index f84d3f1..eac34fa 100644
--- a/pre_commit/errors.py
+++ b/pre_commit/errors.py
@@ -1,2 +1,5 @@
+from __future__ import annotations
+
+
class FatalError(RuntimeError):
pass
diff --git a/pre_commit/file_lock.py b/pre_commit/file_lock.py
index 55a8eb2..f67a586 100644
--- a/pre_commit/file_lock.py
+++ b/pre_commit/file_lock.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import errno
import sys
@@ -20,13 +22,11 @@ if sys.platform == 'win32': # pragma: no cover (windows)
blocked_cb: Callable[[], None],
) -> Generator[None, None, None]:
try:
- # TODO: https://github.com/python/typeshed/pull/3607
msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region)
except OSError:
blocked_cb()
while True:
try:
- # TODO: https://github.com/python/typeshed/pull/3607
msvcrt.locking(fileno, msvcrt.LK_LOCK, _region)
except OSError as e:
# Locking violation. Returned when the _LK_LOCK or _LK_RLCK
@@ -45,7 +45,6 @@ if sys.platform == 'win32': # pragma: no cover (windows)
# The documentation however states:
# "Regions should be locked only briefly and should be unlocked
# before closing a file or exiting the program."
- # TODO: https://github.com/python/typeshed/pull/3607
msvcrt.locking(fileno, msvcrt.LK_UNLCK, _region)
else: # pragma: win32 no cover
import fcntl
diff --git a/pre_commit/git.py b/pre_commit/git.py
index e9ec601..35392b3 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -1,11 +1,9 @@
+from __future__ import annotations
+
import logging
import os.path
import sys
-from typing import Dict
-from typing import List
from typing import MutableMapping
-from typing import Optional
-from typing import Set
from pre_commit.errors import FatalError
from pre_commit.util import CalledProcessError
@@ -18,7 +16,7 @@ logger = logging.getLogger(__name__)
NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false')
-def zsplit(s: str) -> List[str]:
+def zsplit(s: str) -> list[str]:
s = s.strip('\0')
if s:
return s.split('\0')
@@ -27,8 +25,8 @@ def zsplit(s: str) -> List[str]:
def no_git_env(
- _env: Optional[MutableMapping[str, str]] = None,
-) -> Dict[str, str]:
+ _env: MutableMapping[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
@@ -45,6 +43,7 @@ def no_git_env(
k in {
'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT',
+ 'GIT_HTTP_PROXY_AUTHMETHOD',
}
}
@@ -58,13 +57,15 @@ def get_root() -> str:
root = os.path.abspath(
cmd_output('git', 'rev-parse', '--show-cdup')[1].strip(),
)
- git_dir = os.path.abspath(get_git_dir())
+ inside_git_dir = cmd_output(
+ 'git', 'rev-parse', '--is-inside-git-dir',
+ )[1].strip()
except CalledProcessError:
raise FatalError(
'git failed. Is it installed, and are you in a Git repository '
'directory?',
)
- if os.path.samefile(root, git_dir):
+ if inside_git_dir != 'false':
raise FatalError(
'git toplevel unexpectedly empty! make sure you are not '
'inside the `.git` directory of your repository.',
@@ -73,15 +74,25 @@ def get_root() -> str:
def get_git_dir(git_root: str = '.') -> str:
- opts = ('--git-common-dir', '--git-dir')
- _, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root)
- for line, opt in zip(out.splitlines(), opts):
- if line != opt: # pragma: no branch (git < 2.5)
- return os.path.normpath(os.path.join(git_root, line))
+ opt = '--git-dir'
+ _, out, _ = cmd_output('git', 'rev-parse', opt, cwd=git_root)
+ git_dir = out.strip()
+ if git_dir != opt:
+ return os.path.normpath(os.path.join(git_root, git_dir))
else:
raise AssertionError('unreachable: no git dir')
+def get_git_common_dir(git_root: str = '.') -> str:
+ opt = '--git-common-dir'
+ _, out, _ = cmd_output('git', 'rev-parse', opt, cwd=git_root)
+ git_common_dir = out.strip()
+ if git_common_dir != opt:
+ return os.path.normpath(os.path.join(git_root, git_common_dir))
+ else: # pragma: no cover (git < 2.5)
+ return get_git_dir(git_root)
+
+
def get_remote_url(git_root: str) -> str:
_, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)
return out.strip()
@@ -95,7 +106,7 @@ def is_in_merge_conflict() -> bool:
)
-def parse_merge_msg_for_conflicts(merge_msg: bytes) -> List[str]:
+def parse_merge_msg_for_conflicts(merge_msg: bytes) -> list[str]:
# Conflicted files start with tabs
return [
line.lstrip(b'#').strip().decode()
@@ -105,7 +116,7 @@ def parse_merge_msg_for_conflicts(merge_msg: bytes) -> List[str]:
]
-def get_conflicted_files() -> Set[str]:
+def get_conflicted_files() -> set[str]:
logger.info('Checking merge-conflict files only.')
# Need to get the conflicted files from the MERGE_MSG because they could
# have resolved the conflict by choosing one side or the other
@@ -126,7 +137,7 @@ def get_conflicted_files() -> Set[str]:
return set(merge_conflict_filenames) | set(merge_diff_filenames)
-def get_staged_files(cwd: Optional[str] = None) -> List[str]:
+def get_staged_files(cwd: str | None = None) -> list[str]:
return zsplit(
cmd_output(
'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z',
@@ -137,7 +148,7 @@ def get_staged_files(cwd: Optional[str] = None) -> List[str]:
)
-def intent_to_add_files() -> List[str]:
+def intent_to_add_files() -> list[str]:
_, stdout, _ = cmd_output(
'git', 'status', '--ignore-submodules', '--porcelain', '-z',
)
@@ -153,11 +164,11 @@ def intent_to_add_files() -> List[str]:
return intent_to_add
-def get_all_files() -> List[str]:
+def get_all_files() -> list[str]:
return zsplit(cmd_output('git', 'ls-files', '-z')[1])
-def get_changed_files(old: str, new: str) -> List[str]:
+def get_changed_files(old: str, new: str) -> list[str]:
diff_cmd = ('git', 'diff', '--name-only', '--no-ext-diff', '-z')
try:
_, out, _ = cmd_output(*diff_cmd, f'{old}...{new}')
@@ -230,3 +241,18 @@ def check_for_cygwin_mismatch() -> None:
f' - python {exe_type[is_cygwin_python]}\n'
f' - git {exe_type[is_cygwin_git]}\n',
)
+
+
+def get_best_candidate_tag(rev: str, git_repo: str) -> str:
+ """Get the best tag candidate.
+
+ Multiple tags can exist on a SHA. Sometimes a moving tag is attached
+ to a version tag. Try to pick the tag that looks like a version.
+ """
+ tags = cmd_output(
+ 'git', *NO_FS_MONITOR, 'tag', '--points-at', rev, cwd=git_repo,
+ )[1].splitlines()
+ for tag in tags:
+ if '.' in tag:
+ return tag
+ return rev
diff --git a/pre_commit/hook.py b/pre_commit/hook.py
index 82e99c5..202abb3 100644
--- a/pre_commit/hook.py
+++ b/pre_commit/hook.py
@@ -1,10 +1,10 @@
+from __future__ import annotations
+
import logging
import shlex
from typing import Any
-from typing import Dict
from typing import NamedTuple
from typing import Sequence
-from typing import Tuple
from pre_commit.prefix import Prefix
@@ -38,11 +38,11 @@ class Hook(NamedTuple):
verbose: bool
@property
- def cmd(self) -> Tuple[str, ...]:
+ def cmd(self) -> tuple[str, ...]:
return (*shlex.split(self.entry), *self.args)
@property
- def install_key(self) -> Tuple[Prefix, str, str, Tuple[str, ...]]:
+ def install_key(self) -> tuple[Prefix, str, str, tuple[str, ...]]:
return (
self.prefix,
self.language,
@@ -51,7 +51,7 @@ class Hook(NamedTuple):
)
@classmethod
- def create(cls, src: str, prefix: Prefix, dct: Dict[str, Any]) -> 'Hook':
+ def create(cls, src: str, prefix: Prefix, dct: dict[str, Any]) -> Hook:
# TODO: have cfgv do this (?)
extra_keys = set(dct) - _KEYS
if extra_keys:
diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py
index 0bcedd6..cfcbf68 100644
--- a/pre_commit/languages/all.py
+++ b/pre_commit/languages/all.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
from typing import Callable
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Tuple
from pre_commit.hook import Hook
from pre_commit.languages import conda
@@ -30,7 +30,7 @@ from pre_commit.prefix import Prefix
class Language(NamedTuple):
name: str
# Use `None` for no installation / environment
- ENVIRONMENT_DIR: Optional[str]
+ ENVIRONMENT_DIR: str | None
# return a value to replace `'default` for `language_version`
get_default_version: Callable[[], str]
# return whether the environment is healthy (or should be rebuilt)
@@ -38,7 +38,7 @@ class Language(NamedTuple):
# install a repository for the given language and language_version
install_environment: Callable[[Prefix, str, Sequence[str]], None]
# execute a hook and return the exit code and output
- run_hook: 'Callable[[Hook, Sequence[str], bool], Tuple[int, bytes]]'
+ run_hook: Callable[[Hook, Sequence[str], bool], tuple[int, bytes]]
# TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018
diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py
index 97e2f69..88ac53f 100644
--- a/pre_commit/languages/conda.py
+++ b/pre_commit/languages/conda.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import contextlib
import os
from typing import Generator
from typing import Sequence
-from typing import Tuple
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
@@ -86,7 +87,7 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
# TODO: Some rare commands need to be run using `conda run` but mostly we
# can run them without which is much quicker and produces a better
# output.
diff --git a/pre_commit/languages/coursier.py b/pre_commit/languages/coursier.py
index 2841467..bb3e0b8 100644
--- a/pre_commit/languages/coursier.py
+++ b/pre_commit/languages/coursier.py
@@ -1,14 +1,16 @@
+from __future__ import annotations
+
import contextlib
import os
from typing import Generator
from typing import Sequence
-from typing import Tuple
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.hook import Hook
from pre_commit.languages import helpers
+from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure
@@ -26,6 +28,14 @@ def install_environment(
helpers.assert_version_default('coursier', version)
helpers.assert_no_additional_deps('coursier', additional_dependencies)
+ # Support both possible executable names (either "cs" or "coursier")
+ executable = find_executable('cs') or find_executable('coursier')
+ if executable is None:
+ raise AssertionError(
+ 'pre-commit requires system-installed "cs" or "coursier" '
+ 'executables in the application search path',
+ )
+
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
channel = prefix.path('.pre-commit-channel')
with clean_path_on_failure(envdir):
@@ -35,7 +45,7 @@ def install_environment(
helpers.run_setup_cmd(
prefix,
(
- 'cs',
+ executable,
'install',
'--default-channels=false',
f'--channel={channel}',
@@ -66,6 +76,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]: # pragma: win32 no cover
+) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/dart.py b/pre_commit/languages/dart.py
index 16e7554..65135f8 100644
--- a/pre_commit/languages/dart.py
+++ b/pre_commit/languages/dart.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import contextlib
import os.path
import shutil
import tempfile
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -76,7 +77,7 @@ def install_environment(
with tempfile.TemporaryDirectory() as dep_tmp:
dep, _, version = dep_s.partition(':')
if version:
- dep_cmd: Tuple[str, ...] = (dep, '--version', version)
+ dep_cmd: tuple[str, ...] = (dep, '--version', version)
else:
dep_cmd = (dep,)
@@ -104,6 +105,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py
index 644d8d2..af1860c 100644
--- a/pre_commit/languages/docker.py
+++ b/pre_commit/languages/docker.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import hashlib
import json
import os
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.hook import Hook
@@ -76,7 +77,7 @@ def build_docker_image(
*,
pull: bool,
) -> None: # pragma: win32 no cover
- cmd: Tuple[str, ...] = (
+ cmd: tuple[str, ...] = (
'docker', 'build',
'--tag', docker_tag(prefix),
'--label', PRE_COMMIT_LABEL,
@@ -105,14 +106,14 @@ def install_environment(
os.mkdir(directory)
-def get_docker_user() -> Tuple[str, ...]: # pragma: win32 no cover
+def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover
try:
return ('-u', f'{os.getuid()}:{os.getgid()}')
except AttributeError:
return ()
-def docker_cmd() -> Tuple[str, ...]: # pragma: win32 no cover
+def docker_cmd() -> tuple[str, ...]: # pragma: win32 no cover
return (
'docker', 'run',
'--rm',
@@ -129,7 +130,7 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]: # pragma: win32 no cover
+) -> tuple[int, bytes]: # pragma: win32 no cover
# Rebuild the docker image in case it has gone missing, as many people do
# automated cleanup of docker images.
build_docker_image(hook.prefix, pull=False)
diff --git a/pre_commit/languages/docker_image.py b/pre_commit/languages/docker_image.py
index 311d127..ccc1d67 100644
--- a/pre_commit/languages/docker_image.py
+++ b/pre_commit/languages/docker_image.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
from typing import Sequence
-from typing import Tuple
from pre_commit.hook import Hook
from pre_commit.languages import helpers
@@ -15,6 +16,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]: # pragma: win32 no cover
+) -> tuple[int, bytes]: # pragma: win32 no cover
cmd = docker_cmd() + hook.cmd
return helpers.run_xargs(hook, cmd, file_args, color=color)
diff --git a/pre_commit/languages/dotnet.py b/pre_commit/languages/dotnet.py
index 094d2f1..a16e7f0 100644
--- a/pre_commit/languages/dotnet.py
+++ b/pre_commit/languages/dotnet.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import contextlib
import os.path
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -84,6 +85,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py
index d2b02d2..4cb95af 100644
--- a/pre_commit/languages/fail.py
+++ b/pre_commit/languages/fail.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
from typing import Sequence
-from typing import Tuple
from pre_commit.hook import Hook
from pre_commit.languages import helpers
@@ -14,7 +15,7 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
out = f'{hook.entry}\n\n'.encode()
out += b'\n'.join(f.encode() for f in file_args) + b'\n'
return 1, out
diff --git a/pre_commit/languages/golang.py b/pre_commit/languages/golang.py
index 10ebc62..759c268 100644
--- a/pre_commit/languages/golang.py
+++ b/pre_commit/languages/golang.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import contextlib
import os.path
import sys
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit import git
@@ -95,6 +96,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py
index 276ce16..8080826 100644
--- a/pre_commit/languages/helpers.py
+++ b/pre_commit/languages/helpers.py
@@ -1,13 +1,12 @@
+from __future__ import annotations
+
import multiprocessing
import os
import random
import re
from typing import Any
-from typing import List
-from typing import Optional
from typing import overload
from typing import Sequence
-from typing import Tuple
from typing import TYPE_CHECKING
import pre_commit.constants as C
@@ -32,7 +31,7 @@ def exe_exists(exe: str) -> bool:
homedir = os.path.expanduser('~')
try:
- common: Optional[str] = os.path.commonpath((found, homedir))
+ common: str | None = os.path.commonpath((found, homedir))
except ValueError: # on windows, different drives raises ValueError
common = None
@@ -48,7 +47,7 @@ def exe_exists(exe: str) -> bool:
)
-def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...], **kwargs: Any) -> None:
+def run_setup_cmd(prefix: Prefix, cmd: tuple[str, ...], **kwargs: Any) -> None:
cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs)
@@ -58,7 +57,7 @@ def environment_dir(d: None, language_version: str) -> None: ...
def environment_dir(d: str, language_version: str) -> str: ...
-def environment_dir(d: Optional[str], language_version: str) -> Optional[str]:
+def environment_dir(d: str | None, language_version: str) -> str | None:
if d is None:
return None
else:
@@ -68,7 +67,8 @@ def environment_dir(d: Optional[str], language_version: str) -> Optional[str]:
def assert_version_default(binary: str, version: str) -> None:
if version != C.DEFAULT:
raise AssertionError(
- f'For now, pre-commit requires system-installed {binary}',
+ f'for now, pre-commit requires system-installed {binary} -- '
+ f'you selected `language_version: {version}`',
)
@@ -78,8 +78,9 @@ def assert_no_additional_deps(
) -> None:
if additional_deps:
raise AssertionError(
- f'For now, pre-commit does not support '
- f'additional_dependencies for {lang}',
+ f'for now, pre-commit does not support '
+ f'additional_dependencies for {lang} -- '
+ f'you selected `additional_dependencies: {additional_deps}`',
)
@@ -95,7 +96,7 @@ def no_install(
prefix: Prefix,
version: str,
additional_dependencies: Sequence[str],
-) -> 'NoReturn':
+) -> NoReturn:
raise AssertionError('This type is not installable')
@@ -113,7 +114,7 @@ def target_concurrency(hook: Hook) -> int:
return 1
-def _shuffled(seq: Sequence[str]) -> List[str]:
+def _shuffled(seq: Sequence[str]) -> list[str]:
"""Deterministically shuffle"""
fixed_random = random.Random()
fixed_random.seed(FIXED_RANDOM_SEED, version=1)
@@ -125,10 +126,10 @@ def _shuffled(seq: Sequence[str]) -> List[str]:
def run_xargs(
hook: Hook,
- cmd: Tuple[str, ...],
+ cmd: tuple[str, ...],
file_args: Sequence[str],
**kwargs: Any,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
# Shuffle the files so that they more evenly fill out the xargs partitions,
# but do it deterministically in case a hook cares about ordering.
file_args = _shuffled(file_args)
diff --git a/pre_commit/languages/lua.py b/pre_commit/languages/lua.py
index f699937..38bdf54 100644
--- a/pre_commit/languages/lua.py
+++ b/pre_commit/languages/lua.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import contextlib
import os
import sys
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -85,6 +86,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]: # pragma: win32 no cover
+) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py
index 8dc4e8b..b084e8f 100644
--- a/pre_commit/languages/node.py
+++ b/pre_commit/languages/node.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import contextlib
import functools
import os
import sys
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -122,6 +123,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/perl.py b/pre_commit/languages/perl.py
index bbf5504..0eee258 100644
--- a/pre_commit/languages/perl.py
+++ b/pre_commit/languages/perl.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import contextlib
import os
import shlex
from typing import Generator
from typing import Sequence
-from typing import Tuple
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
@@ -62,6 +63,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py
index a713c3f..f2758c5 100644
--- a/pre_commit/languages/pygrep.py
+++ b/pre_commit/languages/pygrep.py
@@ -1,11 +1,11 @@
+from __future__ import annotations
+
import argparse
import re
import sys
from typing import NamedTuple
-from typing import Optional
from typing import Pattern
from typing import Sequence
-from typing import Tuple
from pre_commit import output
from pre_commit.hook import Hook
@@ -90,12 +90,12 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
exe = (sys.executable, '-m', __name__) + tuple(hook.args) + (hook.entry,)
return xargs(exe, file_args, color=color)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description=(
'grep-like finder using python regexes. Unlike grep, this tool '
diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py
index faa6029..668ba35 100644
--- a/pre_commit/languages/python.py
+++ b/pre_commit/languages/python.py
@@ -1,12 +1,11 @@
+from __future__ import annotations
+
import contextlib
import functools
import os
import sys
-from typing import Dict
from typing import Generator
-from typing import Optional
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -35,7 +34,7 @@ def _version_info(exe: str) -> str:
return f'<<error retrieving version from {exe}>>'
-def _read_pyvenv_cfg(filename: str) -> Dict[str, str]:
+def _read_pyvenv_cfg(filename: str) -> dict[str, str]:
ret = {}
with open(filename, encoding='UTF-8') as f:
for line in f:
@@ -65,7 +64,7 @@ def get_env_patch(venv: str) -> PatchesT:
def _find_by_py_launcher(
version: str,
-) -> Optional[str]: # pragma: no cover (windows only)
+) -> str | None: # pragma: no cover (windows only)
if version.startswith('python'):
num = version[len('python'):]
cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)')
@@ -77,8 +76,8 @@ def _find_by_py_launcher(
return None
-def _find_by_sys_executable() -> Optional[str]:
- def _norm(path: str) -> Optional[str]:
+def _find_by_sys_executable() -> str | None:
+ def _norm(path: str) -> str | None:
_, exe = os.path.split(path.lower())
exe, _, _ = exe.partition('.exe')
if exe not in {'python', 'pythonw'} and find_executable(exe):
@@ -133,7 +132,7 @@ def _sys_executable_matches(version: str) -> bool:
return sys.version_info[:len(info)] == info
-def norm_version(version: str) -> Optional[str]:
+def norm_version(version: str) -> str | None:
if version == C.DEFAULT: # use virtualenv's default
return None
elif _sys_executable_matches(version): # virtualenv defaults to our exe
@@ -209,6 +208,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/r.py b/pre_commit/languages/r.py
index e034e39..c736b38 100644
--- a/pre_commit/languages/r.py
+++ b/pre_commit/languages/r.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import contextlib
import os
import shlex
import shutil
from typing import Generator
from typing import Sequence
-from typing import Tuple
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
@@ -58,7 +59,11 @@ def _prefix_if_non_local_file_entry(
def _rscript_exec() -> str:
- return os.path.join(os.getenv('R_HOME', ''), 'Rscript')
+ r_home = os.environ.get('R_HOME')
+ if r_home is None:
+ return 'Rscript'
+ else:
+ return os.path.join(r_home, 'bin', 'Rscript')
def _entry_validate(entry: Sequence[str]) -> None:
@@ -80,7 +85,7 @@ def _entry_validate(entry: Sequence[str]) -> None:
)
-def _cmd_from_hook(hook: Hook) -> Tuple[str, ...]:
+def _cmd_from_hook(hook: Hook) -> tuple[str, ...]:
entry = shlex.split(hook.entry)
_entry_validate(entry)
@@ -102,9 +107,7 @@ def install_environment(
shutil.copy(prefix.path('renv.lock'), env_dir)
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
- cmd_output_b(
- _rscript_exec(), '--vanilla', '-e',
- f"""\
+ r_code_inst_environment = f"""\
prefix_dir <- {prefix.prefix_dir!r}
options(
repos = c(CRAN = "https://cran.rstudio.com"),
@@ -131,24 +134,41 @@ def install_environment(
if (is_package) {{
renv::install(prefix_dir)
}}
- """,
+ """
+
+ cmd_output_b(
+ _rscript_exec(), '--vanilla', '-e',
+ _inline_r_setup(r_code_inst_environment),
cwd=env_dir,
)
if additional_dependencies:
+ r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
with in_env(prefix, version):
cmd_output_b(
_rscript_exec(), *RSCRIPT_OPTS, '-e',
- 'renv::install(commandArgs(trailingOnly = TRUE))',
+ _inline_r_setup(r_code_inst_add),
*additional_dependencies,
cwd=env_dir,
)
+def _inline_r_setup(code: str) -> str:
+ """
+ Some behaviour of R cannot be configured via env variables, but can
+ only be configured via R options once R has started. These are set here.
+ """
+ with_option = f"""\
+ options(install.packages.compile.from.source = "never")
+ {code}
+ """
+ return with_option
+
+
def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(
hook, _cmd_from_hook(hook), file_args, color=color,
diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py
index 81bc954..ae64492 100644
--- a/pre_commit/languages/ruby.py
+++ b/pre_commit/languages/ruby.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import functools
import os.path
@@ -5,7 +7,6 @@ import shutil
import tarfile
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -146,6 +147,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py
index 7ea3f54..39e3628 100644
--- a/pre_commit/languages/rust.py
+++ b/pre_commit/languages/rust.py
@@ -1,9 +1,9 @@
+from __future__ import annotations
+
import contextlib
import os.path
from typing import Generator
from typing import Sequence
-from typing import Set
-from typing import Tuple
import toml
@@ -39,7 +39,7 @@ def in_env(prefix: Prefix) -> Generator[None, None, None]:
def _add_dependencies(
cargo_toml_path: str,
- additional_dependencies: Set[str],
+ additional_dependencies: set[str],
) -> None:
with open(cargo_toml_path, 'r+') as f:
cargo_toml = toml.load(f)
@@ -81,7 +81,7 @@ def install_environment(
_add_dependencies(prefix.path('Cargo.toml'), lib_deps)
with clean_path_on_failure(directory):
- packages_to_install: Set[Tuple[str, ...]] = {('--path', '.')}
+ packages_to_install: set[tuple[str, ...]] = {('--path', '.')}
for cli_dep in cli_deps:
cli_dep = cli_dep[len('cli:'):]
package, _, version = cli_dep.partition(':')
@@ -101,6 +101,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py
index a5e1365..2844b5e 100644
--- a/pre_commit/languages/script.py
+++ b/pre_commit/languages/script.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
from typing import Sequence
-from typing import Tuple
from pre_commit.hook import Hook
from pre_commit.languages import helpers
@@ -14,6 +15,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:])
return helpers.run_xargs(hook, cmd, file_args, color=color)
diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py
index 66aadc8..c630953 100644
--- a/pre_commit/languages/swift.py
+++ b/pre_commit/languages/swift.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import contextlib
import os
from typing import Generator
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.envcontext import envcontext
@@ -59,6 +60,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]: # pragma: win32 no cover
+) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/languages/system.py b/pre_commit/languages/system.py
index 139f45d..9846c98 100644
--- a/pre_commit/languages/system.py
+++ b/pre_commit/languages/system.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
from typing import Sequence
-from typing import Tuple
from pre_commit.hook import Hook
from pre_commit.languages import helpers
@@ -15,5 +16,5 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
diff --git a/pre_commit/logging_handler.py b/pre_commit/logging_handler.py
index ba05295..1b68fc7 100644
--- a/pre_commit/logging_handler.py
+++ b/pre_commit/logging_handler.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import logging
from typing import Generator
diff --git a/pre_commit/main.py b/pre_commit/main.py
index f1e8d03..645e97f 100644
--- a/pre_commit/main.py
+++ b/pre_commit/main.py
@@ -1,11 +1,10 @@
+from __future__ import annotations
+
import argparse
import logging
import os
import sys
-from typing import Any
-from typing import Optional
from typing import Sequence
-from typing import Union
import pre_commit.constants as C
from pre_commit import git
@@ -46,34 +45,10 @@ def _add_config_option(parser: argparse.ArgumentParser) -> None:
)
-class AppendReplaceDefault(argparse.Action):
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- super().__init__(*args, **kwargs)
- self.appended = False
-
- def __call__(
- self,
- parser: argparse.ArgumentParser,
- namespace: argparse.Namespace,
- values: Union[str, Sequence[str], None],
- option_string: Optional[str] = None,
- ) -> None:
- if not self.appended:
- setattr(namespace, self.dest, [])
- self.appended = True
- getattr(namespace, self.dest).append(values)
-
-
def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
- '-t', '--hook-type', choices=(
- 'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
- 'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
- 'post-rewrite',
- ),
- action=AppendReplaceDefault,
- default=['pre-commit'],
- dest='hook_types',
+ '-t', '--hook-type',
+ choices=C.HOOK_TYPES, action='append', dest='hook_types',
)
@@ -106,7 +81,7 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'--from-ref', '--source', '-s',
help=(
- '(for usage with `--from-ref`) -- this option represents the '
+ '(for usage with `--to-ref`) -- this option represents the '
'original ref in a `from_ref...to_ref` diff expression. '
'For `pre-push` hooks, this represents the branch you are pushing '
'to. '
@@ -117,7 +92,7 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'--to-ref', '--origin', '-o',
help=(
- '(for usage with `--to-ref`) -- this option represents the '
+ '(for usage with `--from-ref`) -- this option represents the '
'destination ref in a `from_ref...to_ref` diff expression. '
'For `pre-push` hooks, this represents the branch being pushed. '
'For `post-checkout` hooks, this represents the branch that is '
@@ -175,7 +150,7 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None:
args.repo = os.path.relpath(args.repo)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
parser = argparse.ArgumentParser(prog='pre-commit')
@@ -197,7 +172,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
autoupdate_parser.add_argument(
'--bleeding-edge', action='store_true',
help=(
- 'Update to the bleeding edge of `master` instead of the latest '
+ 'Update to the bleeding edge of `HEAD` instead of the latest '
'tagged version (the default behavior).'
),
)
@@ -399,7 +374,10 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
elif args.command == 'try-repo':
return try_repo(args)
elif args.command == 'uninstall':
- return uninstall(hook_types=args.hook_types)
+ return uninstall(
+ config_file=args.config,
+ hook_types=args.hook_types,
+ )
else:
raise NotImplementedError(
f'Command {args.command} not implemented.',
diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py
index a6eb0e0..b05a705 100644
--- a/pre_commit/meta_hooks/check_hooks_apply.py
+++ b/pre_commit/meta_hooks/check_hooks_apply.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
import pre_commit.constants as C
@@ -27,7 +28,7 @@ def check_all_hooks_match_files(config_file: str) -> int:
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE])
args = parser.parse_args(argv)
diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py
index 60870f8..0a8249b 100644
--- a/pre_commit/meta_hooks/check_useless_excludes.py
+++ b/pre_commit/meta_hooks/check_useless_excludes.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import argparse
import re
-from typing import Optional
from typing import Sequence
from cfgv import apply_defaults
@@ -65,7 +66,7 @@ def check_useless_excludes(config_file: str) -> int:
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE])
args = parser.parse_args(argv)
diff --git a/pre_commit/meta_hooks/identity.py b/pre_commit/meta_hooks/identity.py
index 12eb02f..72ee440 100644
--- a/pre_commit/meta_hooks/identity.py
+++ b/pre_commit/meta_hooks/identity.py
@@ -1,11 +1,12 @@
+from __future__ import annotations
+
import sys
-from typing import Optional
from typing import Sequence
from pre_commit import output
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
for arg in argv:
output.write_line(arg)
diff --git a/pre_commit/output.py b/pre_commit/output.py
index 24f9d84..4bcf27f 100644
--- a/pre_commit/output.py
+++ b/pre_commit/output.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import contextlib
import sys
from typing import Any
from typing import IO
-from typing import Optional
def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None:
@@ -11,9 +12,9 @@ def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None:
def write_line_b(
- s: Optional[bytes] = None,
+ s: bytes | None = None,
stream: IO[bytes] = sys.stdout.buffer,
- logfile_name: Optional[str] = None,
+ logfile_name: str | None = None,
) -> None:
with contextlib.ExitStack() as exit_stack:
output_streams = [stream]
@@ -28,5 +29,5 @@ def write_line_b(
output_stream.flush()
-def write_line(s: Optional[str] = None, **kwargs: Any) -> None:
+def write_line(s: str | None = None, **kwargs: Any) -> None:
write_line_b(s.encode() if s is not None else s, **kwargs)
diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py
index d344a1d..3fd3129 100644
--- a/pre_commit/parse_shebang.py
+++ b/pre_commit/parse_shebang.py
@@ -1,7 +1,7 @@
+from __future__ import annotations
+
import os.path
from typing import Mapping
-from typing import Optional
-from typing import Tuple
from typing import TYPE_CHECKING
from identify.identify import parse_shebang_from_file
@@ -11,11 +11,11 @@ if TYPE_CHECKING:
class ExecutableNotFoundError(OSError):
- def to_output(self) -> Tuple[int, bytes, None]:
+ def to_output(self) -> tuple[int, bytes, None]:
return (1, self.args[0].encode(), None)
-def parse_filename(filename: str) -> Tuple[str, ...]:
+def parse_filename(filename: str) -> tuple[str, ...]:
if not os.path.exists(filename):
return ()
else:
@@ -23,8 +23,8 @@ def parse_filename(filename: str) -> Tuple[str, ...]:
def find_executable(
- exe: str, _environ: Optional[Mapping[str, str]] = None,
-) -> Optional[str]:
+ exe: str, _environ: Mapping[str, str] | None = None,
+) -> str | None:
exe = os.path.normpath(exe)
if os.sep in exe:
return exe
@@ -47,7 +47,7 @@ def find_executable(
def normexe(orig: str) -> str:
- def _error(msg: str) -> 'NoReturn':
+ def _error(msg: str) -> NoReturn:
raise ExecutableNotFoundError(f'Executable `{orig}` {msg}')
if os.sep not in orig and (not os.altsep or os.altsep not in orig):
@@ -65,7 +65,7 @@ def normexe(orig: str) -> str:
return orig
-def normalize_cmd(cmd: Tuple[str, ...]) -> Tuple[str, ...]:
+def normalize_cmd(cmd: tuple[str, ...]) -> tuple[str, ...]:
"""Fixes for the following issues on windows
- https://bugs.python.org/issue8557
- windows does not parse shebangs
diff --git a/pre_commit/prefix.py b/pre_commit/prefix.py
index 0e3ebbd..f1b28c1 100644
--- a/pre_commit/prefix.py
+++ b/pre_commit/prefix.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import os.path
from typing import NamedTuple
-from typing import Tuple
class Prefix(NamedTuple):
@@ -12,6 +13,6 @@ class Prefix(NamedTuple):
def exists(self, *parts: str) -> bool:
return os.path.exists(self.path(*parts))
- def star(self, end: str) -> Tuple[str, ...]:
+ def star(self, end: str) -> tuple[str, ...]:
paths = os.listdir(self.prefix_dir)
return tuple(path for path in paths if path.endswith(end))
diff --git a/pre_commit/repository.py b/pre_commit/repository.py
index 15827dd..ac5d294 100644
--- a/pre_commit/repository.py
+++ b/pre_commit/repository.py
@@ -1,13 +1,10 @@
+from __future__ import annotations
+
import json
import logging
import os
from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Set
-from typing import Tuple
import pre_commit.constants as C
from pre_commit.clientlib import load_manifest
@@ -33,7 +30,7 @@ def _state_filename(prefix: Prefix, venv: str) -> str:
return prefix.path(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}')
-def _read_state(prefix: Prefix, venv: str) -> Optional[object]:
+def _read_state(prefix: Prefix, venv: str) -> object | None:
filename = _state_filename(prefix, venv)
if not os.path.exists(filename):
return None
@@ -93,9 +90,9 @@ def _hook_install(hook: Hook) -> None:
def _hook(
- *hook_dicts: Dict[str, Any],
- root_config: Dict[str, Any],
-) -> Dict[str, Any]:
+ *hook_dicts: dict[str, Any],
+ root_config: dict[str, Any],
+) -> dict[str, Any]:
ret, rest = dict(hook_dicts[0]), hook_dicts[1:]
for dct in rest:
ret.update(dct)
@@ -140,10 +137,10 @@ def _hook(
def _non_cloned_repository_hooks(
- repo_config: Dict[str, Any],
+ repo_config: dict[str, Any],
store: Store,
- root_config: Dict[str, Any],
-) -> Tuple[Hook, ...]:
+ root_config: dict[str, Any],
+) -> tuple[Hook, ...]:
def _prefix(language_name: str, deps: Sequence[str]) -> Prefix:
language = languages[language_name]
# pygrep / script / system / docker_image do not have
@@ -164,10 +161,10 @@ def _non_cloned_repository_hooks(
def _cloned_repository_hooks(
- repo_config: Dict[str, Any],
+ repo_config: dict[str, Any],
store: Store,
- root_config: Dict[str, Any],
-) -> Tuple[Hook, ...]:
+ root_config: dict[str, Any],
+) -> tuple[Hook, ...]:
repo, rev = repo_config['repo'], repo_config['rev']
manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)
by_id = {hook['id']: hook for hook in load_manifest(manifest_path)}
@@ -196,10 +193,10 @@ def _cloned_repository_hooks(
def _repository_hooks(
- repo_config: Dict[str, Any],
+ repo_config: dict[str, Any],
store: Store,
- root_config: Dict[str, Any],
-) -> Tuple[Hook, ...]:
+ root_config: dict[str, Any],
+) -> tuple[Hook, ...]:
if repo_config['repo'] in {LOCAL, META}:
return _non_cloned_repository_hooks(repo_config, store, root_config)
else:
@@ -207,8 +204,8 @@ def _repository_hooks(
def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:
- def _need_installed() -> List[Hook]:
- seen: Set[Tuple[Prefix, str, str, Tuple[str, ...]]] = set()
+ def _need_installed() -> list[Hook]:
+ seen: set[tuple[Prefix, str, str, tuple[str, ...]]] = set()
ret = []
for hook in hooks:
if hook.install_key not in seen and not _hook_installed(hook):
@@ -224,7 +221,7 @@ def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:
_hook_install(hook)
-def all_hooks(root_config: Dict[str, Any], store: Store) -> Tuple[Hook, ...]:
+def all_hooks(root_config: dict[str, Any], store: Store) -> tuple[Hook, ...]:
return tuple(
hook
for repo in root_config['repos']
diff --git a/pre_commit/resources/ruby-build.tar.gz b/pre_commit/resources/ruby-build.tar.gz
index 01867be..e248c57 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 bad004c..83d8a03 100644
--- a/pre_commit/staged_files_only.py
+++ b/pre_commit/staged_files_only.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import logging
import os.path
@@ -64,9 +66,9 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
# prevent recursive post-checkout hooks (#1418)
no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1')
- cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
try:
+ cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
yield
finally:
# Try to apply the patch we saved
diff --git a/pre_commit/store.py b/pre_commit/store.py
index 27d8553..effebfb 100644
--- a/pre_commit/store.py
+++ b/pre_commit/store.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import logging
import os.path
@@ -5,10 +7,7 @@ import sqlite3
import tempfile
from typing import Callable
from typing import Generator
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Tuple
import pre_commit.constants as C
from pre_commit import file_lock
@@ -40,7 +39,7 @@ def _get_default_directory() -> str:
class Store:
get_default_directory = staticmethod(_get_default_directory)
- def __init__(self, directory: Optional[str] = None) -> None:
+ def __init__(self, directory: str | None = None) -> None:
self.directory = directory or Store.get_default_directory()
self.db_path = os.path.join(self.directory, 'db.db')
self.readonly = (
@@ -92,7 +91,7 @@ class Store:
@contextlib.contextmanager
def connect(
self,
- db_path: Optional[str] = None,
+ db_path: str | None = None,
) -> Generator[sqlite3.Connection, None, None]:
db_path = db_path or self.db_path
# sqlite doesn't close its fd with its contextmanager >.<
@@ -119,7 +118,7 @@ class Store:
) -> str:
repo = self.db_repo_name(repo, deps)
- def _get_result() -> Optional[str]:
+ def _get_result() -> str | None:
# Check if we already exist
with self.connect() as db:
result = db.execute(
@@ -239,18 +238,18 @@ class Store:
self._create_config_table(db)
db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,))
- def select_all_configs(self) -> List[str]:
+ def select_all_configs(self) -> list[str]:
with self.connect() as db:
self._create_config_table(db)
rows = db.execute('SELECT path FROM configs').fetchall()
return [path for path, in rows]
- def delete_configs(self, configs: List[str]) -> None:
+ def delete_configs(self, configs: list[str]) -> None:
with self.connect() as db:
rows = [(path,) for path in configs]
db.executemany('DELETE FROM configs WHERE path = ?', rows)
- def select_all_repos(self) -> List[Tuple[str, str, str]]:
+ def select_all_repos(self) -> list[tuple[str, str, str]]:
with self.connect() as db:
return db.execute('SELECT repo, ref, path from repos').fetchall()
diff --git a/pre_commit/util.py b/pre_commit/util.py
index 6977acb..40c53e5 100644
--- a/pre_commit/util.py
+++ b/pre_commit/util.py
@@ -1,6 +1,9 @@
+from __future__ import annotations
+
import contextlib
import errno
import functools
+import importlib.resources
import os.path
import shutil
import stat
@@ -10,24 +13,13 @@ import tempfile
from types import TracebackType
from typing import Any
from typing import Callable
-from typing import Dict
from typing import Generator
from typing import IO
-from typing import Optional
-from typing import Tuple
-from typing import Type
import yaml
from pre_commit import parse_shebang
-if sys.version_info >= (3, 7): # pragma: >=3.7 cover
- from importlib.resources import open_binary
- from importlib.resources import read_text
-else: # pragma: <3.7 cover
- from importlib_resources import open_binary
- from importlib_resources import read_text
-
Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
yaml_load = functools.partial(yaml.load, Loader=Loader)
Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper)
@@ -73,11 +65,11 @@ def tmpdir() -> Generator[str, None, None]:
def resource_bytesio(filename: str) -> IO[bytes]:
- return open_binary('pre_commit.resources', filename)
+ return importlib.resources.open_binary('pre_commit.resources', filename)
def resource_text(filename: str) -> str:
- return read_text('pre_commit.resources', filename)
+ return importlib.resources.read_text('pre_commit.resources', filename)
def make_executable(filename: str) -> None:
@@ -90,10 +82,10 @@ class CalledProcessError(RuntimeError):
def __init__(
self,
returncode: int,
- cmd: Tuple[str, ...],
+ cmd: tuple[str, ...],
expected_returncode: int,
stdout: bytes,
- stderr: Optional[bytes],
+ stderr: bytes | None,
) -> None:
super().__init__(returncode, cmd, expected_returncode, stdout, stderr)
self.returncode = returncode
@@ -103,7 +95,7 @@ class CalledProcessError(RuntimeError):
self.stderr = stderr
def __bytes__(self) -> bytes:
- def _indent_or_none(part: Optional[bytes]) -> bytes:
+ def _indent_or_none(part: bytes | None) -> bytes:
if part:
return b'\n ' + part.replace(b'\n', b'\n ')
else:
@@ -121,20 +113,20 @@ class CalledProcessError(RuntimeError):
return self.__bytes__().decode()
-def _setdefault_kwargs(kwargs: Dict[str, Any]) -> None:
+def _setdefault_kwargs(kwargs: dict[str, Any]) -> None:
for arg in ('stdin', 'stdout', 'stderr'):
kwargs.setdefault(arg, subprocess.PIPE)
-def _oserror_to_output(e: OSError) -> Tuple[int, bytes, None]:
+def _oserror_to_output(e: OSError) -> tuple[int, bytes, None]:
return 1, force_bytes(e).rstrip(b'\n') + b'\n', None
def cmd_output_b(
*cmd: str,
- retcode: Optional[int] = 0,
+ retcode: int | None = 0,
**kwargs: Any,
-) -> Tuple[int, bytes, Optional[bytes]]:
+) -> tuple[int, bytes, bytes | None]:
_setdefault_kwargs(kwargs)
try:
@@ -156,7 +148,7 @@ def cmd_output_b(
return returncode, stdout_b, stderr_b
-def cmd_output(*cmd: str, **kwargs: Any) -> Tuple[int, str, Optional[str]]:
+def cmd_output(*cmd: str, **kwargs: Any) -> tuple[int, str, str | None]:
returncode, stdout_b, stderr_b = cmd_output_b(*cmd, **kwargs)
stdout = stdout_b.decode() if stdout_b is not None else None
stderr = stderr_b.decode() if stderr_b is not None else None
@@ -169,10 +161,10 @@ if os.name != 'nt': # pragma: win32 no cover
class Pty:
def __init__(self) -> None:
- self.r: Optional[int] = None
- self.w: Optional[int] = None
+ self.r: int | None = None
+ self.w: int | None = None
- def __enter__(self) -> 'Pty':
+ def __enter__(self) -> Pty:
self.r, self.w = openpty()
# tty flags normally change \n to \r\n
@@ -195,18 +187,18 @@ if os.name != 'nt': # pragma: win32 no cover
def __exit__(
self,
- exc_type: Optional[Type[BaseException]],
- exc_value: Optional[BaseException],
- traceback: Optional[TracebackType],
+ exc_type: type[BaseException] | None,
+ exc_value: BaseException | None,
+ traceback: TracebackType | None,
) -> None:
self.close_w()
self.close_r()
def cmd_output_p(
*cmd: str,
- retcode: Optional[int] = 0,
+ retcode: int | None = 0,
**kwargs: Any,
- ) -> Tuple[int, bytes, Optional[bytes]]:
+ ) -> tuple[int, bytes, bytes | None]:
assert retcode is None
assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr']
_setdefault_kwargs(kwargs)
@@ -250,7 +242,7 @@ def rmtree(path: str) -> None:
def handle_remove_readonly(
func: Callable[..., Any],
path: str,
- exc: Tuple[Type[OSError], OSError, TracebackType],
+ exc: tuple[type[OSError], OSError, TracebackType],
) -> None:
excvalue = exc[1]
if (
@@ -265,7 +257,7 @@ def rmtree(path: str) -> None:
shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly)
-def parse_version(s: str) -> Tuple[int, ...]:
+def parse_version(s: str) -> tuple[int, ...]:
"""poor man's version comparison"""
return tuple(int(p) for p in s.split('.'))
diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py
index 6b0fa20..f2b3421 100644
--- a/pre_commit/xargs.py
+++ b/pre_commit/xargs.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import concurrent.futures
import contextlib
import math
@@ -8,11 +10,8 @@ from typing import Any
from typing import Callable
from typing import Generator
from typing import Iterable
-from typing import List
from typing import MutableMapping
-from typing import Optional
from typing import Sequence
-from typing import Tuple
from typing import TypeVar
from pre_commit import parse_shebang
@@ -23,7 +22,7 @@ TArg = TypeVar('TArg')
TRet = TypeVar('TRet')
-def _environ_size(_env: Optional[MutableMapping[str, str]] = None) -> int:
+def _environ_size(_env: MutableMapping[str, str] | None = None) -> int:
environ = _env if _env is not None else getattr(os, 'environb', os.environ)
size = 8 * len(environ) # number of pointers in `envp`
for k, v in environ.items():
@@ -62,8 +61,8 @@ def partition(
cmd: Sequence[str],
varargs: Sequence[str],
target_concurrency: int,
- _max_length: Optional[int] = None,
-) -> Tuple[Tuple[str, ...], ...]:
+ _max_length: int | None = None,
+) -> tuple[tuple[str, ...], ...]:
_max_length = _max_length or _get_platform_max_length()
# Generally, we try to partition evenly into at least `target_concurrency`
@@ -73,7 +72,7 @@ def partition(
cmd = tuple(cmd)
ret = []
- ret_cmd: List[str] = []
+ ret_cmd: list[str] = []
# Reversed so arguments are in order
varargs = list(reversed(varargs))
@@ -115,14 +114,14 @@ def _thread_mapper(maxsize: int) -> Generator[
def xargs(
- cmd: Tuple[str, ...],
+ cmd: tuple[str, ...],
varargs: Sequence[str],
*,
color: bool = False,
target_concurrency: int = 1,
_max_length: int = _get_platform_max_length(),
**kwargs: Any,
-) -> Tuple[int, bytes]:
+) -> tuple[int, bytes]:
"""A simplified implementation of xargs.
color: Make a pty if on a platform that supports it
@@ -152,8 +151,8 @@ def xargs(
partitions = partition(cmd, varargs, target_concurrency, _max_length)
def run_cmd_partition(
- run_cmd: Tuple[str, ...],
- ) -> Tuple[int, bytes, Optional[bytes]]:
+ run_cmd: tuple[str, ...],
+ ) -> tuple[int, bytes, bytes | None]:
return cmd_fn(
*run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs,
)
diff --git a/setup.cfg b/setup.cfg
index ef55b7c..ca92af3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pre_commit
-version = 2.17.0
+version = 2.18.1
description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md
long_description_content_type = text/markdown
@@ -13,7 +13,6 @@ classifiers =
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
@@ -31,8 +30,7 @@ install_requires =
toml
virtualenv>=20.0.8
importlib-metadata;python_version<"3.8"
- importlib-resources<5.3;python_version<"3.7"
-python_requires = >=3.6.1
+python_requires = >=3.7
[options.packages.find]
exclude =
diff --git a/setup.py b/setup.py
index 8bf1ba9..3d93aef 100644
--- a/setup.py
+++ b/setup.py
@@ -1,2 +1,4 @@
+from __future__ import annotations
+
from setuptools import setup
setup()
diff --git a/testing/auto_namedtuple.py b/testing/auto_namedtuple.py
index 0841094..d5a4377 100644
--- a/testing/auto_namedtuple.py
+++ b/testing/auto_namedtuple.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import collections
diff --git a/testing/fixtures.py b/testing/fixtures.py
index f7def08..ef5a041 100644
--- a/testing/fixtures.py
+++ b/testing/fixtures.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import os.path
import shutil
diff --git a/testing/gen-languages-all b/testing/gen-languages-all
index 152cf3c..dfd92c0 100755
--- a/testing/gen-languages-all
+++ b/testing/gen-languages-all
@@ -1,4 +1,6 @@
#!/usr/bin/env python3
+from __future__ import annotations
+
import sys
LANGUAGES = [
diff --git a/testing/make-archives b/testing/make-archives
index ce098ba..1b825fe 100755
--- a/testing/make-archives
+++ b/testing/make-archives
@@ -1,4 +1,6 @@
#!/usr/bin/env python3
+from __future__ import annotations
+
import argparse
import gzip
import os.path
@@ -6,7 +8,6 @@ import shutil
import subprocess
import tarfile
import tempfile
-from typing import Optional
from typing import Sequence
@@ -16,7 +17,7 @@ from typing import Sequence
REPOS = (
('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'),
- ('ruby-build', 'https://github.com/rbenv/ruby-build', '8663d2f'),
+ ('ruby-build', 'https://github.com/rbenv/ruby-build', 'a5ca3e4'),
(
'ruby-download',
'https://github.com/garnieretienne/rvm-download',
@@ -69,7 +70,7 @@ def make_archive(name: str, repo: str, ref: str, destdir: str) -> str:
return output_path
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--dest', default='pre_commit/resources')
args = parser.parse_args(argv)
diff --git a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml
index 2c23700..964cf83 100644
--- a/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml
+++ b/testing/resources/python3_hooks_repo/.pre-commit-hooks.yaml
@@ -2,5 +2,4 @@
name: Python 3 Hook
entry: python3-hook
language: python
- language_version: python3
files: \.py$
diff --git a/testing/resources/python_hooks_repo/foo.py b/testing/resources/python_hooks_repo/foo.py
index 9c4368e..40efde3 100644
--- a/testing/resources/python_hooks_repo/foo.py
+++ b/testing/resources/python_hooks_repo/foo.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import sys
diff --git a/testing/resources/python_hooks_repo/setup.py b/testing/resources/python_hooks_repo/setup.py
index 0559271..cff6cad 100644
--- a/testing/resources/python_hooks_repo/setup.py
+++ b/testing/resources/python_hooks_repo/setup.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from setuptools import setup
setup(
diff --git a/testing/resources/python_venv_hooks_repo/foo.py b/testing/resources/python_venv_hooks_repo/foo.py
index 9c4368e..40efde3 100644
--- a/testing/resources/python_venv_hooks_repo/foo.py
+++ b/testing/resources/python_venv_hooks_repo/foo.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import sys
diff --git a/testing/resources/python_venv_hooks_repo/setup.py b/testing/resources/python_venv_hooks_repo/setup.py
index 0559271..cff6cad 100644
--- a/testing/resources/python_venv_hooks_repo/setup.py
+++ b/testing/resources/python_venv_hooks_repo/setup.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from setuptools import setup
setup(
diff --git a/testing/util.py b/testing/util.py
index 283ed47..0dd1784 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import os.path
import subprocess
diff --git a/testing/zipapp/Dockerfile b/testing/zipapp/Dockerfile
index e21d5fe..7c74c1b 100644
--- a/testing/zipapp/Dockerfile
+++ b/testing/zipapp/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:bionic
+FROM ubuntu:focal
RUN : \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@@ -10,5 +10,5 @@ RUN : \
ENV LANG=C.UTF-8 PATH=/venv/bin:$PATH
RUN : \
- && python3.6 -mvenv /venv \
+ && python3 -mvenv /venv \
&& pip install --no-cache-dir pip setuptools wheel no-manylinux --upgrade
diff --git a/testing/zipapp/entry b/testing/zipapp/entry
index 87f9291..15758d9 100755
--- a/testing/zipapp/entry
+++ b/testing/zipapp/entry
@@ -1,4 +1,6 @@
#!/usr/bin/env python3
+from __future__ import annotations
+
import os.path
import shutil
import stat
@@ -59,10 +61,7 @@ def main() -> int:
if sys.platform == 'win32': # https://bugs.python.org/issue19124
import subprocess
- if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
- return subprocess.Popen(cmd).wait()
- else:
- return subprocess.call(cmd)
+ return subprocess.call(cmd)
else:
os.execvp(cmd[0], cmd)
diff --git a/testing/zipapp/make b/testing/zipapp/make
index effc812..37b5c35 100755
--- a/testing/zipapp/make
+++ b/testing/zipapp/make
@@ -1,4 +1,6 @@
#!/usr/bin/env python3
+from __future__ import annotations
+
import argparse
import base64
import hashlib
diff --git a/testing/zipapp/python b/testing/zipapp/python
index 7184a1a..67910fc 100755
--- a/testing/zipapp/python
+++ b/testing/zipapp/python
@@ -1,5 +1,7 @@
#!/usr/bin/env python3
"""A shim executable to put dependencies on sys.path"""
+from __future__ import annotations
+
import argparse
import os.path
import runpy
@@ -36,10 +38,7 @@ def main() -> int:
if sys.platform == 'win32': # https://bugs.python.org/issue19124
import subprocess
- if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
- return subprocess.Popen(cmd).wait()
- else:
- return subprocess.call(cmd)
+ return subprocess.call(cmd)
else:
os.execvp(cmd[0], cmd)
diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py
index 39a3716..3fb3af5 100644
--- a/tests/clientlib_test.py
+++ b/tests/clientlib_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import logging
import re
diff --git a/tests/color_test.py b/tests/color_test.py
index 5cd226a..89b4fd3 100644
--- a/tests/color_test.py
+++ b/tests/color_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import sys
from unittest import mock
diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py
index 7316eb9..3806b0e 100644
--- a/tests/commands/autoupdate_test.py
+++ b/tests/commands/autoupdate_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import shlex
from unittest import mock
@@ -101,6 +103,24 @@ def test_rev_info_update_tags_only_does_not_pick_tip(tagged):
assert new_info.rev == 'v1.2.3'
+def test_rev_info_update_tags_prefers_version_tag(tagged, out_of_date):
+ cmd_output('git', 'tag', 'latest', cwd=out_of_date.path)
+ config = make_config_from_repo(tagged.path, rev=tagged.original_rev)
+ info = RevInfo.from_config(config)
+ new_info = info.update(tags_only=True, freeze=False)
+ assert new_info.rev == 'v1.2.3'
+
+
+def test_rev_info_update_tags_non_version_tag(out_of_date):
+ cmd_output('git', 'tag', 'latest', cwd=out_of_date.path)
+ config = make_config_from_repo(
+ out_of_date.path, rev=out_of_date.original_rev,
+ )
+ info = RevInfo.from_config(config)
+ new_info = info.update(tags_only=True, freeze=False)
+ assert new_info.rev == 'latest'
+
+
def test_rev_info_update_freeze_tag(tagged):
git_commit(cwd=tagged.path)
config = make_config_from_repo(tagged.path, rev=tagged.original_rev)
diff --git a/tests/commands/clean_test.py b/tests/commands/clean_test.py
index 955a6bc..dd8e4a5 100644
--- a/tests/commands/clean_test.py
+++ b/tests/commands/clean_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
from unittest import mock
diff --git a/tests/commands/gc_test.py b/tests/commands/gc_test.py
index 02b3694..c128e93 100644
--- a/tests/commands/gc_test.py
+++ b/tests/commands/gc_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import pre_commit.constants as C
diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py
index 37b78bc..b0159f8 100644
--- a/tests/commands/hook_impl_test.py
+++ b/tests/commands/hook_impl_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import subprocess
import sys
from unittest import mock
diff --git a/tests/commands/init_templatedir_test.py b/tests/commands/init_templatedir_test.py
index 4e131df..64bfc8b 100644
--- a/tests/commands/init_templatedir_test.py
+++ b/tests/commands/init_templatedir_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
from unittest import mock
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 0b2e248..ae668ac 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import re
@@ -5,6 +7,7 @@ import re_assert
import pre_commit.constants as C
from pre_commit import git
+from pre_commit.commands.install_uninstall import _hook_types
from pre_commit.commands.install_uninstall import CURRENT_HASH
from pre_commit.commands.install_uninstall import install
from pre_commit.commands.install_uninstall import install_hooks
@@ -25,6 +28,36 @@ from testing.util import cwd
from testing.util import git_commit
+def test_hook_types_explicitly_listed():
+ assert _hook_types(os.devnull, ['pre-push']) == ['pre-push']
+
+
+def test_hook_types_default_value_when_not_specified():
+ assert _hook_types(os.devnull, None) == ['pre-commit']
+
+
+def test_hook_types_configured(tmpdir):
+ cfg = tmpdir.join('t.cfg')
+ cfg.write('default_install_hook_types: [pre-push]\nrepos: []\n')
+
+ assert _hook_types(str(cfg), None) == ['pre-push']
+
+
+def test_hook_types_configured_nonsense(tmpdir):
+ cfg = tmpdir.join('t.cfg')
+ cfg.write('default_install_hook_types: []\nrepos: []\n')
+
+ # hopefully the user doesn't do this, but the code allows it!
+ assert _hook_types(str(cfg), None) == []
+
+
+def test_hook_types_configuration_has_error(tmpdir):
+ cfg = tmpdir.join('t.cfg')
+ cfg.write('[')
+
+ assert _hook_types(str(cfg), None) == ['pre-commit']
+
+
def test_is_not_script():
assert is_our_script('setup.py') is False
@@ -59,7 +92,7 @@ def test_install_multiple_hooks_at_once(in_git_dir, store):
install(C.CONFIG_FILE, store, hook_types=['pre-commit', 'pre-push'])
assert in_git_dir.join('.git/hooks/pre-commit').exists()
assert in_git_dir.join('.git/hooks/pre-push').exists()
- uninstall(hook_types=['pre-commit', 'pre-push'])
+ uninstall(C.CONFIG_FILE, hook_types=['pre-commit', 'pre-push'])
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
assert not in_git_dir.join('.git/hooks/pre-push').exists()
@@ -77,14 +110,14 @@ def test_install_hooks_dead_symlink(in_git_dir, store):
def test_uninstall_does_not_blow_up_when_not_there(in_git_dir):
- assert uninstall(hook_types=['pre-commit']) == 0
+ assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
def test_uninstall(in_git_dir, store):
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
install(C.CONFIG_FILE, store, hook_types=['pre-commit'])
assert in_git_dir.join('.git/hooks/pre-commit').exists()
- uninstall(hook_types=['pre-commit'])
+ uninstall(C.CONFIG_FILE, hook_types=['pre-commit'])
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
@@ -414,7 +447,7 @@ def test_uninstall_restores_legacy_hooks(tempdir_factory, store):
# Now install and uninstall pre-commit
assert install(C.CONFIG_FILE, store, hook_types=['pre-commit']) == 0
- assert uninstall(hook_types=['pre-commit']) == 0
+ assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
# Make sure we installed the "old" hook correctly
ret, output = _get_commit_output(tempdir_factory, touch_file='baz')
@@ -449,7 +482,7 @@ def test_uninstall_doesnt_remove_not_our_hooks(in_git_dir):
pre_commit.write('#!/usr/bin/env bash\necho 1\n')
make_executable(pre_commit.strpath)
- assert uninstall(hook_types=['pre-commit']) == 0
+ assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
assert pre_commit.exists()
@@ -1005,3 +1038,16 @@ def test_install_temporarily_allow_mising_config(tempdir_factory, store):
'Skipping `pre-commit`.'
)
assert expected in output
+
+
+def test_install_uninstall_default_hook_types(in_git_dir, store):
+ cfg_src = 'default_install_hook_types: [pre-commit, pre-push]\nrepos: []\n'
+ in_git_dir.join(C.CONFIG_FILE).write(cfg_src)
+
+ assert not install(C.CONFIG_FILE, store, hook_types=None)
+ assert os.access(in_git_dir.join('.git/hooks/pre-commit').strpath, os.X_OK)
+ assert os.access(in_git_dir.join('.git/hooks/pre-push').strpath, os.X_OK)
+
+ assert not uninstall(C.CONFIG_FILE, hook_types=None)
+ assert not in_git_dir.join('.git/hooks/pre-commit').exists()
+ assert not in_git_dir.join('.git/hooks/pre-push').exists()
diff --git a/tests/commands/migrate_config_test.py b/tests/commands/migrate_config_test.py
index f5eddd3..b80244e 100644
--- a/tests/commands/migrate_config_test.py
+++ b/tests/commands/migrate_config_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pre_commit.constants as C
from pre_commit.commands.migrate_config import migrate_config
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index 3a6fa2a..085b063 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import shlex
import sys
diff --git a/tests/commands/sample_config_test.py b/tests/commands/sample_config_test.py
index 8e3a904..cf56e98 100644
--- a/tests/commands/sample_config_test.py
+++ b/tests/commands/sample_config_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit.commands.sample_config import sample_config
diff --git a/tests/commands/try_repo_test.py b/tests/commands/try_repo_test.py
index a157d16..0b2db7e 100644
--- a/tests/commands/try_repo_test.py
+++ b/tests/commands/try_repo_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import re
import time
diff --git a/tests/conftest.py b/tests/conftest.py
index f38f969..b68a1d0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import functools
import io
import logging
diff --git a/tests/envcontext_test.py b/tests/envcontext_test.py
index f9d4dce..c82d326 100644
--- a/tests/envcontext_test.py
+++ b/tests/envcontext_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
from unittest import mock
diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py
index cb76dcf..31c71d2 100644
--- a/tests/error_handler_test.py
+++ b/tests/error_handler_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import stat
import sys
diff --git a/tests/git_test.py b/tests/git_test.py
index bcb3fd1..b9f524a 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import pytest
@@ -19,6 +21,20 @@ def test_get_root_deeper(in_git_dir):
assert os.path.normcase(git.get_root()) == expected
+def test_get_root_in_git_sub_dir(in_git_dir):
+ expected = os.path.normcase(in_git_dir.strpath)
+ with pytest.raises(FatalError):
+ with in_git_dir.join('.git/objects').ensure_dir().as_cwd():
+ assert os.path.normcase(git.get_root()) == expected
+
+
+def test_get_root_not_in_working_dir(in_git_dir):
+ expected = os.path.normcase(in_git_dir.strpath)
+ with pytest.raises(FatalError):
+ with in_git_dir.join('..').ensure_dir().as_cwd():
+ assert os.path.normcase(git.get_root()) == expected
+
+
def test_in_exactly_dot_git(in_git_dir):
with in_git_dir.join('.git').as_cwd(), pytest.raises(FatalError):
git.get_root()
@@ -38,6 +54,22 @@ def test_get_root_bare_worktree(tmpdir):
assert git.get_root() == os.path.abspath('.')
+def test_get_git_dir(tmpdir):
+ """Regression test for #1972"""
+ src = tmpdir.join('src').ensure_dir()
+ cmd_output('git', 'init', str(src))
+ git_commit(cwd=str(src))
+
+ worktree = tmpdir.join('worktree').ensure_dir()
+ cmd_output('git', 'worktree', 'add', '../worktree', cwd=src)
+
+ with worktree.as_cwd():
+ assert git.get_git_dir() == src.ensure_dir(
+ '.git/worktrees/worktree',
+ )
+ assert git.get_git_common_dir() == src.ensure_dir('.git')
+
+
def test_get_root_worktree_in_git(tmpdir):
src = tmpdir.join('src').ensure_dir()
cmd_output('git', 'init', str(src))
diff --git a/tests/languages/conda_test.py b/tests/languages/conda_test.py
index 6faa78f..5023b2a 100644
--- a/tests/languages/conda_test.py
+++ b/tests/languages/conda_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit import envcontext
diff --git a/tests/languages/docker_test.py b/tests/languages/docker_test.py
index ec6bb83..5838761 100644
--- a/tests/languages/docker_test.py
+++ b/tests/languages/docker_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import builtins
import json
import ntpath
diff --git a/tests/languages/golang_test.py b/tests/languages/golang_test.py
index 9a64ed1..9e393cb 100644
--- a/tests/languages/golang_test.py
+++ b/tests/languages/golang_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit.languages.golang import guess_go_dir
diff --git a/tests/languages/helpers_test.py b/tests/languages/helpers_test.py
index fd9b9a4..259cb97 100644
--- a/tests/languages/helpers_test.py
+++ b/tests/languages/helpers_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import multiprocessing
import os.path
import sys
@@ -86,7 +88,9 @@ def test_assert_no_additional_deps():
helpers.assert_no_additional_deps('lang', ['hmmm'])
msg, = excinfo.value.args
assert msg == (
- 'For now, pre-commit does not support additional_dependencies for lang'
+ 'for now, pre-commit does not support additional_dependencies for '
+ 'lang -- '
+ "you selected `additional_dependencies: ['hmmm']`"
)
diff --git a/tests/languages/node_test.py b/tests/languages/node_test.py
index 8e52268..fb5ae71 100644
--- a/tests/languages/node_test.py
+++ b/tests/languages/node_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import json
import os
import shutil
diff --git a/tests/languages/pygrep_test.py b/tests/languages/pygrep_test.py
index d8bacc4..8420046 100644
--- a/tests/languages/pygrep_test.py
+++ b/tests/languages/pygrep_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit.languages import pygrep
diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py
index 8324cac..6160669 100644
--- a/tests/languages/python_test.py
+++ b/tests/languages/python_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import sys
from unittest import mock
@@ -47,16 +49,16 @@ def test_norm_version_of_default_is_sys_executable():
assert python.norm_version('default') is None
-@pytest.mark.parametrize('v', ('python3.6', 'python3', 'python'))
+@pytest.mark.parametrize('v', ('python3.9', 'python3', 'python'))
def test_sys_executable_matches(v):
- with mock.patch.object(sys, 'version_info', (3, 6, 7)):
+ with mock.patch.object(sys, 'version_info', (3, 9, 10)):
assert python._sys_executable_matches(v)
assert python.norm_version(v) is None
@pytest.mark.parametrize('v', ('notpython', 'python3.x'))
def test_sys_executable_matches_does_not_match(v):
- with mock.patch.object(sys, 'version_info', (3, 6, 7)):
+ with mock.patch.object(sys, 'version_info', (3, 9, 10)):
assert not python._sys_executable_matches(v)
@@ -65,7 +67,7 @@ def test_sys_executable_matches_does_not_match(v):
('/usr/bin/python3', '/usr/bin/python3.7', 'python3'),
('/usr/bin/python', '/usr/bin/python3.7', 'python3.7'),
('/usr/bin/python', '/usr/bin/python', None),
- ('/usr/bin/python3.6m', '/usr/bin/python3.6m', 'python3.6m'),
+ ('/usr/bin/python3.7m', '/usr/bin/python3.7m', 'python3.7m'),
('v/bin/python', 'v/bin/pypy', 'pypy'),
),
)
diff --git a/tests/languages/r_test.py b/tests/languages/r_test.py
index 66aa7b3..5bc63b2 100644
--- a/tests/languages/r_test.py
+++ b/tests/languages/r_test.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
import os.path
import pytest
+from pre_commit import envcontext
from pre_commit.languages import r
from testing.fixtures import make_config_from_repo
from testing.fixtures import make_repo
@@ -127,3 +130,14 @@ def test_r_parsing_file_local(tempdir_factory, store):
config=config,
expect_path_prefix=False,
)
+
+
+def test_rscript_exec_relative_to_r_home():
+ expected = os.path.join('r_home_dir', 'bin', 'Rscript')
+ with envcontext.envcontext((('R_HOME', 'r_home_dir'),)):
+ assert r._rscript_exec() == expected
+
+
+def test_path_rscript_exec_no_r_home_set():
+ with envcontext.envcontext((('R_HOME', envcontext.UNSET),)):
+ assert r._rscript_exec() == 'Rscript'
diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py
index 7dff046..dc55456 100644
--- a/tests/languages/ruby_test.py
+++ b/tests/languages/ruby_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import tarfile
from unittest import mock
diff --git a/tests/logging_handler_test.py b/tests/logging_handler_test.py
index fe68593..dc43a99 100644
--- a/tests/logging_handler_test.py
+++ b/tests/logging_handler_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import logging
from pre_commit import color
diff --git a/tests/main_test.py b/tests/main_test.py
index 1ad8d41..a645300 100644
--- a/tests/main_test.py
+++ b/tests/main_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import argparse
import os.path
from unittest import mock
@@ -12,20 +14,6 @@ from testing.auto_namedtuple import auto_namedtuple
from testing.util import cwd
-@pytest.mark.parametrize(
- ('argv', 'expected'),
- (
- ((), ['f']),
- (('--f', 'x'), ['x']),
- (('--f', 'x', '--f', 'y'), ['x', 'y']),
- ),
-)
-def test_append_replace_default(argv, expected):
- parser = argparse.ArgumentParser()
- parser.add_argument('--f', action=main.AppendReplaceDefault, default=['f'])
- assert parser.parse_args(argv).f == expected
-
-
def _args(**kwargs):
kwargs.setdefault('command', 'help')
kwargs.setdefault('config', C.CONFIG_FILE)
@@ -170,7 +158,7 @@ def test_init_templatedir(mock_store_dir):
assert patch.call_count == 1
assert 'tdir' in patch.call_args[0]
- assert patch.call_args[1]['hook_types'] == ['pre-commit']
+ assert patch.call_args[1]['hook_types'] is None
assert patch.call_args[1]['skip_on_missing_config'] is True
diff --git a/tests/meta_hooks/check_hooks_apply_test.py b/tests/meta_hooks/check_hooks_apply_test.py
index 06bdd04..63f9715 100644
--- a/tests/meta_hooks/check_hooks_apply_test.py
+++ b/tests/meta_hooks/check_hooks_apply_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit.meta_hooks import check_hooks_apply
from testing.fixtures import add_config_to_repo
diff --git a/tests/meta_hooks/check_useless_excludes_test.py b/tests/meta_hooks/check_useless_excludes_test.py
index 703bd25..15b68b4 100644
--- a/tests/meta_hooks/check_useless_excludes_test.py
+++ b/tests/meta_hooks/check_useless_excludes_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit import git
from pre_commit.meta_hooks import check_useless_excludes
from pre_commit.util import cmd_output
diff --git a/tests/meta_hooks/identity_test.py b/tests/meta_hooks/identity_test.py
index 3eff00b..97c20ea 100644
--- a/tests/meta_hooks/identity_test.py
+++ b/tests/meta_hooks/identity_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit.meta_hooks import identity
diff --git a/tests/output_test.py b/tests/output_test.py
index 1cdacbb..c806829 100644
--- a/tests/output_test.py
+++ b/tests/output_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
from pre_commit import output
diff --git a/tests/parse_shebang_test.py b/tests/parse_shebang_test.py
index 0bb19c7..d7acbf5 100644
--- a/tests/parse_shebang_test.py
+++ b/tests/parse_shebang_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import os.path
import shutil
diff --git a/tests/prefix_test.py b/tests/prefix_test.py
index 6ce8be1..1eac087 100644
--- a/tests/prefix_test.py
+++ b/tests/prefix_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import pytest
diff --git a/tests/repository_test.py b/tests/repository_test.py
index 8569ba9..cef6887 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import os.path
import shutil
-import sys
from typing import Any
-from typing import Dict
from unittest import mock
import cfgv
@@ -875,7 +875,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('python_hooks_repo')
+ repo_path = get_resource_path('python3_hooks_repo')
manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE))
hooks = [
dict(hook, additional_dependencies=[repo_path]) for hook in manifest
@@ -883,21 +883,27 @@ def local_python_config():
return {'repo': 'local', 'hooks': hooks}
-@pytest.mark.xfail( # pragma: win32 no cover
- sys.platform == 'win32',
- reason='microsoft/azure-pipelines-image-generation#989',
-)
def test_local_python_repo(store, local_python_config):
- hook = _get_hook(local_python_config, store, 'foo')
+ 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"
+
+
+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')
# 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"['filename']\nHello World\n"
+ assert _norm_out(out) == b"2\n['filename']\nHello World\n"
def test_default_language_version(store, local_python_config):
- config: Dict[str, Any] = {
+ config: dict[str, Any] = {
'default_language_version': {'python': 'fake'},
'default_stages': ['commit'],
'repos': [local_python_config],
@@ -914,7 +920,7 @@ def test_default_language_version(store, local_python_config):
def test_default_stages(store, local_python_config):
- config: Dict[str, Any] = {
+ config: dict[str, Any] = {
'default_language_version': {'python': C.DEFAULT},
'default_stages': ['commit'],
'repos': [local_python_config],
diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py
index 2e3f620..a91f315 100644
--- a/tests/staged_files_only_test.py
+++ b/tests/staged_files_only_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import itertools
import os.path
import shutil
diff --git a/tests/store_test.py b/tests/store_test.py
index 5a5d69e..ff671a8 100644
--- a/tests/store_test.py
+++ b/tests/store_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import sqlite3
import stat
diff --git a/tests/util_test.py b/tests/util_test.py
index 01afbd4..6b00f9f 100644
--- a/tests/util_test.py
+++ b/tests/util_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import stat
import subprocess
diff --git a/tests/xargs_test.py b/tests/xargs_test.py
index 7e83ef5..0530e50 100644
--- a/tests/xargs_test.py
+++ b/tests/xargs_test.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import concurrent.futures
import os
import sys
import time
-from typing import Tuple
from unittest import mock
import pytest
@@ -178,7 +179,7 @@ def test_thread_mapper_concurrency_uses_regular_map():
def test_xargs_propagate_kwargs_to_cmd():
env = {'PRE_COMMIT_TEST_VAR': 'Pre commit is awesome'}
- cmd: Tuple[str, ...] = ('bash', '-c', 'echo $PRE_COMMIT_TEST_VAR', '--')
+ cmd: tuple[str, ...] = ('bash', '-c', 'echo $PRE_COMMIT_TEST_VAR', '--')
cmd = parse_shebang.normalize_cmd(cmd)
ret, stdout = xargs.xargs(cmd, ('1',), env=env)
diff --git a/tox.ini b/tox.ini
index 11b20d4..7f43e41 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py36,py37,py38,pypy3,pre-commit
+envlist = py37,py38,pypy3,pre-commit
[testenv]
deps = -rrequirements-dev.txt