1
0
Fork 0

Merging upstream version 2.18.1.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 21:33:11 +01:00
parent 131d7bde70
commit 3396d2e509
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
116 changed files with 718 additions and 410 deletions

3
.gitignore vendored
View file

@ -1,9 +1,6 @@
*.egg-info *.egg-info
*.py[co] *.py[co]
/.coverage /.coverage
/.mypy_cache
/.pytest_cache
/.tox /.tox
/dist /dist
/venv*
.vscode/ .vscode/

View file

@ -4,52 +4,42 @@ repos:
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-docstring-first
- id: check-json
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- id: double-quote-string-fixer
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: double-quote-string-fixer - repo: https://github.com/asottile/setup-cfg-fmt
- repo: https://github.com/PyCQA/flake8 rev: v1.20.0
rev: 4.0.1
hooks: hooks:
- id: flake8 - id: setup-cfg-fmt
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
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/reorder_python_imports
rev: v2.6.0 rev: v3.0.1
hooks: hooks:
- id: reorder-python-imports - 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 - repo: https://github.com/asottile/add-trailing-comma
rev: v2.2.1 rev: v2.2.1
hooks: hooks:
- id: add-trailing-comma - id: add-trailing-comma
args: [--py36-plus] args: [--py36-plus]
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/pyupgrade
rev: v1.20.0 rev: v2.31.1
hooks: 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 - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.931 rev: v0.942
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-all] additional_dependencies: [types-all]
exclude: ^testing/resources/ exclude: ^testing/resources/
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes

View file

@ -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 2.17.0 - 2022-01-18
=================== ===================

View file

@ -93,7 +93,7 @@ language, for example:
here are the apis that should be implemented for a language 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` #### `ENVIRONMENT_DIR`

View file

@ -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) [![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/master.svg)](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/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/master.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/master) [![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 ## pre-commit

View file

@ -1,6 +1,6 @@
trigger: trigger:
branches: branches:
include: [master, test-me-*] include: [main, test-me-*]
tags: tags:
include: ['*'] include: ['*']
@ -50,7 +50,7 @@ jobs:
displayName: install R displayName: install R
- template: job--python-tox.yml@asottile - template: job--python-tox.yml@asottile
parameters: parameters:
toxenvs: [pypy3, py36, py37, py38, py39] toxenvs: [py37, py38, py39]
os: linux os: linux
pre_test: pre_test:
- task: UseRubyVersion@0 - task: UseRubyVersion@0

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from pre_commit.main import main from pre_commit.main import main

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import argparse import argparse
import functools import functools
import logging import logging
@ -5,8 +7,6 @@ import re
import shlex import shlex
import sys import sys
from typing import Any from typing import Any
from typing import Dict
from typing import Optional
from typing import Sequence from typing import Sequence
import cfgv 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.') parser = _make_argparser('Manifest filenames.')
args = parser.parse_args(argv) args = parser.parse_args(argv)
@ -116,7 +116,7 @@ META = 'meta'
# should inherit from cfgv.Conditional if sha support is dropped # should inherit from cfgv.Conditional if sha support is dropped
class WarnMutableRev(cfgv.ConditionalOptional): class WarnMutableRev(cfgv.ConditionalOptional):
def check(self, dct: Dict[str, Any]) -> None: def check(self, dct: dict[str, Any]) -> None:
super().check(dct) super().check(dct)
if self.key in dct: if self.key in dct:
@ -135,7 +135,7 @@ class WarnMutableRev(cfgv.ConditionalOptional):
class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault): class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
def check(self, dct: Dict[str, Any]) -> None: def check(self, dct: dict[str, Any]) -> None:
super().check(dct) super().check(dct)
if '/*' in dct.get(self.key, ''): if '/*' in dct.get(self.key, ''):
@ -154,7 +154,7 @@ class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
class OptionalSensibleRegexAtTop(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) super().check(dct)
if '/*' in dct.get(self.key, ''): if '/*' in dct.get(self.key, ''):
@ -183,7 +183,7 @@ class MigrateShaToRev:
ensure_absent=True, 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}: if dct.get('repo') in {LOCAL, META}:
self._cond('rev').check(dct) self._cond('rev').check(dct)
self._cond('sha').check(dct) self._cond('sha').check(dct)
@ -194,7 +194,7 @@ class MigrateShaToRev:
else: else:
self._cond('rev').check(dct) 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: if 'sha' in dct:
dct['rev'] = dct.pop('sha') dct['rev'] = dct.pop('sha')
@ -212,7 +212,7 @@ def _entry(modname: str) -> str:
def warn_unknown_keys_root( def warn_unknown_keys_root(
extra: Sequence[str], extra: Sequence[str],
orig_keys: Sequence[str], orig_keys: Sequence[str],
dct: Dict[str, str], dct: dict[str, str],
) -> None: ) -> None:
logger.warning(f'Unexpected key(s) present at root: {", ".join(extra)}') 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( def warn_unknown_keys_repo(
extra: Sequence[str], extra: Sequence[str],
orig_keys: Sequence[str], orig_keys: Sequence[str],
dct: Dict[str, str], dct: dict[str, str],
) -> None: ) -> None:
logger.warning( logger.warning(
f'Unexpected key(s) present on {dct["repo"]}: {", ".join(extra)}', f'Unexpected key(s) present on {dct["repo"]}: {", ".join(extra)}',
@ -253,7 +253,7 @@ _meta = (
class NotAllowed(cfgv.OptionalNoDefault): 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: if self.key in dct:
raise cfgv.ValidationError(f'{self.key!r} cannot be overridden') raise cfgv.ValidationError(f'{self.key!r} cannot be overridden')
@ -336,6 +336,11 @@ CONFIG_SCHEMA = cfgv.Map(
'Config', None, 'Config', None,
cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)), 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( cfgv.OptionalRecurse(
'default_language_version', DEFAULT_LANGUAGE_VERSION, {}, 'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
), ),
@ -355,6 +360,7 @@ CONFIG_SCHEMA = cfgv.Map(
cfgv.WarnAdditionalKeys( cfgv.WarnAdditionalKeys(
( (
'repos', 'repos',
'default_install_hook_types',
'default_language_version', 'default_language_version',
'default_stages', 'default_stages',
'files', 'files',
@ -377,7 +383,7 @@ class InvalidConfigError(FatalError):
pass 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) data = yaml_load(contents)
if isinstance(data, list): if isinstance(data, list):
logger.warning( 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.') parser = _make_argparser('Config filenames.')
args = parser.parse_args(argv) args = parser.parse_args(argv)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import argparse import argparse
import os import os
import sys import sys

View file

@ -1,12 +1,10 @@
from __future__ import annotations
import os.path import os.path
import re import re
from typing import Any from typing import Any
from typing import Dict
from typing import List
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import git from pre_commit import git
@ -29,13 +27,13 @@ from pre_commit.util import yaml_load
class RevInfo(NamedTuple): class RevInfo(NamedTuple):
repo: str repo: str
rev: str rev: str
frozen: Optional[str] frozen: str | None
@classmethod @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) 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) git_cmd = ('git', *git.NO_FS_MONITOR)
if tags_only: if tags_only:
@ -61,6 +59,9 @@ class RevInfo(NamedTuple):
except CalledProcessError: except CalledProcessError:
cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD') cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD')
rev = cmd_output(*cmd, cwd=tmp)[1].strip() rev = cmd_output(*cmd, cwd=tmp)[1].strip()
else:
if tags_only:
rev = git.get_best_candidate_tag(rev, tmp)
frozen = None frozen = None
if freeze: if freeze:
@ -76,7 +77,7 @@ class RepositoryCannotBeUpdatedError(RuntimeError):
def _check_hooks_still_exist_at_rev( def _check_hooks_still_exist_at_rev(
repo_config: Dict[str, Any], repo_config: dict[str, Any],
info: RevInfo, info: RevInfo,
store: Store, store: Store,
) -> None: ) -> None:
@ -101,9 +102,9 @@ REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$')
def _original_lines( def _original_lines(
path: str, path: str,
rev_infos: List[Optional[RevInfo]], rev_infos: list[RevInfo | None],
retry: bool = False, retry: bool = False,
) -> Tuple[List[str], List[int]]: ) -> tuple[list[str], list[int]]:
"""detect `rev:` lines or reformat the file""" """detect `rev:` lines or reformat the file"""
with open(path, newline='') as f: with open(path, newline='') as f:
original = f.read() original = f.read()
@ -120,7 +121,7 @@ def _original_lines(
return _original_lines(path, rev_infos, retry=True) 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) lines, idxs = _original_lines(path, rev_infos)
for idx, rev_info in zip(idxs, 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.""" """Auto-update the pre-commit config to the latest versions of repos."""
migrate_config(config_file, quiet=True) migrate_config(config_file, quiet=True)
retv = 0 retv = 0
rev_infos: List[Optional[RevInfo]] = [] rev_infos: list[RevInfo | None] = []
changed = False changed = False
config = load_config(config_file) config = load_config(config_file)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
from pre_commit import output from pre_commit import output

View file

@ -1,8 +1,7 @@
from __future__ import annotations
import os.path import os.path
from typing import Any from typing import Any
from typing import Dict
from typing import Set
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import output from pre_commit import output
@ -17,9 +16,9 @@ from pre_commit.store import Store
def _mark_used_repos( def _mark_used_repos(
store: Store, store: Store,
all_repos: Dict[Tuple[str, str], str], all_repos: dict[tuple[str, str], str],
unused_repos: Set[Tuple[str, str]], unused_repos: set[tuple[str, str]],
repo: Dict[str, Any], repo: dict[str, Any],
) -> None: ) -> None:
if repo['repo'] == META: if repo['repo'] == META:
return return

View file

@ -1,10 +1,10 @@
from __future__ import annotations
import argparse import argparse
import os.path import os.path
import subprocess import subprocess
import sys import sys
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.commands.run import run from pre_commit.commands.run import run
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -18,7 +18,7 @@ def _run_legacy(
hook_type: str, hook_type: str,
hook_dir: str, hook_dir: str,
args: Sequence[str], args: Sequence[str],
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'): if os.environ.get('PRE_COMMIT_RUNNING_LEGACY'):
raise SystemExit( raise SystemExit(
f"bug: pre-commit's script is installed in migration mode\n" f"bug: pre-commit's script is installed in migration mode\n"
@ -69,16 +69,16 @@ def _ns(
color: bool, color: bool,
*, *,
all_files: bool = False, all_files: bool = False,
remote_branch: Optional[str] = None, remote_branch: str | None = None,
local_branch: Optional[str] = None, local_branch: str | None = None,
from_ref: Optional[str] = None, from_ref: str | None = None,
to_ref: Optional[str] = None, to_ref: str | None = None,
remote_name: Optional[str] = None, remote_name: str | None = None,
remote_url: Optional[str] = None, remote_url: str | None = None,
commit_msg_filename: Optional[str] = None, commit_msg_filename: str | None = None,
checkout_type: Optional[str] = None, checkout_type: str | None = None,
is_squash_merge: Optional[str] = None, is_squash_merge: str | None = None,
rewrite_command: Optional[str] = None, rewrite_command: str | None = None,
) -> argparse.Namespace: ) -> argparse.Namespace:
return argparse.Namespace( return argparse.Namespace(
color=color, color=color,
@ -109,7 +109,7 @@ def _pre_push_ns(
color: bool, color: bool,
args: Sequence[str], args: Sequence[str],
stdin: bytes, stdin: bytes,
) -> Optional[argparse.Namespace]: ) -> argparse.Namespace | None:
remote_name = args[0] remote_name = args[0]
remote_url = args[1] remote_url = args[1]
@ -197,7 +197,7 @@ def _run_ns(
color: bool, color: bool,
args: Sequence[str], args: Sequence[str],
stdin: bytes, stdin: bytes,
) -> Optional[argparse.Namespace]: ) -> argparse.Namespace | None:
_check_args_length(hook_type, args) _check_args_length(hook_type, args)
if hook_type == 'pre-push': if hook_type == 'pre-push':
return _pre_push_ns(color, args, stdin) return _pre_push_ns(color, args, stdin)

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import logging import logging
import os.path import os.path
from typing import Sequence
from pre_commit.commands.install_uninstall import install from pre_commit.commands.install_uninstall import install
from pre_commit.store import Store from pre_commit.store import Store
@ -14,7 +15,7 @@ def init_templatedir(
config_file: str, config_file: str,
store: Store, store: Store,
directory: str, directory: str,
hook_types: Sequence[str], hook_types: list[str] | None,
skip_on_missing_config: bool = True, skip_on_missing_config: bool = True,
) -> int: ) -> int:
install( install(

View file

@ -1,14 +1,14 @@
from __future__ import annotations
import logging import logging
import os.path import os.path
import shlex import shlex
import shutil import shutil
import sys import sys
from typing import Optional
from typing import Sequence
from typing import Tuple
from pre_commit import git from pre_commit import git
from pre_commit import output from pre_commit import output
from pre_commit.clientlib import InvalidConfigError
from pre_commit.clientlib import load_config from pre_commit.clientlib import load_config
from pre_commit.repository import all_hooks from pre_commit.repository import all_hooks
from pre_commit.repository import install_hook_envs from pre_commit.repository import install_hook_envs
@ -32,11 +32,23 @@ TEMPLATE_START = '# start templated\n'
TEMPLATE_END = '# end 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( def _hook_paths(
hook_type: str, hook_type: str,
git_dir: Optional[str] = None, git_dir: str | None = None,
) -> Tuple[str, str]: ) -> tuple[str, str]:
git_dir = git_dir if git_dir is not None else git.get_git_dir() 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) pth = os.path.join(git_dir, 'hooks', hook_type)
return pth, f'{pth}.legacy' return pth, f'{pth}.legacy'
@ -54,7 +66,7 @@ def _install_hook_script(
hook_type: str, hook_type: str,
overwrite: bool = False, overwrite: bool = False,
skip_on_missing_config: bool = False, skip_on_missing_config: bool = False,
git_dir: Optional[str] = None, git_dir: str | None = None,
) -> None: ) -> None:
hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir)
@ -103,11 +115,11 @@ def _install_hook_script(
def install( def install(
config_file: str, config_file: str,
store: Store, store: Store,
hook_types: Sequence[str], hook_types: list[str] | None,
overwrite: bool = False, overwrite: bool = False,
hooks: bool = False, hooks: bool = False,
skip_on_missing_config: bool = False, skip_on_missing_config: bool = False,
git_dir: Optional[str] = None, git_dir: str | None = None,
) -> int: ) -> int:
if git_dir is None and git.has_core_hookpaths_set(): if git_dir is None and git.has_core_hookpaths_set():
logger.error( logger.error(
@ -116,7 +128,7 @@ def install(
) )
return 1 return 1
for hook_type in hook_types: for hook_type in _hook_types(config_file, hook_types):
_install_hook_script( _install_hook_script(
config_file, hook_type, config_file, hook_type,
overwrite=overwrite, overwrite=overwrite,
@ -150,7 +162,7 @@ def _uninstall_hook_script(hook_type: str) -> None:
output.write_line(f'Restored previous hooks to {hook_path}') output.write_line(f'Restored previous hooks to {hook_path}')
def uninstall(hook_types: Sequence[str]) -> int: def uninstall(config_file: str, hook_types: list[str] | None) -> int:
for hook_type in hook_types: for hook_type in _hook_types(config_file, hook_types):
_uninstall_hook_script(hook_type) _uninstall_hook_script(hook_type)
return 0 return 0

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import re import re
import textwrap import textwrap

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import argparse import argparse
import contextlib import contextlib
import functools import functools
@ -9,12 +11,8 @@ import time
import unicodedata import unicodedata
from typing import Any from typing import Any
from typing import Collection from typing import Collection
from typing import Dict
from typing import List
from typing import MutableMapping from typing import MutableMapping
from typing import Sequence from typing import Sequence
from typing import Set
from typing import Tuple
from identify.identify import tags_from_path from identify.identify import tags_from_path
@ -62,7 +60,7 @@ def filter_by_include_exclude(
names: Collection[str], names: Collection[str],
include: str, include: str,
exclude: str, exclude: str,
) -> List[str]: ) -> list[str]:
include_re, exclude_re = re.compile(include), re.compile(exclude) include_re, exclude_re = re.compile(include), re.compile(exclude)
return [ return [
filename for filename in names filename for filename in names
@ -76,7 +74,7 @@ class Classifier:
self.filenames = [f for f in filenames if os.path.lexists(f)] self.filenames = [f for f in filenames if os.path.lexists(f)]
@functools.lru_cache(maxsize=None) @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) return tags_from_path(filename)
def by_types( def by_types(
@ -85,7 +83,7 @@ class Classifier:
types: Collection[str], types: Collection[str],
types_or: Collection[str], types_or: Collection[str],
exclude_types: Collection[str], exclude_types: Collection[str],
) -> List[str]: ) -> list[str]:
types = frozenset(types) types = frozenset(types)
types_or = frozenset(types_or) types_or = frozenset(types_or)
exclude_types = frozenset(exclude_types) exclude_types = frozenset(exclude_types)
@ -100,7 +98,7 @@ class Classifier:
ret.append(filename) ret.append(filename)
return ret return ret
def filenames_for_hook(self, hook: Hook) -> Tuple[str, ...]: def filenames_for_hook(self, hook: Hook) -> tuple[str, ...]:
names = self.filenames names = self.filenames
names = filter_by_include_exclude(names, hook.files, hook.exclude) names = filter_by_include_exclude(names, hook.files, hook.exclude)
names = self.by_types( names = self.by_types(
@ -117,7 +115,7 @@ class Classifier:
filenames: Collection[str], filenames: Collection[str],
include: str, include: str,
exclude: str, exclude: str,
) -> 'Classifier': ) -> Classifier:
# on windows we normalize all filenames to use forward slashes # on windows we normalize all filenames to use forward slashes
# this makes it easier to filter using the `files:` regex # this makes it easier to filter using the `files:` regex
# this also makes improperly quoted shell-based hooks work better # this also makes improperly quoted shell-based hooks work better
@ -128,7 +126,7 @@ class Classifier:
return Classifier(filenames) 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', '') skips = environ.get('SKIP', '')
return {skip.strip() for skip in skips.split(',') if skip.strip()} 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( def _run_single_hook(
classifier: Classifier, classifier: Classifier,
hook: Hook, hook: Hook,
skips: Set[str], skips: set[str],
cols: int, cols: int,
diff_before: bytes, diff_before: bytes,
verbose: bool, verbose: bool,
use_color: bool, use_color: bool,
) -> Tuple[bool, bytes]: ) -> tuple[bool, bytes]:
filenames = classifier.filenames_for_hook(hook) filenames = classifier.filenames_for_hook(hook)
if hook.id in skips or hook.alias in skips: if hook.id in skips or hook.alias in skips:
@ -271,9 +269,9 @@ def _get_diff() -> bytes:
def _run_hooks( def _run_hooks(
config: Dict[str, Any], config: dict[str, Any],
hooks: Sequence[Hook], hooks: Sequence[Hook],
skips: Set[str], skips: set[str],
args: argparse.Namespace, args: argparse.Namespace,
) -> int: ) -> int:
"""Actually run the hooks.""" """Actually run the hooks."""

View file

@ -2,6 +2,7 @@
# determine the latest revision? This adds ~200ms from my tests (and is # determine the latest revision? This adds ~200ms from my tests (and is
# significantly faster than https:// or http://). For now, periodically # significantly faster than https:// or http://). For now, periodically
# manually updating the revision is fine. # manually updating the revision is fine.
from __future__ import annotations
SAMPLE_CONFIG = '''\ SAMPLE_CONFIG = '''\
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks

View file

@ -1,8 +1,8 @@
from __future__ import annotations
import argparse import argparse
import logging import logging
import os.path import os.path
from typing import Optional
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import git from pre_commit import git
@ -18,7 +18,7 @@ from pre_commit.xargs import xargs
logger = logging.getLogger(__name__) 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 explicitly passed, use it
if ref is not None: if ref is not None:
return repo, ref return repo, ref

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import sys import sys
if sys.version_info >= (3, 8): # pragma: >=3.8 cover if sys.version_info >= (3, 8): # pragma: >=3.8 cover
@ -22,4 +24,10 @@ STAGES = (
'post-rewrite', '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' DEFAULT = 'default'

View file

@ -1,10 +1,11 @@
from __future__ import annotations
import contextlib import contextlib
import enum import enum
import os import os
from typing import Generator from typing import Generator
from typing import MutableMapping from typing import MutableMapping
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
@ -32,7 +33,7 @@ def format_env(parts: SubstitutionT, env: MutableMapping[str, str]) -> str:
@contextlib.contextmanager @contextlib.contextmanager
def envcontext( def envcontext(
patch: PatchesT, patch: PatchesT,
_env: Optional[MutableMapping[str, str]] = None, _env: MutableMapping[str, str] | None = None,
) -> Generator[None, None, None]: ) -> Generator[None, None, None]:
"""In this context, `os.environ` is modified according to `patch`. """In this context, `os.environ` is modified according to `patch`.

View file

@ -1,9 +1,12 @@
from __future__ import annotations
import contextlib import contextlib
import functools import functools
import os.path import os.path
import sys import sys
import traceback import traceback
from typing import Generator from typing import Generator
from typing import IO
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import output from pre_commit import output
@ -30,7 +33,7 @@ def _log_and_exit(
with contextlib.ExitStack() as ctx: with contextlib.ExitStack() as ctx:
if os.access(storedir, os.W_OK): if os.access(storedir, os.W_OK):
output.write_line(f'Check the log at {log_path}') 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 else: # pragma: win32 no cover
output.write_line(f'Failed to write to log at {log_path}') output.write_line(f'Failed to write to log at {log_path}')
log = sys.stdout.buffer log = sys.stdout.buffer

View file

@ -1,2 +1,5 @@
from __future__ import annotations
class FatalError(RuntimeError): class FatalError(RuntimeError):
pass pass

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import errno import errno
import sys import sys
@ -20,13 +22,11 @@ if sys.platform == 'win32': # pragma: no cover (windows)
blocked_cb: Callable[[], None], blocked_cb: Callable[[], None],
) -> Generator[None, None, None]: ) -> Generator[None, None, None]:
try: try:
# TODO: https://github.com/python/typeshed/pull/3607
msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region) msvcrt.locking(fileno, msvcrt.LK_NBLCK, _region)
except OSError: except OSError:
blocked_cb() blocked_cb()
while True: while True:
try: try:
# TODO: https://github.com/python/typeshed/pull/3607
msvcrt.locking(fileno, msvcrt.LK_LOCK, _region) msvcrt.locking(fileno, msvcrt.LK_LOCK, _region)
except OSError as e: except OSError as e:
# Locking violation. Returned when the _LK_LOCK or _LK_RLCK # 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: # The documentation however states:
# "Regions should be locked only briefly and should be unlocked # "Regions should be locked only briefly and should be unlocked
# before closing a file or exiting the program." # before closing a file or exiting the program."
# TODO: https://github.com/python/typeshed/pull/3607
msvcrt.locking(fileno, msvcrt.LK_UNLCK, _region) msvcrt.locking(fileno, msvcrt.LK_UNLCK, _region)
else: # pragma: win32 no cover else: # pragma: win32 no cover
import fcntl import fcntl

View file

@ -1,11 +1,9 @@
from __future__ import annotations
import logging import logging
import os.path import os.path
import sys import sys
from typing import Dict
from typing import List
from typing import MutableMapping from typing import MutableMapping
from typing import Optional
from typing import Set
from pre_commit.errors import FatalError from pre_commit.errors import FatalError
from pre_commit.util import CalledProcessError from pre_commit.util import CalledProcessError
@ -18,7 +16,7 @@ logger = logging.getLogger(__name__)
NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false') NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false')
def zsplit(s: str) -> List[str]: def zsplit(s: str) -> list[str]:
s = s.strip('\0') s = s.strip('\0')
if s: if s:
return s.split('\0') return s.split('\0')
@ -27,8 +25,8 @@ def zsplit(s: str) -> List[str]:
def no_git_env( def no_git_env(
_env: Optional[MutableMapping[str, str]] = None, _env: MutableMapping[str, str] | None = None,
) -> Dict[str, str]: ) -> dict[str, str]:
# Too many bugs dealing with environment variables and GIT: # Too many bugs dealing with environment variables and GIT:
# https://github.com/pre-commit/pre-commit/issues/300 # https://github.com/pre-commit/pre-commit/issues/300
# In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
@ -45,6 +43,7 @@ def no_git_env(
k in { k in {
'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO', 'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT', 'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT',
'GIT_HTTP_PROXY_AUTHMETHOD',
} }
} }
@ -58,13 +57,15 @@ def get_root() -> str:
root = os.path.abspath( root = os.path.abspath(
cmd_output('git', 'rev-parse', '--show-cdup')[1].strip(), 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: except CalledProcessError:
raise FatalError( raise FatalError(
'git failed. Is it installed, and are you in a Git repository ' 'git failed. Is it installed, and are you in a Git repository '
'directory?', 'directory?',
) )
if os.path.samefile(root, git_dir): if inside_git_dir != 'false':
raise FatalError( raise FatalError(
'git toplevel unexpectedly empty! make sure you are not ' 'git toplevel unexpectedly empty! make sure you are not '
'inside the `.git` directory of your repository.', 'inside the `.git` directory of your repository.',
@ -73,15 +74,25 @@ def get_root() -> str:
def get_git_dir(git_root: str = '.') -> str: def get_git_dir(git_root: str = '.') -> str:
opts = ('--git-common-dir', '--git-dir') opt = '--git-dir'
_, out, _ = cmd_output('git', 'rev-parse', *opts, cwd=git_root) _, out, _ = cmd_output('git', 'rev-parse', opt, cwd=git_root)
for line, opt in zip(out.splitlines(), opts): git_dir = out.strip()
if line != opt: # pragma: no branch (git < 2.5) if git_dir != opt:
return os.path.normpath(os.path.join(git_root, line)) return os.path.normpath(os.path.join(git_root, git_dir))
else: else:
raise AssertionError('unreachable: no git dir') 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: def get_remote_url(git_root: str) -> str:
_, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root) _, out, _ = cmd_output('git', 'config', 'remote.origin.url', cwd=git_root)
return out.strip() 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 # Conflicted files start with tabs
return [ return [
line.lstrip(b'#').strip().decode() 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.') logger.info('Checking merge-conflict files only.')
# Need to get the conflicted files from the MERGE_MSG because they could # Need to get the conflicted files from the MERGE_MSG because they could
# have resolved the conflict by choosing one side or the other # 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) 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( return zsplit(
cmd_output( cmd_output(
'git', 'diff', '--staged', '--name-only', '--no-ext-diff', '-z', '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( _, stdout, _ = cmd_output(
'git', 'status', '--ignore-submodules', '--porcelain', '-z', 'git', 'status', '--ignore-submodules', '--porcelain', '-z',
) )
@ -153,11 +164,11 @@ def intent_to_add_files() -> List[str]:
return intent_to_add 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]) 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') diff_cmd = ('git', 'diff', '--name-only', '--no-ext-diff', '-z')
try: try:
_, out, _ = cmd_output(*diff_cmd, f'{old}...{new}') _, 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' - python {exe_type[is_cygwin_python]}\n'
f' - git {exe_type[is_cygwin_git]}\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

View file

@ -1,10 +1,10 @@
from __future__ import annotations
import logging import logging
import shlex import shlex
from typing import Any from typing import Any
from typing import Dict
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
@ -38,11 +38,11 @@ class Hook(NamedTuple):
verbose: bool verbose: bool
@property @property
def cmd(self) -> Tuple[str, ...]: def cmd(self) -> tuple[str, ...]:
return (*shlex.split(self.entry), *self.args) return (*shlex.split(self.entry), *self.args)
@property @property
def install_key(self) -> Tuple[Prefix, str, str, Tuple[str, ...]]: def install_key(self) -> tuple[Prefix, str, str, tuple[str, ...]]:
return ( return (
self.prefix, self.prefix,
self.language, self.language,
@ -51,7 +51,7 @@ class Hook(NamedTuple):
) )
@classmethod @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 (?) # TODO: have cfgv do this (?)
extra_keys = set(dct) - _KEYS extra_keys = set(dct) - _KEYS
if extra_keys: if extra_keys:

View file

@ -1,8 +1,8 @@
from __future__ import annotations
from typing import Callable from typing import Callable
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import conda from pre_commit.languages import conda
@ -30,7 +30,7 @@ from pre_commit.prefix import Prefix
class Language(NamedTuple): class Language(NamedTuple):
name: str name: str
# Use `None` for no installation / environment # Use `None` for no installation / environment
ENVIRONMENT_DIR: Optional[str] ENVIRONMENT_DIR: str | None
# return a value to replace `'default` for `language_version` # return a value to replace `'default` for `language_version`
get_default_version: Callable[[], str] get_default_version: Callable[[], str]
# return whether the environment is healthy (or should be rebuilt) # 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 a repository for the given language and language_version
install_environment: Callable[[Prefix, str, Sequence[str]], None] install_environment: Callable[[Prefix, str, Sequence[str]], None]
# execute a hook and return the exit code and output # 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 # TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018

View file

@ -1,8 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import os import os
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
@ -86,7 +87,7 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
# TODO: Some rare commands need to be run using `conda run` but mostly we # 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 # can run them without which is much quicker and produces a better
# output. # output.

View file

@ -1,14 +1,16 @@
from __future__ import annotations
import contextlib import contextlib
import os import os
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var from pre_commit.envcontext import Var
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure from pre_commit.util import clean_path_on_failure
@ -26,6 +28,14 @@ def install_environment(
helpers.assert_version_default('coursier', version) helpers.assert_version_default('coursier', version)
helpers.assert_no_additional_deps('coursier', additional_dependencies) 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)) envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
channel = prefix.path('.pre-commit-channel') channel = prefix.path('.pre-commit-channel')
with clean_path_on_failure(envdir): with clean_path_on_failure(envdir):
@ -35,7 +45,7 @@ def install_environment(
helpers.run_setup_cmd( helpers.run_setup_cmd(
prefix, prefix,
( (
'cs', executable,
'install', 'install',
'--default-channels=false', '--default-channels=false',
f'--channel={channel}', f'--channel={channel}',
@ -66,6 +76,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: # pragma: win32 no cover ) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,10 +1,11 @@
from __future__ import annotations
import contextlib import contextlib
import os.path import os.path
import shutil import shutil
import tempfile import tempfile
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -76,7 +77,7 @@ def install_environment(
with tempfile.TemporaryDirectory() as dep_tmp: with tempfile.TemporaryDirectory() as dep_tmp:
dep, _, version = dep_s.partition(':') dep, _, version = dep_s.partition(':')
if version: if version:
dep_cmd: Tuple[str, ...] = (dep, '--version', version) dep_cmd: tuple[str, ...] = (dep, '--version', version)
else: else:
dep_cmd = (dep,) dep_cmd = (dep,)
@ -104,6 +105,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,8 +1,9 @@
from __future__ import annotations
import hashlib import hashlib
import json import json
import os import os
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.hook import Hook from pre_commit.hook import Hook
@ -76,7 +77,7 @@ def build_docker_image(
*, *,
pull: bool, pull: bool,
) -> None: # pragma: win32 no cover ) -> None: # pragma: win32 no cover
cmd: Tuple[str, ...] = ( cmd: tuple[str, ...] = (
'docker', 'build', 'docker', 'build',
'--tag', docker_tag(prefix), '--tag', docker_tag(prefix),
'--label', PRE_COMMIT_LABEL, '--label', PRE_COMMIT_LABEL,
@ -105,14 +106,14 @@ def install_environment(
os.mkdir(directory) os.mkdir(directory)
def get_docker_user() -> Tuple[str, ...]: # pragma: win32 no cover def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover
try: try:
return ('-u', f'{os.getuid()}:{os.getgid()}') return ('-u', f'{os.getuid()}:{os.getgid()}')
except AttributeError: except AttributeError:
return () return ()
def docker_cmd() -> Tuple[str, ...]: # pragma: win32 no cover def docker_cmd() -> tuple[str, ...]: # pragma: win32 no cover
return ( return (
'docker', 'run', 'docker', 'run',
'--rm', '--rm',
@ -129,7 +130,7 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, 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 # Rebuild the docker image in case it has gone missing, as many people do
# automated cleanup of docker images. # automated cleanup of docker images.
build_docker_image(hook.prefix, pull=False) build_docker_image(hook.prefix, pull=False)

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
@ -15,6 +16,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: # pragma: win32 no cover ) -> tuple[int, bytes]: # pragma: win32 no cover
cmd = docker_cmd() + hook.cmd cmd = docker_cmd() + hook.cmd
return helpers.run_xargs(hook, cmd, file_args, color=color) return helpers.run_xargs(hook, cmd, file_args, color=color)

View file

@ -1,8 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import os.path import os.path
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -84,6 +85,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
@ -14,7 +15,7 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
out = f'{hook.entry}\n\n'.encode() out = f'{hook.entry}\n\n'.encode()
out += b'\n'.join(f.encode() for f in file_args) + b'\n' out += b'\n'.join(f.encode() for f in file_args) + b'\n'
return 1, out return 1, out

View file

@ -1,9 +1,10 @@
from __future__ import annotations
import contextlib import contextlib
import os.path import os.path
import sys import sys
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import git from pre_commit import git
@ -95,6 +96,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,13 +1,12 @@
from __future__ import annotations
import multiprocessing import multiprocessing
import os import os
import random import random
import re import re
from typing import Any from typing import Any
from typing import List
from typing import Optional
from typing import overload from typing import overload
from typing import Sequence from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pre_commit.constants as C import pre_commit.constants as C
@ -32,7 +31,7 @@ def exe_exists(exe: str) -> bool:
homedir = os.path.expanduser('~') homedir = os.path.expanduser('~')
try: 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 except ValueError: # on windows, different drives raises ValueError
common = None 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) 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: 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: if d is None:
return None return None
else: 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: def assert_version_default(binary: str, version: str) -> None:
if version != C.DEFAULT: if version != C.DEFAULT:
raise AssertionError( 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: ) -> None:
if additional_deps: if additional_deps:
raise AssertionError( raise AssertionError(
f'For now, pre-commit does not support ' f'for now, pre-commit does not support '
f'additional_dependencies for {lang}', f'additional_dependencies for {lang} -- '
f'you selected `additional_dependencies: {additional_deps}`',
) )
@ -95,7 +96,7 @@ def no_install(
prefix: Prefix, prefix: Prefix,
version: str, version: str,
additional_dependencies: Sequence[str], additional_dependencies: Sequence[str],
) -> 'NoReturn': ) -> NoReturn:
raise AssertionError('This type is not installable') raise AssertionError('This type is not installable')
@ -113,7 +114,7 @@ def target_concurrency(hook: Hook) -> int:
return 1 return 1
def _shuffled(seq: Sequence[str]) -> List[str]: def _shuffled(seq: Sequence[str]) -> list[str]:
"""Deterministically shuffle""" """Deterministically shuffle"""
fixed_random = random.Random() fixed_random = random.Random()
fixed_random.seed(FIXED_RANDOM_SEED, version=1) fixed_random.seed(FIXED_RANDOM_SEED, version=1)
@ -125,10 +126,10 @@ def _shuffled(seq: Sequence[str]) -> List[str]:
def run_xargs( def run_xargs(
hook: Hook, hook: Hook,
cmd: Tuple[str, ...], cmd: tuple[str, ...],
file_args: Sequence[str], file_args: Sequence[str],
**kwargs: Any, **kwargs: Any,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
# Shuffle the files so that they more evenly fill out the xargs partitions, # Shuffle the files so that they more evenly fill out the xargs partitions,
# but do it deterministically in case a hook cares about ordering. # but do it deterministically in case a hook cares about ordering.
file_args = _shuffled(file_args) file_args = _shuffled(file_args)

View file

@ -1,9 +1,10 @@
from __future__ import annotations
import contextlib import contextlib
import os import os
import sys import sys
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -85,6 +86,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: # pragma: win32 no cover ) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,10 +1,11 @@
from __future__ import annotations
import contextlib import contextlib
import functools import functools
import os import os
import sys import sys
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -122,6 +123,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version): with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,9 +1,10 @@
from __future__ import annotations
import contextlib import contextlib
import os import os
import shlex import shlex
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
@ -62,6 +63,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version): with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,11 +1,11 @@
from __future__ import annotations
import argparse import argparse
import re import re
import sys import sys
from typing import NamedTuple from typing import NamedTuple
from typing import Optional
from typing import Pattern from typing import Pattern
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit import output from pre_commit import output
from pre_commit.hook import Hook from pre_commit.hook import Hook
@ -90,12 +90,12 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
exe = (sys.executable, '-m', __name__) + tuple(hook.args) + (hook.entry,) exe = (sys.executable, '-m', __name__) + tuple(hook.args) + (hook.entry,)
return xargs(exe, file_args, color=color) 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( parser = argparse.ArgumentParser(
description=( description=(
'grep-like finder using python regexes. Unlike grep, this tool ' 'grep-like finder using python regexes. Unlike grep, this tool '

View file

@ -1,12 +1,11 @@
from __future__ import annotations
import contextlib import contextlib
import functools import functools
import os import os
import sys import sys
from typing import Dict
from typing import Generator from typing import Generator
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -35,7 +34,7 @@ def _version_info(exe: str) -> str:
return f'<<error retrieving version from {exe}>>' 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 = {} ret = {}
with open(filename, encoding='UTF-8') as f: with open(filename, encoding='UTF-8') as f:
for line in f: for line in f:
@ -65,7 +64,7 @@ def get_env_patch(venv: str) -> PatchesT:
def _find_by_py_launcher( def _find_by_py_launcher(
version: str, version: str,
) -> Optional[str]: # pragma: no cover (windows only) ) -> str | None: # pragma: no cover (windows only)
if version.startswith('python'): if version.startswith('python'):
num = version[len('python'):] num = version[len('python'):]
cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)') cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)')
@ -77,8 +76,8 @@ def _find_by_py_launcher(
return None return None
def _find_by_sys_executable() -> Optional[str]: def _find_by_sys_executable() -> str | None:
def _norm(path: str) -> Optional[str]: def _norm(path: str) -> str | None:
_, exe = os.path.split(path.lower()) _, exe = os.path.split(path.lower())
exe, _, _ = exe.partition('.exe') exe, _, _ = exe.partition('.exe')
if exe not in {'python', 'pythonw'} and find_executable(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 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 if version == C.DEFAULT: # use virtualenv's default
return None return None
elif _sys_executable_matches(version): # virtualenv defaults to our exe elif _sys_executable_matches(version): # virtualenv defaults to our exe
@ -209,6 +208,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version): with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,10 +1,11 @@
from __future__ import annotations
import contextlib import contextlib
import os import os
import shlex import shlex
import shutil import shutil
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT from pre_commit.envcontext import PatchesT
@ -58,7 +59,11 @@ def _prefix_if_non_local_file_entry(
def _rscript_exec() -> str: 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: 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 = shlex.split(hook.entry)
_entry_validate(entry) _entry_validate(entry)
@ -102,9 +107,7 @@ def install_environment(
shutil.copy(prefix.path('renv.lock'), env_dir) shutil.copy(prefix.path('renv.lock'), env_dir)
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv')) shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
cmd_output_b( r_code_inst_environment = f"""\
_rscript_exec(), '--vanilla', '-e',
f"""\
prefix_dir <- {prefix.prefix_dir!r} prefix_dir <- {prefix.prefix_dir!r}
options( options(
repos = c(CRAN = "https://cran.rstudio.com"), repos = c(CRAN = "https://cran.rstudio.com"),
@ -131,24 +134,41 @@ def install_environment(
if (is_package) {{ if (is_package) {{
renv::install(prefix_dir) renv::install(prefix_dir)
}} }}
""", """
cmd_output_b(
_rscript_exec(), '--vanilla', '-e',
_inline_r_setup(r_code_inst_environment),
cwd=env_dir, cwd=env_dir,
) )
if additional_dependencies: if additional_dependencies:
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
with in_env(prefix, version): with in_env(prefix, version):
cmd_output_b( cmd_output_b(
_rscript_exec(), *RSCRIPT_OPTS, '-e', _rscript_exec(), *RSCRIPT_OPTS, '-e',
'renv::install(commandArgs(trailingOnly = TRUE))', _inline_r_setup(r_code_inst_add),
*additional_dependencies, *additional_dependencies,
cwd=env_dir, 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( def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version): with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs( return helpers.run_xargs(
hook, _cmd_from_hook(hook), file_args, color=color, hook, _cmd_from_hook(hook), file_args, color=color,

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import functools import functools
import os.path import os.path
@ -5,7 +7,6 @@ import shutil
import tarfile import tarfile
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -146,6 +147,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version): with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,9 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import os.path import os.path
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Set
from typing import Tuple
import toml import toml
@ -39,7 +39,7 @@ def in_env(prefix: Prefix) -> Generator[None, None, None]:
def _add_dependencies( def _add_dependencies(
cargo_toml_path: str, cargo_toml_path: str,
additional_dependencies: Set[str], additional_dependencies: set[str],
) -> None: ) -> None:
with open(cargo_toml_path, 'r+') as f: with open(cargo_toml_path, 'r+') as f:
cargo_toml = toml.load(f) cargo_toml = toml.load(f)
@ -81,7 +81,7 @@ def install_environment(
_add_dependencies(prefix.path('Cargo.toml'), lib_deps) _add_dependencies(prefix.path('Cargo.toml'), lib_deps)
with clean_path_on_failure(directory): 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: for cli_dep in cli_deps:
cli_dep = cli_dep[len('cli:'):] cli_dep = cli_dep[len('cli:'):]
package, _, version = cli_dep.partition(':') package, _, version = cli_dep.partition(':')
@ -101,6 +101,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
@ -14,6 +15,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:]) cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:])
return helpers.run_xargs(hook, cmd, file_args, color=color) return helpers.run_xargs(hook, cmd, file_args, color=color)

View file

@ -1,8 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import os import os
from typing import Generator from typing import Generator
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
@ -59,6 +60,6 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: # pragma: win32 no cover ) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix): with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Sequence from typing import Sequence
from typing import Tuple
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
@ -15,5 +16,5 @@ def run_hook(
hook: Hook, hook: Hook,
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
return helpers.run_xargs(hook, hook.cmd, file_args, color=color) return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import logging import logging
from typing import Generator from typing import Generator

View file

@ -1,11 +1,10 @@
from __future__ import annotations
import argparse import argparse
import logging import logging
import os import os
import sys import sys
from typing import Any
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Union
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import git 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: def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
'-t', '--hook-type', choices=( '-t', '--hook-type',
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg', choices=C.HOOK_TYPES, action='append', dest='hook_types',
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
'post-rewrite',
),
action=AppendReplaceDefault,
default=['pre-commit'],
dest='hook_types',
) )
@ -106,7 +81,7 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
'--from-ref', '--source', '-s', '--from-ref', '--source', '-s',
help=( 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. ' 'original ref in a `from_ref...to_ref` diff expression. '
'For `pre-push` hooks, this represents the branch you are pushing ' 'For `pre-push` hooks, this represents the branch you are pushing '
'to. ' 'to. '
@ -117,7 +92,7 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
'--to-ref', '--origin', '-o', '--to-ref', '--origin', '-o',
help=( 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. ' 'destination ref in a `from_ref...to_ref` diff expression. '
'For `pre-push` hooks, this represents the branch being pushed. ' 'For `pre-push` hooks, this represents the branch being pushed. '
'For `post-checkout` hooks, this represents the branch that is ' '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) 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:] argv = argv if argv is not None else sys.argv[1:]
parser = argparse.ArgumentParser(prog='pre-commit') parser = argparse.ArgumentParser(prog='pre-commit')
@ -197,7 +172,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
autoupdate_parser.add_argument( autoupdate_parser.add_argument(
'--bleeding-edge', action='store_true', '--bleeding-edge', action='store_true',
help=( 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).' 'tagged version (the default behavior).'
), ),
) )
@ -399,7 +374,10 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
elif args.command == 'try-repo': elif args.command == 'try-repo':
return try_repo(args) return try_repo(args)
elif args.command == 'uninstall': elif args.command == 'uninstall':
return uninstall(hook_types=args.hook_types) return uninstall(
config_file=args.config,
hook_types=args.hook_types,
)
else: else:
raise NotImplementedError( raise NotImplementedError(
f'Command {args.command} not implemented.', f'Command {args.command} not implemented.',

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import argparse import argparse
from typing import Optional
from typing import Sequence from typing import Sequence
import pre_commit.constants as C import pre_commit.constants as C
@ -27,7 +28,7 @@ def check_all_hooks_match_files(config_file: str) -> int:
return retv return retv
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE]) parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE])
args = parser.parse_args(argv) args = parser.parse_args(argv)

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import argparse import argparse
import re import re
from typing import Optional
from typing import Sequence from typing import Sequence
from cfgv import apply_defaults from cfgv import apply_defaults
@ -65,7 +66,7 @@ def check_useless_excludes(config_file: str) -> int:
return retv return retv
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE]) parser.add_argument('filenames', nargs='*', default=[C.CONFIG_FILE])
args = parser.parse_args(argv) args = parser.parse_args(argv)

View file

@ -1,11 +1,12 @@
from __future__ import annotations
import sys import sys
from typing import Optional
from typing import Sequence from typing import Sequence
from pre_commit import output 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:] argv = argv if argv is not None else sys.argv[1:]
for arg in argv: for arg in argv:
output.write_line(arg) output.write_line(arg)

View file

@ -1,8 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import sys import sys
from typing import Any from typing import Any
from typing import IO from typing import IO
from typing import Optional
def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None: 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( def write_line_b(
s: Optional[bytes] = None, s: bytes | None = None,
stream: IO[bytes] = sys.stdout.buffer, stream: IO[bytes] = sys.stdout.buffer,
logfile_name: Optional[str] = None, logfile_name: str | None = None,
) -> None: ) -> None:
with contextlib.ExitStack() as exit_stack: with contextlib.ExitStack() as exit_stack:
output_streams = [stream] output_streams = [stream]
@ -28,5 +29,5 @@ def write_line_b(
output_stream.flush() 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) write_line_b(s.encode() if s is not None else s, **kwargs)

View file

@ -1,7 +1,7 @@
from __future__ import annotations
import os.path import os.path
from typing import Mapping from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from identify.identify import parse_shebang_from_file from identify.identify import parse_shebang_from_file
@ -11,11 +11,11 @@ if TYPE_CHECKING:
class ExecutableNotFoundError(OSError): 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) 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): if not os.path.exists(filename):
return () return ()
else: else:
@ -23,8 +23,8 @@ def parse_filename(filename: str) -> Tuple[str, ...]:
def find_executable( def find_executable(
exe: str, _environ: Optional[Mapping[str, str]] = None, exe: str, _environ: Mapping[str, str] | None = None,
) -> Optional[str]: ) -> str | None:
exe = os.path.normpath(exe) exe = os.path.normpath(exe)
if os.sep in exe: if os.sep in exe:
return exe return exe
@ -47,7 +47,7 @@ def find_executable(
def normexe(orig: str) -> str: def normexe(orig: str) -> str:
def _error(msg: str) -> 'NoReturn': def _error(msg: str) -> NoReturn:
raise ExecutableNotFoundError(f'Executable `{orig}` {msg}') raise ExecutableNotFoundError(f'Executable `{orig}` {msg}')
if os.sep not in orig and (not os.altsep or os.altsep not in orig): 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 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 """Fixes for the following issues on windows
- https://bugs.python.org/issue8557 - https://bugs.python.org/issue8557
- windows does not parse shebangs - windows does not parse shebangs

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import os.path import os.path
from typing import NamedTuple from typing import NamedTuple
from typing import Tuple
class Prefix(NamedTuple): class Prefix(NamedTuple):
@ -12,6 +13,6 @@ class Prefix(NamedTuple):
def exists(self, *parts: str) -> bool: def exists(self, *parts: str) -> bool:
return os.path.exists(self.path(*parts)) 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) paths = os.listdir(self.prefix_dir)
return tuple(path for path in paths if path.endswith(end)) return tuple(path for path in paths if path.endswith(end))

View file

@ -1,13 +1,10 @@
from __future__ import annotations
import json import json
import logging import logging
import os import os
from typing import Any from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Set
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.clientlib import load_manifest 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}') 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) filename = _state_filename(prefix, venv)
if not os.path.exists(filename): if not os.path.exists(filename):
return None return None
@ -93,9 +90,9 @@ def _hook_install(hook: Hook) -> None:
def _hook( def _hook(
*hook_dicts: Dict[str, Any], *hook_dicts: dict[str, Any],
root_config: Dict[str, Any], root_config: dict[str, Any],
) -> Dict[str, Any]: ) -> dict[str, Any]:
ret, rest = dict(hook_dicts[0]), hook_dicts[1:] ret, rest = dict(hook_dicts[0]), hook_dicts[1:]
for dct in rest: for dct in rest:
ret.update(dct) ret.update(dct)
@ -140,10 +137,10 @@ def _hook(
def _non_cloned_repository_hooks( def _non_cloned_repository_hooks(
repo_config: Dict[str, Any], repo_config: dict[str, Any],
store: Store, store: Store,
root_config: Dict[str, Any], root_config: dict[str, Any],
) -> Tuple[Hook, ...]: ) -> tuple[Hook, ...]:
def _prefix(language_name: str, deps: Sequence[str]) -> Prefix: def _prefix(language_name: str, deps: Sequence[str]) -> Prefix:
language = languages[language_name] language = languages[language_name]
# pygrep / script / system / docker_image do not have # pygrep / script / system / docker_image do not have
@ -164,10 +161,10 @@ def _non_cloned_repository_hooks(
def _cloned_repository_hooks( def _cloned_repository_hooks(
repo_config: Dict[str, Any], repo_config: dict[str, Any],
store: Store, store: Store,
root_config: Dict[str, Any], root_config: dict[str, Any],
) -> Tuple[Hook, ...]: ) -> tuple[Hook, ...]:
repo, rev = repo_config['repo'], repo_config['rev'] repo, rev = repo_config['repo'], repo_config['rev']
manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE) manifest_path = os.path.join(store.clone(repo, rev), C.MANIFEST_FILE)
by_id = {hook['id']: hook for hook in load_manifest(manifest_path)} by_id = {hook['id']: hook for hook in load_manifest(manifest_path)}
@ -196,10 +193,10 @@ def _cloned_repository_hooks(
def _repository_hooks( def _repository_hooks(
repo_config: Dict[str, Any], repo_config: dict[str, Any],
store: Store, store: Store,
root_config: Dict[str, Any], root_config: dict[str, Any],
) -> Tuple[Hook, ...]: ) -> tuple[Hook, ...]:
if repo_config['repo'] in {LOCAL, META}: if repo_config['repo'] in {LOCAL, META}:
return _non_cloned_repository_hooks(repo_config, store, root_config) return _non_cloned_repository_hooks(repo_config, store, root_config)
else: else:
@ -207,8 +204,8 @@ def _repository_hooks(
def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None: def install_hook_envs(hooks: Sequence[Hook], store: Store) -> None:
def _need_installed() -> List[Hook]: def _need_installed() -> list[Hook]:
seen: Set[Tuple[Prefix, str, str, Tuple[str, ...]]] = set() seen: set[tuple[Prefix, str, str, tuple[str, ...]]] = set()
ret = [] ret = []
for hook in hooks: for hook in hooks:
if hook.install_key not in seen and not _hook_installed(hook): 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) _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( return tuple(
hook hook
for repo in root_config['repos'] for repo in root_config['repos']

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import logging import logging
import os.path import os.path
@ -64,9 +66,9 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
# prevent recursive post-checkout hooks (#1418) # prevent recursive post-checkout hooks (#1418)
no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1') no_checkout_env = dict(os.environ, _PRE_COMMIT_SKIP_POST_CHECKOUT='1')
cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
try: try:
cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
yield yield
finally: finally:
# Try to apply the patch we saved # Try to apply the patch we saved

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import logging import logging
import os.path import os.path
@ -5,10 +7,7 @@ import sqlite3
import tempfile import tempfile
from typing import Callable from typing import Callable
from typing import Generator from typing import Generator
from typing import List
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import file_lock from pre_commit import file_lock
@ -40,7 +39,7 @@ def _get_default_directory() -> str:
class Store: class Store:
get_default_directory = staticmethod(_get_default_directory) 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.directory = directory or Store.get_default_directory()
self.db_path = os.path.join(self.directory, 'db.db') self.db_path = os.path.join(self.directory, 'db.db')
self.readonly = ( self.readonly = (
@ -92,7 +91,7 @@ class Store:
@contextlib.contextmanager @contextlib.contextmanager
def connect( def connect(
self, self,
db_path: Optional[str] = None, db_path: str | None = None,
) -> Generator[sqlite3.Connection, None, None]: ) -> Generator[sqlite3.Connection, None, None]:
db_path = db_path or self.db_path db_path = db_path or self.db_path
# sqlite doesn't close its fd with its contextmanager >.< # sqlite doesn't close its fd with its contextmanager >.<
@ -119,7 +118,7 @@ class Store:
) -> str: ) -> str:
repo = self.db_repo_name(repo, deps) repo = self.db_repo_name(repo, deps)
def _get_result() -> Optional[str]: def _get_result() -> str | None:
# Check if we already exist # Check if we already exist
with self.connect() as db: with self.connect() as db:
result = db.execute( result = db.execute(
@ -239,18 +238,18 @@ class Store:
self._create_config_table(db) self._create_config_table(db)
db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,)) 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: with self.connect() as db:
self._create_config_table(db) self._create_config_table(db)
rows = db.execute('SELECT path FROM configs').fetchall() rows = db.execute('SELECT path FROM configs').fetchall()
return [path for path, in rows] 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: with self.connect() as db:
rows = [(path,) for path in configs] rows = [(path,) for path in configs]
db.executemany('DELETE FROM configs WHERE path = ?', rows) 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: with self.connect() as db:
return db.execute('SELECT repo, ref, path from repos').fetchall() return db.execute('SELECT repo, ref, path from repos').fetchall()

View file

@ -1,6 +1,9 @@
from __future__ import annotations
import contextlib import contextlib
import errno import errno
import functools import functools
import importlib.resources
import os.path import os.path
import shutil import shutil
import stat import stat
@ -10,24 +13,13 @@ import tempfile
from types import TracebackType from types import TracebackType
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import Dict
from typing import Generator from typing import Generator
from typing import IO from typing import IO
from typing import Optional
from typing import Tuple
from typing import Type
import yaml import yaml
from pre_commit import parse_shebang 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) Loader = getattr(yaml, 'CSafeLoader', yaml.SafeLoader)
yaml_load = functools.partial(yaml.load, Loader=Loader) yaml_load = functools.partial(yaml.load, Loader=Loader)
Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper)
@ -73,11 +65,11 @@ def tmpdir() -> Generator[str, None, None]:
def resource_bytesio(filename: str) -> IO[bytes]: 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: 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: def make_executable(filename: str) -> None:
@ -90,10 +82,10 @@ class CalledProcessError(RuntimeError):
def __init__( def __init__(
self, self,
returncode: int, returncode: int,
cmd: Tuple[str, ...], cmd: tuple[str, ...],
expected_returncode: int, expected_returncode: int,
stdout: bytes, stdout: bytes,
stderr: Optional[bytes], stderr: bytes | None,
) -> None: ) -> None:
super().__init__(returncode, cmd, expected_returncode, stdout, stderr) super().__init__(returncode, cmd, expected_returncode, stdout, stderr)
self.returncode = returncode self.returncode = returncode
@ -103,7 +95,7 @@ class CalledProcessError(RuntimeError):
self.stderr = stderr self.stderr = stderr
def __bytes__(self) -> bytes: def __bytes__(self) -> bytes:
def _indent_or_none(part: Optional[bytes]) -> bytes: def _indent_or_none(part: bytes | None) -> bytes:
if part: if part:
return b'\n ' + part.replace(b'\n', b'\n ') return b'\n ' + part.replace(b'\n', b'\n ')
else: else:
@ -121,20 +113,20 @@ class CalledProcessError(RuntimeError):
return self.__bytes__().decode() 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'): for arg in ('stdin', 'stdout', 'stderr'):
kwargs.setdefault(arg, subprocess.PIPE) 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 return 1, force_bytes(e).rstrip(b'\n') + b'\n', None
def cmd_output_b( def cmd_output_b(
*cmd: str, *cmd: str,
retcode: Optional[int] = 0, retcode: int | None = 0,
**kwargs: Any, **kwargs: Any,
) -> Tuple[int, bytes, Optional[bytes]]: ) -> tuple[int, bytes, bytes | None]:
_setdefault_kwargs(kwargs) _setdefault_kwargs(kwargs)
try: try:
@ -156,7 +148,7 @@ def cmd_output_b(
return returncode, stdout_b, stderr_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) returncode, stdout_b, stderr_b = cmd_output_b(*cmd, **kwargs)
stdout = stdout_b.decode() if stdout_b is not None else None stdout = stdout_b.decode() if stdout_b is not None else None
stderr = stderr_b.decode() if stderr_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: class Pty:
def __init__(self) -> None: def __init__(self) -> None:
self.r: Optional[int] = None self.r: int | None = None
self.w: Optional[int] = None self.w: int | None = None
def __enter__(self) -> 'Pty': def __enter__(self) -> Pty:
self.r, self.w = openpty() self.r, self.w = openpty()
# tty flags normally change \n to \r\n # tty flags normally change \n to \r\n
@ -195,18 +187,18 @@ if os.name != 'nt': # pragma: win32 no cover
def __exit__( def __exit__(
self, self,
exc_type: Optional[Type[BaseException]], exc_type: type[BaseException] | None,
exc_value: Optional[BaseException], exc_value: BaseException | None,
traceback: Optional[TracebackType], traceback: TracebackType | None,
) -> None: ) -> None:
self.close_w() self.close_w()
self.close_r() self.close_r()
def cmd_output_p( def cmd_output_p(
*cmd: str, *cmd: str,
retcode: Optional[int] = 0, retcode: int | None = 0,
**kwargs: Any, **kwargs: Any,
) -> Tuple[int, bytes, Optional[bytes]]: ) -> tuple[int, bytes, bytes | None]:
assert retcode is None assert retcode is None
assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr'] assert kwargs['stderr'] == subprocess.STDOUT, kwargs['stderr']
_setdefault_kwargs(kwargs) _setdefault_kwargs(kwargs)
@ -250,7 +242,7 @@ def rmtree(path: str) -> None:
def handle_remove_readonly( def handle_remove_readonly(
func: Callable[..., Any], func: Callable[..., Any],
path: str, path: str,
exc: Tuple[Type[OSError], OSError, TracebackType], exc: tuple[type[OSError], OSError, TracebackType],
) -> None: ) -> None:
excvalue = exc[1] excvalue = exc[1]
if ( if (
@ -265,7 +257,7 @@ def rmtree(path: str) -> None:
shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly) 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""" """poor man's version comparison"""
return tuple(int(p) for p in s.split('.')) return tuple(int(p) for p in s.split('.'))

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import concurrent.futures import concurrent.futures
import contextlib import contextlib
import math import math
@ -8,11 +10,8 @@ from typing import Any
from typing import Callable from typing import Callable
from typing import Generator from typing import Generator
from typing import Iterable from typing import Iterable
from typing import List
from typing import MutableMapping from typing import MutableMapping
from typing import Optional
from typing import Sequence from typing import Sequence
from typing import Tuple
from typing import TypeVar from typing import TypeVar
from pre_commit import parse_shebang from pre_commit import parse_shebang
@ -23,7 +22,7 @@ TArg = TypeVar('TArg')
TRet = TypeVar('TRet') 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) environ = _env if _env is not None else getattr(os, 'environb', os.environ)
size = 8 * len(environ) # number of pointers in `envp` size = 8 * len(environ) # number of pointers in `envp`
for k, v in environ.items(): for k, v in environ.items():
@ -62,8 +61,8 @@ def partition(
cmd: Sequence[str], cmd: Sequence[str],
varargs: Sequence[str], varargs: Sequence[str],
target_concurrency: int, target_concurrency: int,
_max_length: Optional[int] = None, _max_length: int | None = None,
) -> Tuple[Tuple[str, ...], ...]: ) -> tuple[tuple[str, ...], ...]:
_max_length = _max_length or _get_platform_max_length() _max_length = _max_length or _get_platform_max_length()
# Generally, we try to partition evenly into at least `target_concurrency` # Generally, we try to partition evenly into at least `target_concurrency`
@ -73,7 +72,7 @@ def partition(
cmd = tuple(cmd) cmd = tuple(cmd)
ret = [] ret = []
ret_cmd: List[str] = [] ret_cmd: list[str] = []
# Reversed so arguments are in order # Reversed so arguments are in order
varargs = list(reversed(varargs)) varargs = list(reversed(varargs))
@ -115,14 +114,14 @@ def _thread_mapper(maxsize: int) -> Generator[
def xargs( def xargs(
cmd: Tuple[str, ...], cmd: tuple[str, ...],
varargs: Sequence[str], varargs: Sequence[str],
*, *,
color: bool = False, color: bool = False,
target_concurrency: int = 1, target_concurrency: int = 1,
_max_length: int = _get_platform_max_length(), _max_length: int = _get_platform_max_length(),
**kwargs: Any, **kwargs: Any,
) -> Tuple[int, bytes]: ) -> tuple[int, bytes]:
"""A simplified implementation of xargs. """A simplified implementation of xargs.
color: Make a pty if on a platform that supports it 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) partitions = partition(cmd, varargs, target_concurrency, _max_length)
def run_cmd_partition( def run_cmd_partition(
run_cmd: Tuple[str, ...], run_cmd: tuple[str, ...],
) -> Tuple[int, bytes, Optional[bytes]]: ) -> tuple[int, bytes, bytes | None]:
return cmd_fn( return cmd_fn(
*run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs, *run_cmd, retcode=None, stderr=subprocess.STDOUT, **kwargs,
) )

View file

@ -1,6 +1,6 @@
[metadata] [metadata]
name = pre_commit name = pre_commit
version = 2.17.0 version = 2.18.1
description = A framework for managing and maintaining multi-language pre-commit hooks. description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md long_description = file: README.md
long_description_content_type = text/markdown long_description_content_type = text/markdown
@ -13,7 +13,6 @@ classifiers =
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
@ -31,8 +30,7 @@ install_requires =
toml toml
virtualenv>=20.0.8 virtualenv>=20.0.8
importlib-metadata;python_version<"3.8" importlib-metadata;python_version<"3.8"
importlib-resources<5.3;python_version<"3.7" python_requires = >=3.7
python_requires = >=3.6.1
[options.packages.find] [options.packages.find]
exclude = exclude =

View file

@ -1,2 +1,4 @@
from __future__ import annotations
from setuptools import setup from setuptools import setup
setup() setup()

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import collections import collections

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import os.path import os.path
import shutil import shutil

View file

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import sys import sys
LANGUAGES = [ LANGUAGES = [

View file

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import argparse import argparse
import gzip import gzip
import os.path import os.path
@ -6,7 +8,6 @@ import shutil
import subprocess import subprocess
import tarfile import tarfile
import tempfile import tempfile
from typing import Optional
from typing import Sequence from typing import Sequence
@ -16,7 +17,7 @@ from typing import Sequence
REPOS = ( REPOS = (
('rbenv', 'https://github.com/rbenv/rbenv', '38e1fbb'), ('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', 'ruby-download',
'https://github.com/garnieretienne/rvm-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 return output_path
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--dest', default='pre_commit/resources') parser.add_argument('--dest', default='pre_commit/resources')
args = parser.parse_args(argv) args = parser.parse_args(argv)

View file

@ -2,5 +2,4 @@
name: Python 3 Hook name: Python 3 Hook
entry: python3-hook entry: python3-hook
language: python language: python
language_version: python3
files: \.py$ files: \.py$

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import sys import sys

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from setuptools import setup from setuptools import setup
setup( setup(

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import sys import sys

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from setuptools import setup from setuptools import setup
setup( setup(

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import contextlib import contextlib
import os.path import os.path
import subprocess import subprocess

View file

@ -1,4 +1,4 @@
FROM ubuntu:bionic FROM ubuntu:focal
RUN : \ RUN : \
&& apt-get update \ && apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@ -10,5 +10,5 @@ RUN : \
ENV LANG=C.UTF-8 PATH=/venv/bin:$PATH ENV LANG=C.UTF-8 PATH=/venv/bin:$PATH
RUN : \ RUN : \
&& python3.6 -mvenv /venv \ && python3 -mvenv /venv \
&& pip install --no-cache-dir pip setuptools wheel no-manylinux --upgrade && pip install --no-cache-dir pip setuptools wheel no-manylinux --upgrade

View file

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import os.path import os.path
import shutil import shutil
import stat import stat
@ -59,9 +61,6 @@ def main() -> int:
if sys.platform == 'win32': # https://bugs.python.org/issue19124 if sys.platform == 'win32': # https://bugs.python.org/issue19124
import subprocess 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: else:
os.execvp(cmd[0], cmd) os.execvp(cmd[0], cmd)

View file

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
import argparse import argparse
import base64 import base64
import hashlib import hashlib

View file

@ -1,5 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""A shim executable to put dependencies on sys.path""" """A shim executable to put dependencies on sys.path"""
from __future__ import annotations
import argparse import argparse
import os.path import os.path
import runpy import runpy
@ -36,9 +38,6 @@ def main() -> int:
if sys.platform == 'win32': # https://bugs.python.org/issue19124 if sys.platform == 'win32': # https://bugs.python.org/issue19124
import subprocess 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: else:
os.execvp(cmd[0], cmd) os.execvp(cmd[0], cmd)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import logging import logging
import re import re

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import sys import sys
from unittest import mock from unittest import mock

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import shlex import shlex
from unittest import mock 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' 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): def test_rev_info_update_freeze_tag(tagged):
git_commit(cwd=tagged.path) git_commit(cwd=tagged.path)
config = make_config_from_repo(tagged.path, rev=tagged.original_rev) config = make_config_from_repo(tagged.path, rev=tagged.original_rev)

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
from unittest import mock from unittest import mock

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os import os
import pre_commit.constants as C import pre_commit.constants as C

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import subprocess import subprocess
import sys import sys
from unittest import mock from unittest import mock

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
from unittest import mock from unittest import mock

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import re import re
@ -5,6 +7,7 @@ import re_assert
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import git 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 CURRENT_HASH
from pre_commit.commands.install_uninstall import install from pre_commit.commands.install_uninstall import install
from pre_commit.commands.install_uninstall import install_hooks from pre_commit.commands.install_uninstall import install_hooks
@ -25,6 +28,36 @@ from testing.util import cwd
from testing.util import git_commit 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(): def test_is_not_script():
assert is_our_script('setup.py') is False 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']) 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-commit').exists()
assert in_git_dir.join('.git/hooks/pre-push').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-commit').exists()
assert not in_git_dir.join('.git/hooks/pre-push').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): 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): def test_uninstall(in_git_dir, store):
assert not in_git_dir.join('.git/hooks/pre-commit').exists() assert not in_git_dir.join('.git/hooks/pre-commit').exists()
install(C.CONFIG_FILE, store, hook_types=['pre-commit']) install(C.CONFIG_FILE, store, hook_types=['pre-commit'])
assert in_git_dir.join('.git/hooks/pre-commit').exists() 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() 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 # Now install and uninstall pre-commit
assert install(C.CONFIG_FILE, store, hook_types=['pre-commit']) == 0 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 # Make sure we installed the "old" hook correctly
ret, output = _get_commit_output(tempdir_factory, touch_file='baz') 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') pre_commit.write('#!/usr/bin/env bash\necho 1\n')
make_executable(pre_commit.strpath) 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() assert pre_commit.exists()
@ -1005,3 +1038,16 @@ def test_install_temporarily_allow_mising_config(tempdir_factory, store):
'Skipping `pre-commit`.' 'Skipping `pre-commit`.'
) )
assert expected in output 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()

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit.commands.migrate_config import migrate_config from pre_commit.commands.migrate_config import migrate_config

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import shlex import shlex
import sys import sys

View file

@ -1,3 +1,5 @@
from __future__ import annotations
from pre_commit.commands.sample_config import sample_config from pre_commit.commands.sample_config import sample_config

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import re import re
import time import time

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import functools import functools
import io import io
import logging import logging

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os import os
from unittest import mock from unittest import mock

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import stat import stat
import sys import sys

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import pytest import pytest
@ -19,6 +21,20 @@ def test_get_root_deeper(in_git_dir):
assert os.path.normcase(git.get_root()) == expected 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): def test_in_exactly_dot_git(in_git_dir):
with in_git_dir.join('.git').as_cwd(), pytest.raises(FatalError): with in_git_dir.join('.git').as_cwd(), pytest.raises(FatalError):
git.get_root() git.get_root()
@ -38,6 +54,22 @@ def test_get_root_bare_worktree(tmpdir):
assert git.get_root() == os.path.abspath('.') 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): def test_get_root_worktree_in_git(tmpdir):
src = tmpdir.join('src').ensure_dir() src = tmpdir.join('src').ensure_dir()
cmd_output('git', 'init', str(src)) cmd_output('git', 'init', str(src))

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from pre_commit import envcontext from pre_commit import envcontext

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import builtins import builtins
import json import json
import ntpath import ntpath

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from pre_commit.languages.golang import guess_go_dir from pre_commit.languages.golang import guess_go_dir

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import multiprocessing import multiprocessing
import os.path import os.path
import sys import sys
@ -86,7 +88,9 @@ def test_assert_no_additional_deps():
helpers.assert_no_additional_deps('lang', ['hmmm']) helpers.assert_no_additional_deps('lang', ['hmmm'])
msg, = excinfo.value.args msg, = excinfo.value.args
assert msg == ( assert msg == (
'For now, pre-commit does not support additional_dependencies for lang' 'for now, pre-commit does not support additional_dependencies for '
'lang -- '
"you selected `additional_dependencies: ['hmmm']`"
) )

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import json import json
import os import os
import shutil import shutil

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from pre_commit.languages import pygrep from pre_commit.languages import pygrep

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path import os.path
import sys import sys
from unittest import mock from unittest import mock
@ -47,16 +49,16 @@ def test_norm_version_of_default_is_sys_executable():
assert python.norm_version('default') is None 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): 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._sys_executable_matches(v)
assert python.norm_version(v) is None assert python.norm_version(v) is None
@pytest.mark.parametrize('v', ('notpython', 'python3.x')) @pytest.mark.parametrize('v', ('notpython', 'python3.x'))
def test_sys_executable_matches_does_not_match(v): 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) 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/python3', '/usr/bin/python3.7', 'python3'),
('/usr/bin/python', '/usr/bin/python3.7', 'python3.7'), ('/usr/bin/python', '/usr/bin/python3.7', 'python3.7'),
('/usr/bin/python', '/usr/bin/python', None), ('/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'), ('v/bin/python', 'v/bin/pypy', 'pypy'),
), ),
) )

Some files were not shown because too many files have changed in this diff Show more