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
*.py[co]
/.coverage
/.mypy_cache
/.pytest_cache
/.tox
/dist
/venv*
.vscode/

View file

@ -4,52 +4,42 @@ repos:
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-docstring-first
- id: check-json
- id: check-yaml
- id: debug-statements
- id: double-quote-string-fixer
- id: name-tests-test
- id: requirements-txt-fixer
- id: double-quote-string-fixer
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.20.0
hooks:
- id: flake8
additional_dependencies: [flake8-typing-imports==1.10.0]
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.6.0
hooks:
- id: autopep8
- repo: https://github.com/pre-commit/pre-commit
rev: v2.17.0
hooks:
- id: validate_manifest
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
hooks:
- id: pyupgrade
args: [--py36-plus]
- id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder_python_imports
rev: v2.6.0
rev: v3.0.1
hooks:
- id: reorder-python-imports
args: [--py3-plus]
exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/)
args: [--py37-plus, --add-import, 'from __future__ import annotations']
- repo: https://github.com/asottile/add-trailing-comma
rev: v2.2.1
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.20.0
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.1
hooks:
- id: setup-cfg-fmt
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.6.0
hooks:
- id: autopep8
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.931
rev: v0.942
hooks:
- id: mypy
additional_dependencies: [types-all]
exclude: ^testing/resources/
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes

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

View file

@ -93,7 +93,7 @@ language, for example:
here are the apis that should be implemented for a language
Note that these are also documented in [`pre_commit/languages/all.py`](https://github.com/pre-commit/pre-commit/blob/master/pre_commit/languages/all.py)
Note that these are also documented in [`pre_commit/languages/all.py`](https://github.com/pre-commit/pre-commit/blob/main/pre_commit/languages/all.py)
#### `ENVIRONMENT_DIR`

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)
[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=master)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit/master.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/master)
[![Build Status](https://dev.azure.com/asottile/asottile/_apis/build/status/pre-commit.pre-commit?branchName=main)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main)
[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/21/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=21&branchName=main)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit/main.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit/main)
## pre-commit

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
from __future__ import annotations
import argparse
import logging
import os.path
from typing import Optional
from typing import Tuple
import pre_commit.constants as C
from pre_commit import git
@ -18,7 +18,7 @@ from pre_commit.xargs import xargs
logger = logging.getLogger(__name__)
def _repo_ref(tmpdir: str, repo: str, ref: Optional[str]) -> Tuple[str, str]:
def _repo_ref(tmpdir: str, repo: str, ref: str | None) -> tuple[str, str]:
# if `ref` is explicitly passed, use it
if ref is not None:
return repo, ref

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import sys
if sys.version_info >= (3, 8): # pragma: >=3.8 cover
@ -22,4 +24,10 @@ STAGES = (
'post-rewrite',
)
HOOK_TYPES = (
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
'post-rewrite',
)
DEFAULT = 'default'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,16 @@
from __future__ import annotations
import contextlib
import os
from typing import Generator
from typing import Sequence
from typing import Tuple
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.hook import Hook
from pre_commit.languages import helpers
from pre_commit.parse_shebang import find_executable
from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure
@ -26,6 +28,14 @@ def install_environment(
helpers.assert_version_default('coursier', version)
helpers.assert_no_additional_deps('coursier', additional_dependencies)
# Support both possible executable names (either "cs" or "coursier")
executable = find_executable('cs') or find_executable('coursier')
if executable is None:
raise AssertionError(
'pre-commit requires system-installed "cs" or "coursier" '
'executables in the application search path',
)
envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
channel = prefix.path('.pre-commit-channel')
with clean_path_on_failure(envdir):
@ -35,7 +45,7 @@ def install_environment(
helpers.run_setup_cmd(
prefix,
(
'cs',
executable,
'install',
'--default-channels=false',
f'--channel={channel}',
@ -66,6 +76,6 @@ def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
) -> Tuple[int, bytes]: # pragma: win32 no cover
) -> tuple[int, bytes]: # pragma: win32 no cover
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,11 @@
from __future__ import annotations
import contextlib
import os
import shlex
import shutil
from typing import Generator
from typing import Sequence
from typing import Tuple
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
@ -58,7 +59,11 @@ def _prefix_if_non_local_file_entry(
def _rscript_exec() -> str:
return os.path.join(os.getenv('R_HOME', ''), 'Rscript')
r_home = os.environ.get('R_HOME')
if r_home is None:
return 'Rscript'
else:
return os.path.join(r_home, 'bin', 'Rscript')
def _entry_validate(entry: Sequence[str]) -> None:
@ -80,7 +85,7 @@ def _entry_validate(entry: Sequence[str]) -> None:
)
def _cmd_from_hook(hook: Hook) -> Tuple[str, ...]:
def _cmd_from_hook(hook: Hook) -> tuple[str, ...]:
entry = shlex.split(hook.entry)
_entry_validate(entry)
@ -102,9 +107,7 @@ def install_environment(
shutil.copy(prefix.path('renv.lock'), env_dir)
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
cmd_output_b(
_rscript_exec(), '--vanilla', '-e',
f"""\
r_code_inst_environment = f"""\
prefix_dir <- {prefix.prefix_dir!r}
options(
repos = c(CRAN = "https://cran.rstudio.com"),
@ -131,24 +134,41 @@ def install_environment(
if (is_package) {{
renv::install(prefix_dir)
}}
""",
"""
cmd_output_b(
_rscript_exec(), '--vanilla', '-e',
_inline_r_setup(r_code_inst_environment),
cwd=env_dir,
)
if additional_dependencies:
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
with in_env(prefix, version):
cmd_output_b(
_rscript_exec(), *RSCRIPT_OPTS, '-e',
'renv::install(commandArgs(trailingOnly = TRUE))',
_inline_r_setup(r_code_inst_add),
*additional_dependencies,
cwd=env_dir,
)
def _inline_r_setup(code: str) -> str:
"""
Some behaviour of R cannot be configured via env variables, but can
only be configured via R options once R has started. These are set here.
"""
with_option = f"""\
options(install.packages.compile.from.source = "never")
{code}
"""
return with_option
def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
) -> Tuple[int, bytes]:
) -> tuple[int, bytes]:
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(
hook, _cmd_from_hook(hook), file_args, color=color,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,10 @@
from __future__ import annotations
import argparse
import logging
import os
import sys
from typing import Any
from typing import Optional
from typing import Sequence
from typing import Union
import pre_commit.constants as C
from pre_commit import git
@ -46,34 +45,10 @@ def _add_config_option(parser: argparse.ArgumentParser) -> None:
)
class AppendReplaceDefault(argparse.Action):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.appended = False
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Union[str, Sequence[str], None],
option_string: Optional[str] = None,
) -> None:
if not self.appended:
setattr(namespace, self.dest, [])
self.appended = True
getattr(namespace, self.dest).append(values)
def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'-t', '--hook-type', choices=(
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
'post-rewrite',
),
action=AppendReplaceDefault,
default=['pre-commit'],
dest='hook_types',
'-t', '--hook-type',
choices=C.HOOK_TYPES, action='append', dest='hook_types',
)
@ -106,7 +81,7 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'--from-ref', '--source', '-s',
help=(
'(for usage with `--from-ref`) -- this option represents the '
'(for usage with `--to-ref`) -- this option represents the '
'original ref in a `from_ref...to_ref` diff expression. '
'For `pre-push` hooks, this represents the branch you are pushing '
'to. '
@ -117,7 +92,7 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'--to-ref', '--origin', '-o',
help=(
'(for usage with `--to-ref`) -- this option represents the '
'(for usage with `--from-ref`) -- this option represents the '
'destination ref in a `from_ref...to_ref` diff expression. '
'For `pre-push` hooks, this represents the branch being pushed. '
'For `post-checkout` hooks, this represents the branch that is '
@ -175,7 +150,7 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None:
args.repo = os.path.relpath(args.repo)
def main(argv: Optional[Sequence[str]] = None) -> int:
def main(argv: Sequence[str] | None = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
parser = argparse.ArgumentParser(prog='pre-commit')
@ -197,7 +172,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
autoupdate_parser.add_argument(
'--bleeding-edge', action='store_true',
help=(
'Update to the bleeding edge of `master` instead of the latest '
'Update to the bleeding edge of `HEAD` instead of the latest '
'tagged version (the default behavior).'
),
)
@ -399,7 +374,10 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
elif args.command == 'try-repo':
return try_repo(args)
elif args.command == 'uninstall':
return uninstall(hook_types=args.hook_types)
return uninstall(
config_file=args.config,
hook_types=args.hook_types,
)
else:
raise NotImplementedError(
f'Command {args.command} not implemented.',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import shlex
from unittest import mock
@ -101,6 +103,24 @@ def test_rev_info_update_tags_only_does_not_pick_tip(tagged):
assert new_info.rev == 'v1.2.3'
def test_rev_info_update_tags_prefers_version_tag(tagged, out_of_date):
cmd_output('git', 'tag', 'latest', cwd=out_of_date.path)
config = make_config_from_repo(tagged.path, rev=tagged.original_rev)
info = RevInfo.from_config(config)
new_info = info.update(tags_only=True, freeze=False)
assert new_info.rev == 'v1.2.3'
def test_rev_info_update_tags_non_version_tag(out_of_date):
cmd_output('git', 'tag', 'latest', cwd=out_of_date.path)
config = make_config_from_repo(
out_of_date.path, rev=out_of_date.original_rev,
)
info = RevInfo.from_config(config)
new_info = info.update(tags_only=True, freeze=False)
assert new_info.rev == 'latest'
def test_rev_info_update_freeze_tag(tagged):
git_commit(cwd=tagged.path)
config = make_config_from_repo(tagged.path, rev=tagged.original_rev)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path
import re
@ -5,6 +7,7 @@ import re_assert
import pre_commit.constants as C
from pre_commit import git
from pre_commit.commands.install_uninstall import _hook_types
from pre_commit.commands.install_uninstall import CURRENT_HASH
from pre_commit.commands.install_uninstall import install
from pre_commit.commands.install_uninstall import install_hooks
@ -25,6 +28,36 @@ from testing.util import cwd
from testing.util import git_commit
def test_hook_types_explicitly_listed():
assert _hook_types(os.devnull, ['pre-push']) == ['pre-push']
def test_hook_types_default_value_when_not_specified():
assert _hook_types(os.devnull, None) == ['pre-commit']
def test_hook_types_configured(tmpdir):
cfg = tmpdir.join('t.cfg')
cfg.write('default_install_hook_types: [pre-push]\nrepos: []\n')
assert _hook_types(str(cfg), None) == ['pre-push']
def test_hook_types_configured_nonsense(tmpdir):
cfg = tmpdir.join('t.cfg')
cfg.write('default_install_hook_types: []\nrepos: []\n')
# hopefully the user doesn't do this, but the code allows it!
assert _hook_types(str(cfg), None) == []
def test_hook_types_configuration_has_error(tmpdir):
cfg = tmpdir.join('t.cfg')
cfg.write('[')
assert _hook_types(str(cfg), None) == ['pre-commit']
def test_is_not_script():
assert is_our_script('setup.py') is False
@ -59,7 +92,7 @@ def test_install_multiple_hooks_at_once(in_git_dir, store):
install(C.CONFIG_FILE, store, hook_types=['pre-commit', 'pre-push'])
assert in_git_dir.join('.git/hooks/pre-commit').exists()
assert in_git_dir.join('.git/hooks/pre-push').exists()
uninstall(hook_types=['pre-commit', 'pre-push'])
uninstall(C.CONFIG_FILE, hook_types=['pre-commit', 'pre-push'])
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
assert not in_git_dir.join('.git/hooks/pre-push').exists()
@ -77,14 +110,14 @@ def test_install_hooks_dead_symlink(in_git_dir, store):
def test_uninstall_does_not_blow_up_when_not_there(in_git_dir):
assert uninstall(hook_types=['pre-commit']) == 0
assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
def test_uninstall(in_git_dir, store):
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
install(C.CONFIG_FILE, store, hook_types=['pre-commit'])
assert in_git_dir.join('.git/hooks/pre-commit').exists()
uninstall(hook_types=['pre-commit'])
uninstall(C.CONFIG_FILE, hook_types=['pre-commit'])
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
@ -414,7 +447,7 @@ def test_uninstall_restores_legacy_hooks(tempdir_factory, store):
# Now install and uninstall pre-commit
assert install(C.CONFIG_FILE, store, hook_types=['pre-commit']) == 0
assert uninstall(hook_types=['pre-commit']) == 0
assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
# Make sure we installed the "old" hook correctly
ret, output = _get_commit_output(tempdir_factory, touch_file='baz')
@ -449,7 +482,7 @@ def test_uninstall_doesnt_remove_not_our_hooks(in_git_dir):
pre_commit.write('#!/usr/bin/env bash\necho 1\n')
make_executable(pre_commit.strpath)
assert uninstall(hook_types=['pre-commit']) == 0
assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
assert pre_commit.exists()
@ -1005,3 +1038,16 @@ def test_install_temporarily_allow_mising_config(tempdir_factory, store):
'Skipping `pre-commit`.'
)
assert expected in output
def test_install_uninstall_default_hook_types(in_git_dir, store):
cfg_src = 'default_install_hook_types: [pre-commit, pre-push]\nrepos: []\n'
in_git_dir.join(C.CONFIG_FILE).write(cfg_src)
assert not install(C.CONFIG_FILE, store, hook_types=None)
assert os.access(in_git_dir.join('.git/hooks/pre-commit').strpath, os.X_OK)
assert os.access(in_git_dir.join('.git/hooks/pre-push').strpath, os.X_OK)
assert not uninstall(C.CONFIG_FILE, hook_types=None)
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
assert not in_git_dir.join('.git/hooks/pre-push').exists()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path
import pytest
@ -19,6 +21,20 @@ def test_get_root_deeper(in_git_dir):
assert os.path.normcase(git.get_root()) == expected
def test_get_root_in_git_sub_dir(in_git_dir):
expected = os.path.normcase(in_git_dir.strpath)
with pytest.raises(FatalError):
with in_git_dir.join('.git/objects').ensure_dir().as_cwd():
assert os.path.normcase(git.get_root()) == expected
def test_get_root_not_in_working_dir(in_git_dir):
expected = os.path.normcase(in_git_dir.strpath)
with pytest.raises(FatalError):
with in_git_dir.join('..').ensure_dir().as_cwd():
assert os.path.normcase(git.get_root()) == expected
def test_in_exactly_dot_git(in_git_dir):
with in_git_dir.join('.git').as_cwd(), pytest.raises(FatalError):
git.get_root()
@ -38,6 +54,22 @@ def test_get_root_bare_worktree(tmpdir):
assert git.get_root() == os.path.abspath('.')
def test_get_git_dir(tmpdir):
"""Regression test for #1972"""
src = tmpdir.join('src').ensure_dir()
cmd_output('git', 'init', str(src))
git_commit(cwd=str(src))
worktree = tmpdir.join('worktree').ensure_dir()
cmd_output('git', 'worktree', 'add', '../worktree', cwd=src)
with worktree.as_cwd():
assert git.get_git_dir() == src.ensure_dir(
'.git/worktrees/worktree',
)
assert git.get_git_common_dir() == src.ensure_dir('.git')
def test_get_root_worktree_in_git(tmpdir):
src = tmpdir.join('src').ensure_dir()
cmd_output('git', 'init', str(src))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
from __future__ import annotations
import os.path
import sys
from unittest import mock
@ -47,16 +49,16 @@ def test_norm_version_of_default_is_sys_executable():
assert python.norm_version('default') is None
@pytest.mark.parametrize('v', ('python3.6', 'python3', 'python'))
@pytest.mark.parametrize('v', ('python3.9', 'python3', 'python'))
def test_sys_executable_matches(v):
with mock.patch.object(sys, 'version_info', (3, 6, 7)):
with mock.patch.object(sys, 'version_info', (3, 9, 10)):
assert python._sys_executable_matches(v)
assert python.norm_version(v) is None
@pytest.mark.parametrize('v', ('notpython', 'python3.x'))
def test_sys_executable_matches_does_not_match(v):
with mock.patch.object(sys, 'version_info', (3, 6, 7)):
with mock.patch.object(sys, 'version_info', (3, 9, 10)):
assert not python._sys_executable_matches(v)
@ -65,7 +67,7 @@ def test_sys_executable_matches_does_not_match(v):
('/usr/bin/python3', '/usr/bin/python3.7', 'python3'),
('/usr/bin/python', '/usr/bin/python3.7', 'python3.7'),
('/usr/bin/python', '/usr/bin/python', None),
('/usr/bin/python3.6m', '/usr/bin/python3.6m', 'python3.6m'),
('/usr/bin/python3.7m', '/usr/bin/python3.7m', 'python3.7m'),
('v/bin/python', 'v/bin/pypy', 'pypy'),
),
)

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