diff options
Diffstat (limited to 'pre_commit')
-rw-r--r-- | pre_commit/commands/autoupdate.py | 12 | ||||
-rw-r--r-- | pre_commit/commands/hook_impl.py | 3 | ||||
-rw-r--r-- | pre_commit/commands/migrate_config.py | 4 | ||||
-rw-r--r-- | pre_commit/commands/run.py | 39 | ||||
-rw-r--r-- | pre_commit/constants.py | 4 | ||||
-rw-r--r-- | pre_commit/git.py | 3 | ||||
-rw-r--r-- | pre_commit/languages/all.py | 4 | ||||
-rw-r--r-- | pre_commit/languages/helpers.py | 4 | ||||
-rw-r--r-- | pre_commit/languages/node.py | 2 | ||||
-rw-r--r-- | pre_commit/languages/python.py | 147 | ||||
-rw-r--r-- | pre_commit/languages/python_venv.py | 46 | ||||
-rw-r--r-- | pre_commit/languages/ruby.py | 2 | ||||
-rw-r--r-- | pre_commit/main.py | 2 | ||||
-rw-r--r-- | pre_commit/meta_hooks/check_hooks_apply.py | 7 | ||||
-rw-r--r-- | pre_commit/meta_hooks/check_useless_excludes.py | 7 | ||||
-rw-r--r-- | pre_commit/resources/empty_template_go.mod | 0 | ||||
-rw-r--r-- | pre_commit/staged_files_only.py | 9 | ||||
-rw-r--r-- | pre_commit/store.py | 9 | ||||
-rw-r--r-- | pre_commit/util.py | 3 |
19 files changed, 158 insertions, 149 deletions
diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 8c9fdd7..87f6d53 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -84,7 +84,9 @@ def _check_hooks_still_exist_at_rev( ) -REV_LINE_RE = re.compile(r'^(\s+)rev:(\s*)([^\s#]+)(.*)(\r?\n)$', re.DOTALL) +REV_LINE_RE = re.compile( + r'^(\s+)rev:(\s*)([\'"]?)([^\s#]+)(.*)(\r?\n)$', re.DOTALL, +) def _original_lines( @@ -116,15 +118,15 @@ def _write_new_config(path: str, rev_infos: List[Optional[RevInfo]]) -> None: continue match = REV_LINE_RE.match(lines[idx]) assert match is not None - new_rev_s = yaml_dump({'rev': rev_info.rev}) + new_rev_s = yaml_dump({'rev': rev_info.rev}, default_style=match[3]) new_rev = new_rev_s.split(':', 1)[1].strip() if rev_info.frozen is not None: comment = f' # frozen: {rev_info.frozen}' - elif match[4].strip().startswith('# frozen:'): + elif match[5].strip().startswith('# frozen:'): comment = '' else: - comment = match[4] - lines[idx] = f'{match[1]}rev:{match[2]}{new_rev}{comment}{match[5]}' + comment = match[5] + lines[idx] = f'{match[1]}rev:{match[2]}{new_rev}{comment}{match[6]}' with open(path, 'w', newline='') as f: f.write(''.join(lines)) diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 4843fc7..d0e226f 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -150,6 +150,7 @@ def _pre_push_ns( _EXPECTED_ARG_LENGTH_BY_HOOK = { 'commit-msg': 1, 'post-checkout': 3, + 'post-commit': 0, 'pre-commit': 0, 'pre-merge-commit': 0, 'pre-push': 2, @@ -186,7 +187,7 @@ def _run_ns( return _pre_push_ns(color, args, stdin) elif hook_type in {'commit-msg', 'prepare-commit-msg'}: return _ns(hook_type, color, commit_msg_filename=args[0]) - elif hook_type in {'pre-merge-commit', 'pre-commit'}: + elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}: return _ns(hook_type, color) elif hook_type == 'post-checkout': return _ns( diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index d83b8e9..d580ff1 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -2,6 +2,7 @@ import re import yaml +from pre_commit.clientlib import load_config from pre_commit.util import yaml_load @@ -43,6 +44,9 @@ def _migrate_sha_to_rev(contents: str) -> str: def migrate_config(config_file: str, quiet: bool = False) -> int: + # ensure that the configuration is a valid pre-commit configuration + load_config(config_file) + with open(config_file) as f: orig_contents = contents = f.read() diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 8c8401c..567b7cd 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -72,13 +72,7 @@ def filter_by_include_exclude( class Classifier: - def __init__(self, filenames: Sequence[str]) -> None: - # 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 - # see #1173 - if os.altsep == '/' and os.sep == '\\': - filenames = [f.replace(os.sep, os.altsep) for f in filenames] + def __init__(self, filenames: Collection[str]) -> None: self.filenames = [f for f in filenames if os.path.lexists(f)] @functools.lru_cache(maxsize=None) @@ -105,6 +99,22 @@ class Classifier: names = self.by_types(names, hook.types, hook.exclude_types) return tuple(names) + @classmethod + def from_config( + cls, + filenames: Collection[str], + include: str, + exclude: str, + ) -> '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 + # see #1173 + if os.altsep == '/' and os.sep == '\\': + filenames = [f.replace(os.sep, os.altsep) for f in filenames] + filenames = filter_by_include_exclude(filenames, include, exclude) + return Classifier(filenames) + def _get_skips(environ: EnvironT) -> Set[str]: skips = environ.get('SKIP', '') @@ -221,7 +231,8 @@ def _compute_cols(hooks: Sequence[Hook]) -> int: def _all_filenames(args: argparse.Namespace) -> Collection[str]: - if args.hook_stage == 'post-checkout': # no files for post-checkout + # these hooks do not operate on files + if args.hook_stage in {'post-checkout', 'post-commit'}: return () elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}: return (args.commit_msg_filename,) @@ -246,10 +257,9 @@ def _run_hooks( """Actually run the hooks.""" skips = _get_skips(environ) cols = _compute_cols(hooks) - filenames = filter_by_include_exclude( + classifier = Classifier.from_config( _all_filenames(args), config['files'], config['exclude'], ) - classifier = Classifier(filenames) retval = 0 for hook in hooks: retval |= _run_single_hook( @@ -323,6 +333,12 @@ def run( f'`--hook-stage {args.hook_stage}`', ) return 1 + # prevent recursive post-checkout hooks (#1418) + if ( + args.hook_stage == 'post-checkout' and + environ.get('_PRE_COMMIT_SKIP_POST_CHECKOUT') + ): + return 0 # Expose from-ref / to-ref as environment variables for hooks to consume if args.from_ref and args.to_ref: @@ -340,6 +356,9 @@ def run( if args.checkout_type: environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type + # Set pre_commit flag + environ['PRE_COMMIT'] = '1' + with contextlib.ExitStack() as exit_stack: if stash: exit_stack.enter_context(staged_files_only(store.directory)) diff --git a/pre_commit/constants.py b/pre_commit/constants.py index e2b8e3a..5150fdc 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -17,8 +17,8 @@ VERSION = importlib_metadata.version('pre_commit') # `manual` is not invoked by any installed git hook. See #719 STAGES = ( - 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', 'manual', - 'post-checkout', 'push', + 'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', + 'post-commit', 'manual', 'post-checkout', 'push', ) DEFAULT = 'default' diff --git a/pre_commit/git.py b/pre_commit/git.py index 7e757f2..576bef8 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -158,7 +158,8 @@ def init_repo(path: str, remote: str) -> None: remote = os.path.abspath(remote) env = no_git_env() - cmd_output_b('git', 'init', path, env=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) diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index 8f4ffa8..5609631 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -14,7 +14,6 @@ from pre_commit.languages import node from pre_commit.languages import perl from pre_commit.languages import pygrep from pre_commit.languages import python -from pre_commit.languages import python_venv from pre_commit.languages import ruby from pre_commit.languages import rust from pre_commit.languages import script @@ -49,7 +48,6 @@ languages = { 'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 - 'python_venv': Language(name='python_venv', ENVIRONMENT_DIR=python_venv.ENVIRONMENT_DIR, get_default_version=python_venv.get_default_version, healthy=python_venv.healthy, install_environment=python_venv.install_environment, run_hook=python_venv.run_hook), # noqa: E501 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 @@ -57,4 +55,6 @@ languages = { 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 # END GENERATED } +# TODO: fully deprecate `python_venv` +languages['python_venv'] = languages['python'] all_languages = sorted(languages) diff --git a/pre_commit/languages/helpers.py b/pre_commit/languages/helpers.py index b5c95e5..01c65ab 100644 --- a/pre_commit/languages/helpers.py +++ b/pre_commit/languages/helpers.py @@ -18,7 +18,7 @@ from pre_commit.xargs import xargs if TYPE_CHECKING: from typing import NoReturn -FIXED_RANDOM_SEED = 1542676186 +FIXED_RANDOM_SEED = 1542676187 def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...]) -> None: @@ -92,7 +92,7 @@ def _shuffled(seq: Sequence[str]) -> List[str]: fixed_random.seed(FIXED_RANDOM_SEED, version=1) seq = list(seq) - random.shuffle(seq, random=fixed_random.random) + fixed_random.shuffle(seq) return seq diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 9b636d3..26f4919 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -79,7 +79,7 @@ def install_environment( # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath if sys.platform == 'win32': # pragma: no cover - envdir = f'\\\\?\\{os.path.normpath(envdir)}' + envdir = fr'\\?\{os.path.normpath(envdir)}' with clean_path_on_failure(envdir): cmd = [ sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir, diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 85d8281..6f7c900 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -2,8 +2,7 @@ import contextlib import functools import os import sys -from typing import Callable -from typing import ContextManager +from typing import Dict from typing import Generator from typing import Optional from typing import Sequence @@ -26,6 +25,28 @@ from pre_commit.util import cmd_output_b ENVIRONMENT_DIR = 'py_env' +@functools.lru_cache(maxsize=None) +def _version_info(exe: str) -> str: + prog = 'import sys;print(".".join(str(p) for p in sys.version_info))' + try: + return cmd_output(exe, '-S', '-c', prog)[1].strip() + except CalledProcessError: + return f'<<error retrieving version from {exe}>>' + + +def _read_pyvenv_cfg(filename: str) -> Dict[str, str]: + ret = {} + with open(filename) as f: + for line in f: + try: + k, v = line.split('=') + except ValueError: # blank line / comment / etc. + continue + else: + ret[k.strip()] = v.strip() + return ret + + def bin_dir(venv: str) -> str: """On windows there's a different directory for the virtualenv""" bin_part = 'Scripts' if os.name == 'nt' else 'bin' @@ -34,6 +55,7 @@ def bin_dir(venv: str) -> str: def get_env_patch(venv: str) -> PatchesT: return ( + ('PIP_DISABLE_PIP_VERSION_CHECK', '1'), ('PYTHONHOME', UNSET), ('VIRTUAL_ENV', venv), ('PATH', (bin_dir(venv), os.pathsep, Var('PATH'))), @@ -45,9 +67,10 @@ def _find_by_py_launcher( ) -> Optional[str]: # pragma: no cover (windows only) if version.startswith('python'): num = version[len('python'):] + cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)') + env = dict(os.environ, PYTHONIOENCODING='UTF-8') try: - cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)') - return cmd_output(*cmd)[1].strip() + return cmd_output(*cmd, env=env)[1].strip() except CalledProcessError: pass return None @@ -115,6 +138,9 @@ def _sys_executable_matches(version: str) -> bool: def norm_version(version: str) -> str: + if version == C.DEFAULT: + return os.path.realpath(sys.executable) + # first see if our current executable is appropriate if _sys_executable_matches(version): return sys.executable @@ -139,70 +165,59 @@ def norm_version(version: str) -> str: return os.path.expanduser(version) -def py_interface( - _dir: str, - _make_venv: Callable[[str, str], None], -) -> Tuple[ - Callable[[Prefix, str], ContextManager[None]], - Callable[[Prefix, str], bool], - Callable[[Hook, Sequence[str], bool], Tuple[int, bytes]], - Callable[[Prefix, str, Sequence[str]], None], -]: - @contextlib.contextmanager - def in_env( - prefix: Prefix, - language_version: str, - ) -> Generator[None, None, None]: - envdir = prefix.path(helpers.environment_dir(_dir, language_version)) - with envcontext(get_env_patch(envdir)): - yield - - def healthy(prefix: Prefix, language_version: str) -> bool: - envdir = helpers.environment_dir(_dir, language_version) - exe_name = 'python.exe' if sys.platform == 'win32' else 'python' - py_exe = prefix.path(bin_dir(envdir), exe_name) - with in_env(prefix, language_version): - retcode, _, _ = cmd_output_b( - py_exe, '-c', 'import ctypes, datetime, io, os, ssl, weakref', - cwd='/', - retcode=None, - ) - return retcode == 0 - - def run_hook( - hook: Hook, - file_args: Sequence[str], - color: bool, - ) -> Tuple[int, bytes]: - with in_env(hook.prefix, hook.language_version): - return helpers.run_xargs(hook, hook.cmd, file_args, color=color) - - def install_environment( - prefix: Prefix, - version: str, - additional_dependencies: Sequence[str], - ) -> None: - directory = helpers.environment_dir(_dir, version) - install = ('python', '-mpip', 'install', '.', *additional_dependencies) - - env_dir = prefix.path(directory) - with clean_path_on_failure(env_dir): - if version != C.DEFAULT: - python = norm_version(version) - else: - python = os.path.realpath(sys.executable) - _make_venv(env_dir, python) - with in_env(prefix, version): - helpers.run_setup_cmd(prefix, install) +@contextlib.contextmanager +def in_env( + prefix: Prefix, + language_version: str, +) -> Generator[None, None, None]: + directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) + envdir = prefix.path(directory) + with envcontext(get_env_patch(envdir)): + yield - return in_env, healthy, run_hook, install_environment +def healthy(prefix: Prefix, language_version: str) -> bool: + directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version) + envdir = prefix.path(directory) + pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg') -def make_venv(envdir: str, python: str) -> None: - env = dict(os.environ, VIRTUALENV_NO_DOWNLOAD='1') - cmd = (sys.executable, '-mvirtualenv', envdir, '-p', python) - cmd_output_b(*cmd, env=env, cwd='/') + # created with "old" virtualenv + if not os.path.exists(pyvenv_cfg): + return False + + exe_name = 'python.exe' if sys.platform == 'win32' else 'python' + py_exe = prefix.path(bin_dir(envdir), exe_name) + cfg = _read_pyvenv_cfg(pyvenv_cfg) + + return ( + 'version_info' in cfg and + _version_info(py_exe) == cfg['version_info'] and ( + 'base-executable' not in cfg or + _version_info(cfg['base-executable']) == cfg['version_info'] + ) + ) -_interface = py_interface(ENVIRONMENT_DIR, make_venv) -in_env, healthy, run_hook, install_environment = _interface +def install_environment( + prefix: Prefix, + version: str, + additional_dependencies: Sequence[str], +) -> None: + envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version)) + python = norm_version(version) + venv_cmd = (sys.executable, '-mvirtualenv', envdir, '-p', python) + install_cmd = ('python', '-mpip', 'install', '.', *additional_dependencies) + + with clean_path_on_failure(envdir): + cmd_output_b(*venv_cmd, cwd='/') + with in_env(prefix, version): + helpers.run_setup_cmd(prefix, install_cmd) + + +def run_hook( + hook: Hook, + file_args: Sequence[str], + color: bool, +) -> Tuple[int, bytes]: + with in_env(hook.prefix, hook.language_version): + return helpers.run_xargs(hook, hook.cmd, file_args, color=color) diff --git a/pre_commit/languages/python_venv.py b/pre_commit/languages/python_venv.py deleted file mode 100644 index 5404c8b..0000000 --- a/pre_commit/languages/python_venv.py +++ /dev/null @@ -1,46 +0,0 @@ -import os.path - -from pre_commit.languages import python -from pre_commit.util import CalledProcessError -from pre_commit.util import cmd_output -from pre_commit.util import cmd_output_b - -ENVIRONMENT_DIR = 'py_venv' -get_default_version = python.get_default_version - - -def orig_py_exe(exe: str) -> str: # pragma: no cover (platform specific) - """A -mvenv virtualenv made from a -mvirtualenv virtualenv installs - packages to the incorrect location. Attempt to find the _original_ exe - and invoke `-mvenv` from there. - - See: - - https://github.com/pre-commit/pre-commit/issues/755 - - https://github.com/pypa/virtualenv/issues/1095 - - https://bugs.python.org/issue30811 - """ - try: - prefix_script = 'import sys; print(sys.real_prefix)' - _, prefix, _ = cmd_output(exe, '-c', prefix_script) - prefix = prefix.strip() - except CalledProcessError: - # not created from -mvirtualenv - return exe - - if os.name == 'nt': - expected = os.path.join(prefix, 'python.exe') - else: - expected = os.path.join(prefix, 'bin', os.path.basename(exe)) - - if os.path.exists(expected): - return expected - else: - return exe - - -def make_venv(envdir: str, python: str) -> None: - cmd_output_b(orig_py_exe(python), '-mvenv', envdir, cwd='/') - - -_interface = python.py_interface(ENVIRONMENT_DIR, make_venv) -in_env, healthy, run_hook, install_environment = _interface diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 61241f8..fe524ec 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -9,6 +9,7 @@ from typing import Tuple import pre_commit.constants as C from pre_commit.envcontext import envcontext from pre_commit.envcontext import PatchesT +from pre_commit.envcontext import UNSET from pre_commit.envcontext import Var from pre_commit.hook import Hook from pre_commit.languages import helpers @@ -28,6 +29,7 @@ def get_env_patch( ) -> PatchesT: # pragma: win32 no cover patches: PatchesT = ( ('GEM_HOME', os.path.join(venv, 'gems')), + ('GEM_PATH', UNSET), ('RBENV_ROOT', venv), ('BUNDLE_IGNORE_CONFIG', '1'), ( diff --git a/pre_commit/main.py b/pre_commit/main.py index 790b347..874eb53 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -79,7 +79,7 @@ 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-checkout', + 'prepare-commit-msg', 'commit-msg', 'post-commit', 'post-checkout', ), action=AppendReplaceDefault, default=['pre-commit'], diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py index d0244a9..a1e9352 100644 --- a/pre_commit/meta_hooks/check_hooks_apply.py +++ b/pre_commit/meta_hooks/check_hooks_apply.py @@ -11,10 +11,13 @@ from pre_commit.store import Store def check_all_hooks_match_files(config_file: str) -> int: - classifier = Classifier(git.get_all_files()) + config = load_config(config_file) + classifier = Classifier.from_config( + git.get_all_files(), config['files'], config['exclude'], + ) retv = 0 - for hook in all_hooks(load_config(config_file), Store()): + for hook in all_hooks(config, Store()): if hook.always_run or hook.language == 'fail': continue elif not classifier.filenames_for_hook(hook): diff --git a/pre_commit/meta_hooks/check_useless_excludes.py b/pre_commit/meta_hooks/check_useless_excludes.py index 30b8d81..db6865c 100644 --- a/pre_commit/meta_hooks/check_useless_excludes.py +++ b/pre_commit/meta_hooks/check_useless_excludes.py @@ -28,11 +28,14 @@ def exclude_matches_any( def check_useless_excludes(config_file: str) -> int: config = load_config(config_file) - classifier = Classifier(git.get_all_files()) + filenames = git.get_all_files() + classifier = Classifier.from_config( + filenames, config['files'], config['exclude'], + ) retv = 0 exclude = config['exclude'] - if not exclude_matches_any(classifier.filenames, '', exclude): + if not exclude_matches_any(filenames, '', exclude): print( f'The global exclude pattern {exclude!r} does not match any files', ) diff --git a/pre_commit/resources/empty_template_go.mod b/pre_commit/resources/empty_template_go.mod new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pre_commit/resources/empty_template_go.mod diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index 09d323d..6179301 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -56,8 +56,10 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: with open(patch_filename, 'wb') as patch_file: patch_file.write(diff_stdout_binary) - # Clear the working directory of unstaged changes - cmd_output_b('git', 'checkout', '--', '.') + # 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) + try: yield finally: @@ -72,8 +74,9 @@ 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', '--', '.') + cmd_output_b('git', 'checkout', '--', '.', env=no_checkout_env) _git_apply(patch_filename) + logger.info(f'Restored changes from {patch_filename}.') else: # There weren't any staged files so we don't need to do anything diff --git a/pre_commit/store.py b/pre_commit/store.py index 760b37a..6d8c40a 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -30,10 +30,11 @@ def _get_default_directory() -> str: `Store.get_default_directory` can be mocked in tests and `_get_default_directory` can be tested. """ - return os.environ.get('PRE_COMMIT_HOME') or os.path.join( + ret = os.environ.get('PRE_COMMIT_HOME') or os.path.join( os.environ.get('XDG_CACHE_HOME') or os.path.expanduser('~/.cache'), 'pre-commit', ) + return os.path.realpath(ret) class Store: @@ -182,9 +183,9 @@ class Store: return self._new_repo(repo, ref, deps, clone_strategy) LOCAL_RESOURCES = ( - 'Cargo.toml', 'main.go', 'main.rs', '.npmignore', 'package.json', - 'pre_commit_dummy_package.gemspec', 'setup.py', 'environment.yml', - 'Makefile.PL', + 'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore', + 'package.json', 'pre_commit_dummy_package.gemspec', 'setup.py', + 'environment.yml', 'Makefile.PL', ) def make_local(self, deps: Sequence[str]) -> str: diff --git a/pre_commit/util.py b/pre_commit/util.py index 2db579a..0338b37 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -36,10 +36,11 @@ yaml_load = functools.partial(yaml.load, Loader=Loader) Dumper = getattr(yaml, 'CSafeDumper', yaml.SafeDumper) -def yaml_dump(o: Any) -> str: +def yaml_dump(o: Any, **kwargs: Any) -> str: # when python/mypy#1484 is solved, this can be `functools.partial` return yaml.dump( o, Dumper=Dumper, default_flow_style=False, indent=4, sort_keys=False, + **kwargs, ) |