summaryrefslogtreecommitdiffstats
path: root/pre_commit
diff options
context:
space:
mode:
Diffstat (limited to '')
-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
16 files changed, 90 insertions, 67 deletions
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