summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-12-04 03:57:54 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-12-04 03:57:54 +0000
commitfc1651ef0b7ccedc604b28d5893f5cd486cd4091 (patch)
tree3ea033abb546734a46b2e947756e0d9f792b0e80
parentReleasing debian version 2.15.0-1. (diff)
downloadpre-commit-fc1651ef0b7ccedc604b28d5893f5cd486cd4091.tar.xz
pre-commit-fc1651ef0b7ccedc604b28d5893f5cd486cd4091.zip
Merging upstream version 2.16.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/FUNDING.yml2
-rw-r--r--.github/ISSUE_TEMPLATE/bug.yaml41
-rw-r--r--.pre-commit-config.yaml12
-rw-r--r--CHANGELOG.md30
-rw-r--r--pre_commit/__main__.py2
-rw-r--r--pre_commit/clientlib.py25
-rw-r--r--pre_commit/commands/autoupdate.py22
-rw-r--r--pre_commit/commands/install_uninstall.py13
-rw-r--r--pre_commit/commands/run.py2
-rw-r--r--pre_commit/constants.py6
-rw-r--r--pre_commit/git.py12
-rw-r--r--pre_commit/hook.py1
-rw-r--r--pre_commit/languages/pygrep.py2
-rw-r--r--pre_commit/main.py2
-rw-r--r--pre_commit/meta_hooks/check_hooks_apply.py2
-rw-r--r--pre_commit/meta_hooks/check_useless_excludes.py2
-rw-r--r--pre_commit/meta_hooks/identity.py2
-rwxr-xr-xpre_commit/resources/hook-tmpl50
-rw-r--r--pre_commit/staged_files_only.py10
-rw-r--r--pre_commit/util.py4
-rw-r--r--requirements-dev.txt2
-rw-r--r--setup.cfg5
-rwxr-xr-xtesting/gen-languages-all2
-rwxr-xr-xtesting/make-archives2
-rwxr-xr-xtesting/zipapp/entry2
-rwxr-xr-xtesting/zipapp/make2
-rwxr-xr-xtesting/zipapp/python2
-rw-r--r--tests/clientlib_test.py64
-rw-r--r--tests/commands/autoupdate_test.py9
-rw-r--r--tests/commands/install_uninstall_test.py6
-rw-r--r--tests/commands/run_test.py12
-rw-r--r--tests/git_test.py10
-rw-r--r--tests/repository_test.py16
-rw-r--r--tests/staged_files_only_test.py43
34 files changed, 297 insertions, 122 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 9408e44..0000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-github: asottile
-open_collective: pre-commit
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
new file mode 100644
index 0000000..6cce5fe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -0,0 +1,41 @@
+name: bug report
+description: something went wrong
+body:
+ - type: markdown
+ attributes:
+ value: |
+ this is for issues for `pre-commit` (the framework).
+ if you are reporting an issue for [pre-commit.ci] please report it at [pre-commit-ci/issues]
+
+ [pre-commit.ci]: https://pre-commit.ci
+ [pre-commit-ci/issues]: https://github.com/pre-commit-ci/issues
+ - type: textarea
+ id: freeform
+ attributes:
+ label: describe your issue
+ placeholder: 'I was doing ... I ran ... I expected ... I got ...'
+ validations:
+ required: true
+ - type: input
+ id: version
+ attributes:
+ label: pre-commit --version
+ placeholder: pre-commit x.x.x
+ validations:
+ required: true
+ - type: textarea
+ id: configuration
+ attributes:
+ label: .pre-commit-config.yaml
+ description: (auto-rendered as yaml, no need for backticks)
+ placeholder: 'repos: ...'
+ render: yaml
+ validations:
+ required: true
+ - type: textarea
+ id: error-log
+ attributes:
+ label: '~/.cache/pre-commit/pre-commit.log (if present)'
+ placeholder: "### version information\n..."
+ validations:
+ required: false
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 57466c7..49517c3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,7 +12,7 @@ repos:
- id: requirements-txt-fixer
- id: double-quote-string-fixer
- repo: https://github.com/PyCQA/flake8
- rev: 3.9.2
+ rev: 4.0.1
hooks:
- id: flake8
additional_dependencies: [flake8-typing-imports==1.10.0]
@@ -21,11 +21,11 @@ repos:
hooks:
- id: autopep8
- repo: https://github.com/pre-commit/pre-commit
- rev: v2.15.0
+ rev: v2.16.0
hooks:
- id: validate_manifest
- repo: https://github.com/asottile/pyupgrade
- rev: v2.25.0
+ rev: v2.29.1
hooks:
- id: pyupgrade
args: [--py36-plus]
@@ -35,16 +35,16 @@ repos:
- id: reorder-python-imports
args: [--py3-plus]
- repo: https://github.com/asottile/add-trailing-comma
- rev: v2.1.0
+ rev: v2.2.1
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
- rev: v1.17.0
+ rev: v1.20.0
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.910
+ rev: v0.910-1
hooks:
- id: mypy
additional_dependencies: [types-all]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b93256..55f46d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,33 @@
+2.16.0 - 2021-11-30
+===================
+
+### Features
+- add warning for regexes containing `[\/]` or `[/\\]`.
+ - #2053 PR by @radek-sprta.
+ - #2043 issue by @asottile.
+- move hook template back to `bash` resolving shebang-portability issues.
+ - #2065 PR by @asottile.
+- add support for `fail_fast` at the individual hook level.
+ - #2097 PR by @colens3.
+ - #1143 issue by @potiuk.
+- allow passthrough of `GIT_CONFIG_KEY_*`, `GIT_CONFIG_VALUE_*`, and
+ `GIT_CONFIG_COUNT`.
+ - #2136 PR by @emzeat.
+
+### Fixes
+- fix pre-commit autoupdate for `core.useBuiltinFSMonitor=true` on windows.
+ - #2047 PR by @asottile.
+ - #2046 issue by @lcnittl.
+- fix temporary file stashing with for `submodule.recurse=1`.
+ - #2071 PR by @asottile.
+ - #2063 issue by @a666.
+- ban broken importlib-resources versions.
+ - #2098 PR by @asottile.
+- replace `exit(...)` with `raise SystemExit(...)` for portability.
+ - #2103 PR by @asottile.
+ - #2104 PR by @asottile.
+
+
2.15.0 - 2021-09-02
===================
diff --git a/pre_commit/__main__.py b/pre_commit/__main__.py
index 5414068..83bd93c 100644
--- a/pre_commit/__main__.py
+++ b/pre_commit/__main__.py
@@ -2,4 +2,4 @@ from pre_commit.main import main
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py
index bc7274a..6377a8b 100644
--- a/pre_commit/clientlib.py
+++ b/pre_commit/clientlib.py
@@ -70,6 +70,7 @@ MANIFEST_HOOK_DICT = cfgv.Map(
),
cfgv.Optional('args', cfgv.check_array(cfgv.check_string), []),
cfgv.Optional('always_run', cfgv.check_bool, False),
+ cfgv.Optional('fail_fast', cfgv.check_bool, False),
cfgv.Optional('pass_filenames', cfgv.check_bool, True),
cfgv.Optional('description', cfgv.check_string, ''),
cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
@@ -143,6 +144,18 @@ class OptionalSensibleRegexAtHook(cfgv.OptionalNoDefault):
f"regex, not a glob -- matching '/*' probably isn't what you "
f'want here',
)
+ if r'[\/]' in dct.get(self.key, ''):
+ logger.warning(
+ fr'pre-commit normalizes slashes in the {self.key!r} field '
+ fr'in hook {dct.get("id")!r} to forward slashes, so you '
+ fr'can use / instead of [\/]',
+ )
+ if r'[/\\]' in dct.get(self.key, ''):
+ logger.warning(
+ fr'pre-commit normalizes slashes in the {self.key!r} field '
+ fr'in hook {dct.get("id")!r} to forward slashes, so you '
+ fr'can use / instead of [/\\]',
+ )
class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
@@ -154,6 +167,18 @@ class OptionalSensibleRegexAtTop(cfgv.OptionalNoDefault):
f'The top-level {self.key!r} field is a regex, not a glob -- '
f"matching '/*' probably isn't what you want here",
)
+ if r'[\/]' in dct.get(self.key, ''):
+ logger.warning(
+ fr'pre-commit normalizes the slashes in the top-level '
+ fr'{self.key!r} field to forward slashes, so you can use / '
+ fr'instead of [\/]',
+ )
+ if r'[/\\]' in dct.get(self.key, ''):
+ logger.warning(
+ fr'pre-commit normalizes the slashes in the top-level '
+ fr'{self.key!r} field to forward slashes, so you can use / '
+ fr'instead of [/\\]',
+ )
class MigrateShaToRev:
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py
index 33a3473..5cb978e 100644
--- a/pre_commit/commands/autoupdate.py
+++ b/pre_commit/commands/autoupdate.py
@@ -36,24 +36,36 @@ class RevInfo(NamedTuple):
return cls(config['repo'], config['rev'], None)
def update(self, tags_only: bool, freeze: bool) -> 'RevInfo':
+ git_cmd = ('git', *git.NO_FS_MONITOR)
+
if tags_only:
- tag_cmd = ('git', 'describe', 'FETCH_HEAD', '--tags', '--abbrev=0')
+ tag_cmd = (
+ *git_cmd, 'describe',
+ 'FETCH_HEAD', '--tags', '--abbrev=0',
+ )
else:
- tag_cmd = ('git', 'describe', 'FETCH_HEAD', '--tags', '--exact')
+ tag_cmd = (
+ *git_cmd, 'describe',
+ 'FETCH_HEAD', '--tags', '--exact',
+ )
with tmpdir() as tmp:
git.init_repo(tmp, self.repo)
- cmd_output_b('git', 'fetch', 'origin', 'HEAD', '--tags', cwd=tmp)
+ cmd_output_b(
+ *git_cmd, 'fetch', 'origin', 'HEAD', '--tags',
+ cwd=tmp,
+ )
try:
rev = cmd_output(*tag_cmd, cwd=tmp)[1].strip()
except CalledProcessError:
- cmd = ('git', 'rev-parse', 'FETCH_HEAD')
+ cmd = (*git_cmd, 'rev-parse', 'FETCH_HEAD')
rev = cmd_output(*cmd, cwd=tmp)[1].strip()
frozen = None
if freeze:
- exact = cmd_output('git', 'rev-parse', rev, cwd=tmp)[1].strip()
+ exact_rev_cmd = (*git_cmd, 'rev-parse', rev)
+ exact = cmd_output(*exact_rev_cmd, cwd=tmp)[1].strip()
if exact != rev:
rev, frozen = exact, rev
return self._replace(rev=rev, frozen=frozen)
diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py
index 73c8d60..7974423 100644
--- a/pre_commit/commands/install_uninstall.py
+++ b/pre_commit/commands/install_uninstall.py
@@ -1,6 +1,7 @@
import itertools
import logging
import os.path
+import shlex
import shutil
import sys
from typing import Optional
@@ -100,19 +101,17 @@ def _install_hook_script(
args = ['hook-impl', f'--config={config_file}', f'--hook-type={hook_type}']
if skip_on_missing_config:
args.append('--skip-on-missing-config')
- params = {'INSTALL_PYTHON': sys.executable, 'ARGS': args}
with open(hook_path, 'w') as hook_file:
contents = resource_text('hook-tmpl')
before, rest = contents.split(TEMPLATE_START)
- to_template, after = rest.split(TEMPLATE_END)
-
- before = before.replace('#!/usr/bin/env python3', shebang())
+ _, after = rest.split(TEMPLATE_END)
hook_file.write(before + TEMPLATE_START)
- for line in to_template.splitlines():
- var = line.split()[0]
- hook_file.write(f'{var} = {params[var]!r}\n')
+ hook_file.write(f'INSTALL_PYTHON={shlex.quote(sys.executable)}\n')
+ # TODO: python3.8+: shlex.join
+ args_s = ' '.join(shlex.quote(part) for part in args)
+ hook_file.write(f'ARGS=({args_s})\n')
hook_file.write(TEMPLATE_END + after)
make_executable(hook_path)
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 95ad5e9..2714faf 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -290,7 +290,7 @@ def _run_hooks(
verbose=args.verbose, use_color=args.color,
)
retval |= current_retval
- if retval and config['fail_fast']:
+ if retval and (config['fail_fast'] or hook.fail_fast):
break
if retval and args.show_diff_on_failure and prior_diff:
if args.all_files:
diff --git a/pre_commit/constants.py b/pre_commit/constants.py
index 1a69c90..d2f9363 100644
--- a/pre_commit/constants.py
+++ b/pre_commit/constants.py
@@ -1,9 +1,9 @@
import sys
-if sys.version_info < (3, 8): # pragma: no cover (<PY38)
- import importlib_metadata
-else: # pragma: no cover (PY38+)
+if sys.version_info >= (3, 8): # pragma: >=3.8 cover
import importlib.metadata as importlib_metadata
+else: # pragma: <3.8 cover
+ import importlib_metadata
CONFIG_FILE = '.pre-commit-config.yaml'
MANIFEST_FILE = '.pre-commit-hooks.yaml'
diff --git a/pre_commit/git.py b/pre_commit/git.py
index 6264529..e9ec601 100644
--- a/pre_commit/git.py
+++ b/pre_commit/git.py
@@ -12,9 +12,11 @@ from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
-
logger = logging.getLogger(__name__)
+# see #2046
+NO_FS_MONITOR = ('-c', 'core.useBuiltinFSMonitor=false')
+
def zsplit(s: str) -> List[str]:
s = s.strip('\0')
@@ -39,9 +41,10 @@ def no_git_env(
return {
k: v for k, v in _env.items()
if not k.startswith('GIT_') or
+ k.startswith(('GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_')) or
k in {
'GIT_EXEC_PATH', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSL_CAINFO',
- 'GIT_SSL_NO_VERIFY',
+ 'GIT_SSL_NO_VERIFY', 'GIT_CONFIG_COUNT',
}
}
@@ -185,10 +188,11 @@ def init_repo(path: str, remote: str) -> None:
if os.path.isdir(remote):
remote = os.path.abspath(remote)
+ git = ('git', *NO_FS_MONITOR)
env = no_git_env()
# avoid the user's template so that hooks do not recurse
- cmd_output_b('git', 'init', '--template=', path, env=env)
- cmd_output_b('git', 'remote', 'add', 'origin', remote, cwd=path, env=env)
+ cmd_output_b(*git, 'init', '--template=', path, env=env)
+ cmd_output_b(*git, 'remote', 'add', 'origin', remote, cwd=path, env=env)
def commit(repo: str = '.') -> None:
diff --git a/pre_commit/hook.py b/pre_commit/hook.py
index ea77394..82e99c5 100644
--- a/pre_commit/hook.py
+++ b/pre_commit/hook.py
@@ -27,6 +27,7 @@ class Hook(NamedTuple):
additional_dependencies: Sequence[str]
args: Sequence[str]
always_run: bool
+ fail_fast: bool
pass_filenames: bool
description: str
language_version: str
diff --git a/pre_commit/languages/pygrep.py b/pre_commit/languages/pygrep.py
index c80d679..a713c3f 100644
--- a/pre_commit/languages/pygrep.py
+++ b/pre_commit/languages/pygrep.py
@@ -124,4 +124,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/pre_commit/main.py b/pre_commit/main.py
index 2b50c91..f1e8d03 100644
--- a/pre_commit/main.py
+++ b/pre_commit/main.py
@@ -411,4 +411,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py
index a1e9352..a6eb0e0 100644
--- a/pre_commit/meta_hooks/check_hooks_apply.py
+++ b/pre_commit/meta_hooks/check_hooks_apply.py
@@ -39,4 +39,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py
index 6116597..60870f8 100644
--- a/pre_commit/meta_hooks/check_useless_excludes.py
+++ b/pre_commit/meta_hooks/check_useless_excludes.py
@@ -77,4 +77,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/pre_commit/meta_hooks/identity.py b/pre_commit/meta_hooks/identity.py
index 730d0ec..12eb02f 100644
--- a/pre_commit/meta_hooks/identity.py
+++ b/pre_commit/meta_hooks/identity.py
@@ -13,4 +13,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/pre_commit/resources/hook-tmpl b/pre_commit/resources/hook-tmpl
index 299144e..53d29f9 100755
--- a/pre_commit/resources/hook-tmpl
+++ b/pre_commit/resources/hook-tmpl
@@ -1,44 +1,20 @@
-#!/usr/bin/env python3
+#!/usr/bin/env bash
# File generated by pre-commit: https://pre-commit.com
# ID: 138fd403232d2ddd5efb44317e38bf03
-import os
-import sys
-
-# we try our best, but the shebang of this script is difficult to determine:
-# - macos doesn't ship with python3
-# - windows executables are almost always `python.exe`
-# therefore we continue to support python2 for this small script
-if sys.version_info < (3, 3):
- from distutils.spawn import find_executable as which
-else:
- from shutil import which
-
-# work around https://github.com/Homebrew/homebrew-core/issues/30445
-os.environ.pop('__PYVENV_LAUNCHER__', None)
# start templated
-INSTALL_PYTHON = ''
-ARGS = ['hook-impl']
+INSTALL_PYTHON=''
+ARGS=(hook-impl)
# end templated
-ARGS.extend(('--hook-dir', os.path.realpath(os.path.dirname(__file__))))
-ARGS.append('--')
-ARGS.extend(sys.argv[1:])
-
-DNE = '`pre-commit` not found. Did you forget to activate your virtualenv?'
-if os.access(INSTALL_PYTHON, os.X_OK):
- CMD = [INSTALL_PYTHON, '-mpre_commit']
-elif which('pre-commit'):
- CMD = ['pre-commit']
-else:
- raise SystemExit(DNE)
-CMD.extend(ARGS)
-if sys.platform == 'win32': # https://bugs.python.org/issue19124
- import subprocess
+HERE="$(cd "$(dirname "$0")" && pwd)"
+ARGS+=(--hook-dir "$HERE" -- "$@")
- if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
- raise SystemExit(subprocess.Popen(CMD).wait())
- else:
- raise SystemExit(subprocess.call(CMD))
-else:
- os.execvp(CMD[0], CMD)
+if [ -x "$INSTALL_PYTHON" ]; then
+ exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
+elif command -v pre-commit > /dev/null; then
+ exec pre-commit "${ARGS[@]}"
+else
+ echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2
+ exit 1
+fi
diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py
index 48cc102..bad004c 100644
--- a/pre_commit/staged_files_only.py
+++ b/pre_commit/staged_files_only.py
@@ -13,6 +13,12 @@ from pre_commit.xargs import xargs
logger = logging.getLogger('pre_commit')
+# without forcing submodule.recurse=0, changes in nested submodules will be
+# discarded if `submodule.recurse=1` is configured
+# we choose this instead of `--no-recurse-submodules` because it works on
+# versions of git before that option was added to `git checkout`
+_CHECKOUT_CMD = ('git', '-c', 'submodule.recurse=0', 'checkout', '--', '.')
+
def _git_apply(patch: str) -> None:
args = ('apply', '--whitespace=nowarn', patch)
@@ -58,7 +64,7 @@ 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('git', 'checkout', '--', '.', env=no_checkout_env)
+ cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
try:
yield
@@ -74,7 +80,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
# We failed to apply the patch, presumably due to fixes made
# by hooks.
# Roll back the changes made by hooks.
- cmd_output_b('git', 'checkout', '--', '.', env=no_checkout_env)
+ cmd_output_b(*_CHECKOUT_CMD, env=no_checkout_env)
_git_apply(patch_filename)
logger.info(f'Restored changes from {patch_filename}.')
diff --git a/pre_commit/util.py b/pre_commit/util.py
index 6bf8ae7..6977acb 100644
--- a/pre_commit/util.py
+++ b/pre_commit/util.py
@@ -21,10 +21,10 @@ import yaml
from pre_commit import parse_shebang
-if sys.version_info >= (3, 7): # pragma: no cover (PY37+)
+if sys.version_info >= (3, 7): # pragma: >=3.7 cover
from importlib.resources import open_binary
from importlib.resources import read_text
-else: # pragma: no cover (<PY37)
+else: # pragma: <3.7 cover
from importlib_resources import open_binary
from importlib_resources import read_text
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 56afd41..3a7b11c 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,4 +1,4 @@
-covdefaults
+covdefaults>=2.1
coverage
distlib
pytest
diff --git a/setup.cfg b/setup.cfg
index c0f4f0e..02669c7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pre_commit
-version = 2.15.0
+version = 2.16.0
description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md
long_description_content_type = text/markdown
@@ -17,6 +17,7 @@ classifiers =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
+ Programming Language :: Python :: 3.10
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
@@ -30,7 +31,7 @@ install_requires =
toml
virtualenv>=20.0.8
importlib-metadata;python_version<"3.8"
- importlib-resources;python_version<"3.7"
+ importlib-resources<5.3;python_version<"3.7"
python_requires = >=3.6.1
[options.packages.find]
diff --git a/testing/gen-languages-all b/testing/gen-languages-all
index 51e4bce..c933c14 100755
--- a/testing/gen-languages-all
+++ b/testing/gen-languages-all
@@ -25,4 +25,4 @@ def main() -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/testing/make-archives b/testing/make-archives
index cb0b0a4..707fd88 100755
--- a/testing/make-archives
+++ b/testing/make-archives
@@ -80,4 +80,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/testing/zipapp/entry b/testing/zipapp/entry
index f0a345e..87f9291 100755
--- a/testing/zipapp/entry
+++ b/testing/zipapp/entry
@@ -68,4 +68,4 @@ def main() -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/testing/zipapp/make b/testing/zipapp/make
index 8740b2f..55b6d2c 100755
--- a/testing/zipapp/make
+++ b/testing/zipapp/make
@@ -106,4 +106,4 @@ def main() -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/testing/zipapp/python b/testing/zipapp/python
index 97c5928..7184a1a 100755
--- a/testing/zipapp/python
+++ b/testing/zipapp/python
@@ -45,4 +45,4 @@ def main() -> int:
if __name__ == '__main__':
- exit(main())
+ raise SystemExit(main())
diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py
index da794e6..5427b1d 100644
--- a/tests/clientlib_test.py
+++ b/tests/clientlib_test.py
@@ -247,38 +247,64 @@ def test_warn_mutable_rev_conditional():
cfgv.validate(config_obj, CONFIG_REPO_DICT)
-def test_validate_optional_sensible_regex_at_hook_level(caplog):
+@pytest.mark.parametrize(
+ ('regex', 'warning'),
+ (
+ (
+ r'dir/*.py',
+ "The 'files' field in hook 'flake8' is a regex, not a glob -- "
+ "matching '/*' probably isn't what you want here",
+ ),
+ (
+ r'dir[\/].*\.py',
+ r"pre-commit normalizes slashes in the 'files' field in hook "
+ r"'flake8' to forward slashes, so you can use / instead of [\/]",
+ ),
+ (
+ r'dir[/\\].*\.py',
+ r"pre-commit normalizes slashes in the 'files' field in hook "
+ r"'flake8' to forward slashes, so you can use / instead of [/\\]",
+ ),
+ ),
+)
+def test_validate_optional_sensible_regex_at_hook(caplog, regex, warning):
config_obj = {
'id': 'flake8',
- 'files': 'dir/*.py',
+ 'files': regex,
}
cfgv.validate(config_obj, CONFIG_HOOK_DICT)
- assert caplog.record_tuples == [
+ assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
+
+
+@pytest.mark.parametrize(
+ ('regex', 'warning'),
+ (
(
- 'pre_commit',
- logging.WARNING,
- "The 'files' field in hook 'flake8' is a regex, not a glob -- "
+ r'dir/*.py',
+ "The top-level 'files' field is a regex, not a glob -- "
"matching '/*' probably isn't what you want here",
),
- ]
-
-
-def test_validate_optional_sensible_regex_at_top_level(caplog):
+ (
+ r'dir[\/].*\.py',
+ r"pre-commit normalizes the slashes in the top-level 'files' "
+ r'field to forward slashes, so you can use / instead of [\/]',
+ ),
+ (
+ r'dir[/\\].*\.py',
+ r"pre-commit normalizes the slashes in the top-level 'files' "
+ r'field to forward slashes, so you can use / instead of [/\\]',
+ ),
+ ),
+)
+def test_validate_optional_sensible_regex_at_top_level(caplog, regex, warning):
config_obj = {
- 'files': 'dir/*.py',
+ 'files': regex,
'repos': [],
}
cfgv.validate(config_obj, CONFIG_SCHEMA)
- assert caplog.record_tuples == [
- (
- 'pre_commit',
- logging.WARNING,
- "The top-level 'files' field is a regex, not a glob -- matching "
- "'/*' probably isn't what you want here",
- ),
- ]
+ assert caplog.record_tuples == [('pre_commit', logging.WARNING, warning)]
@pytest.mark.parametrize('fn', (validate_config_main, validate_manifest_main))
diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py
index b2bad60..7316eb9 100644
--- a/tests/commands/autoupdate_test.py
+++ b/tests/commands/autoupdate_test.py
@@ -5,6 +5,7 @@ import pytest
import yaml
import pre_commit.constants as C
+from pre_commit import envcontext
from pre_commit import git
from pre_commit import util
from pre_commit.commands.autoupdate import _check_hooks_still_exist_at_rev
@@ -176,6 +177,14 @@ def test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store):
assert cfg.read() == fmt.format(out_of_date.path, out_of_date.head_rev)
+def test_autoupdate_with_core_useBuiltinFSMonitor(out_of_date, tmpdir, store):
+ # force the setting on "globally" for git
+ home = tmpdir.join('fakehome').ensure_dir()
+ home.join('.gitconfig').write('[core]\nuseBuiltinFSMonitor = true\n')
+ with envcontext.envcontext((('HOME', str(home)),)):
+ test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store)
+
+
def test_autoupdate_pure_yaml(out_of_date, tmpdir, store):
with mock.patch.object(util, 'Dumper', yaml.SafeDumper):
test_autoupdate_out_of_date_repo(out_of_date, tmpdir, store)
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 3c07124..8339903 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -278,11 +278,7 @@ def test_environment_not_sourced(tempdir_factory, store):
hook = os.path.join(path, '.git/hooks/pre-commit')
with open(hook) as f:
src = f.read()
- src = re.sub(
- '\nINSTALL_PYTHON =.*\n',
- '\nINSTALL_PYTHON = "/dne"\n',
- src,
- )
+ src = re.sub('\nINSTALL_PYTHON=.*\n', '\nINSTALL_PYTHON="/dne"\n', src)
with open(hook, 'w') as f:
f.write(src)
diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py
index 8c15395..3a6fa2a 100644
--- a/tests/commands/run_test.py
+++ b/tests/commands/run_test.py
@@ -985,6 +985,18 @@ def test_fail_fast(cap_out, store, repo_with_failing_hook):
assert printed.count(b'Failing hook') == 1
+def test_fail_fast_per_hook(cap_out, store, repo_with_failing_hook):
+ with modify_config() as config:
+ # More than one hook
+ config['repos'][0]['hooks'] *= 2
+ config['repos'][0]['hooks'][0]['fail_fast'] = True
+ stage_a_file()
+
+ ret, printed = _do_run(cap_out, store, repo_with_failing_hook, run_opts())
+ # it should have only run one hook
+ assert printed.count(b'Failing hook') == 1
+
+
def test_classifier_removes_dne():
classifier = Classifier(('this_file_does_not_exist',))
assert classifier.filenames == []
diff --git a/tests/git_test.py b/tests/git_test.py
index aa21880..bcb3fd1 100644
--- a/tests/git_test.py
+++ b/tests/git_test.py
@@ -227,6 +227,11 @@ def test_no_git_env():
'GIT_SSH': '/usr/bin/ssh',
'GIT_SSH_COMMAND': 'ssh -o',
'GIT_DIR': '/none/shall/pass',
+ 'GIT_CONFIG_KEY_0': 'user.name',
+ 'GIT_CONFIG_VALUE_0': 'anthony',
+ 'GIT_CONFIG_KEY_1': 'user.email',
+ 'GIT_CONFIG_VALUE_1': 'asottile@example.com',
+ 'GIT_CONFIG_COUNT': '2',
}
no_git_env = git.no_git_env(env)
assert no_git_env == {
@@ -234,6 +239,11 @@ def test_no_git_env():
'GIT_EXEC_PATH': '/some/git/exec/path',
'GIT_SSH': '/usr/bin/ssh',
'GIT_SSH_COMMAND': 'ssh -o',
+ 'GIT_CONFIG_KEY_0': 'user.name',
+ 'GIT_CONFIG_VALUE_0': 'anthony',
+ 'GIT_CONFIG_KEY_1': 'user.email',
+ 'GIT_CONFIG_VALUE_1': 'asottile@example.com',
+ 'GIT_CONFIG_COUNT': '2',
}
diff --git a/tests/repository_test.py b/tests/repository_test.py
index 4121fed..36268e1 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -111,8 +111,8 @@ def test_local_conda_additional_dependencies(store):
'name': 'local-conda',
'entry': 'python',
'language': 'conda',
- 'args': ['-c', 'import tzdata; print("OK")'],
- 'additional_dependencies': ['python-tzdata'],
+ 'args': ['-c', 'import botocore; print("OK")'],
+ 'additional_dependencies': ['botocore'],
}],
}
hook = _get_hook(config, store, 'local-conda')
@@ -164,7 +164,7 @@ def test_python_hook_weird_setup_cfg(in_git_dir, tempdir_factory, store):
)
-def test_python_venv(tempdir_factory, store): # pragma: no cover (no venv)
+def test_python_venv(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'python_venv_hooks_repo',
'foo', [os.devnull],
@@ -245,7 +245,6 @@ def test_run_a_docker_image_hook(tempdir_factory, store, hook_id):
)
-@xfailif_windows # pragma: win32 no cover
def test_run_a_node_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'node_hooks_repo',
@@ -253,7 +252,6 @@ def test_run_a_node_hook(tempdir_factory, store):
)
-@xfailif_windows # pragma: win32 no cover
def test_run_a_node_hook_default_version(tempdir_factory, store):
# make sure that this continues to work for platforms where node is not
# installed at the system
@@ -263,7 +261,6 @@ def test_run_a_node_hook_default_version(tempdir_factory, store):
test_run_a_node_hook(tempdir_factory, store)
-@xfailif_windows # pragma: win32 no cover
def test_run_versioned_node_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'node_versioned_hooks_repo',
@@ -271,7 +268,6 @@ def test_run_versioned_node_hook(tempdir_factory, store):
)
-@xfailif_windows # pragma: win32 no cover
def test_node_hook_with_npm_userconfig_set(tempdir_factory, store, tmpdir):
cfg = tmpdir.join('cfg')
cfg.write('cache=/dne\n')
@@ -653,7 +649,6 @@ def test_additional_ruby_dependencies_installed(tempdir_factory, store):
assert 'tins' in output
-@xfailif_windows # pragma: win32 no cover
def test_additional_node_dependencies_installed(tempdir_factory, store):
path = make_repo(tempdir_factory, 'node_hooks_repo')
config = make_config_from_repo(path)
@@ -1007,6 +1002,7 @@ def test_manifest_hooks(tempdir_factory, store):
types=['file'],
types_or=[],
verbose=False,
+ fail_fast=False,
)
@@ -1025,13 +1021,13 @@ def test_local_perl_additional_dependencies(store):
'name': 'hello',
'entry': 'perltidy --version',
'language': 'perl',
- 'additional_dependencies': ['SHANCOCK/Perl-Tidy-20200110.tar.gz'],
+ 'additional_dependencies': ['SHANCOCK/Perl-Tidy-20211029.tar.gz'],
}],
}
hook = _get_hook(config, store, 'hello')
ret, out = _hook_run(hook, (), color=False)
assert ret == 0
- assert _norm_out(out).startswith(b'This is perltidy, v20200110')
+ assert _norm_out(out).startswith(b'This is perltidy, v20211029')
@pytest.mark.parametrize(
diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py
index ddb9574..2e3f620 100644
--- a/tests/staged_files_only_test.py
+++ b/tests/staged_files_only_test.py
@@ -181,9 +181,11 @@ def test_img_conflict(img_staged, patch_dir):
@pytest.fixture
-def submodule_with_commits(tempdir_factory):
+def repo_with_commits(tempdir_factory):
path = git_dir(tempdir_factory)
with cwd(path):
+ open('foo', 'a+').close()
+ cmd_output('git', 'add', 'foo')
git_commit()
rev1 = cmd_output('git', 'rev-parse', 'HEAD')[1].strip()
git_commit()
@@ -196,18 +198,21 @@ def checkout_submodule(rev):
@pytest.fixture
-def sub_staged(submodule_with_commits, tempdir_factory):
+def sub_staged(repo_with_commits, tempdir_factory):
path = git_dir(tempdir_factory)
with cwd(path):
+ open('bar', 'a+').close()
+ cmd_output('git', 'add', 'bar')
+ git_commit()
cmd_output(
- 'git', 'submodule', 'add', submodule_with_commits.path, 'sub',
+ 'git', 'submodule', 'add', repo_with_commits.path, 'sub',
)
- checkout_submodule(submodule_with_commits.rev1)
+ checkout_submodule(repo_with_commits.rev1)
cmd_output('git', 'add', 'sub')
yield auto_namedtuple(
path=path,
sub_path=os.path.join(path, 'sub'),
- submodule=submodule_with_commits,
+ submodule=repo_with_commits,
)
@@ -242,6 +247,34 @@ def test_sub_something_unstaged(sub_staged, patch_dir):
_test_sub_state(sub_staged, 'rev2', 'AM')
+def test_submodule_does_not_discard_changes(sub_staged, patch_dir):
+ with open('bar', 'w') as f:
+ f.write('unstaged changes')
+
+ foo_path = os.path.join(sub_staged.sub_path, 'foo')
+ with open(foo_path, 'w') as f:
+ f.write('foo contents')
+
+ with staged_files_only(patch_dir):
+ with open('bar') as f:
+ assert f.read() == ''
+
+ with open(foo_path) as f:
+ assert f.read() == 'foo contents'
+
+ with open('bar') as f:
+ assert f.read() == 'unstaged changes'
+
+ with open(foo_path) as f:
+ assert f.read() == 'foo contents'
+
+
+def test_submodule_does_not_discard_changes_recurse(sub_staged, patch_dir):
+ cmd_output('git', 'config', 'submodule.recurse', '1', cwd=sub_staged.path)
+
+ test_submodule_does_not_discard_changes(sub_staged, patch_dir)
+
+
def test_stage_utf8_changes(foo_staged, patch_dir):
contents = '\u2603'
with open('foo', 'w', encoding='UTF-8') as foo_file: