diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 9f41bf4b..78d0ec44 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -110,6 +110,7 @@ MANIFEST_HOOK_DICT = cfgv.Map( cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('exclude', check_string_regex, '^$'), + cfgv.Optional('exclude_file_path', check_string_regex, '^$'), cfgv.Optional('types', cfgv.check_array(check_type_tag), ['file']), cfgv.Optional('types_or', cfgv.check_array(check_type_tag), []), cfgv.Optional('exclude_types', cfgv.check_array(check_type_tag), []), @@ -357,6 +358,7 @@ CONFIG_SCHEMA = cfgv.Map( StagesMigration('default_stages', STAGES), cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('exclude', check_string_regex, '^$'), + cfgv.Optional('exclude_file_path', check_string_regex, '^$'), cfgv.Optional('fail_fast', cfgv.check_bool, False), cfgv.Optional( 'minimum_pre_commit_version', diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 076f16d8..621ff6a3 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -63,6 +63,7 @@ def filter_by_include_exclude( exclude: str, ) -> Generator[str, None, None]: include_re, exclude_re = re.compile(include), re.compile(exclude) + return ( filename for filename in names if include_re.search(filename) @@ -98,11 +99,23 @@ class Classifier: yield filename def filenames_for_hook(self, hook: Hook) -> Generator[str, None, None]: + exclude = hook.exclude or '$^' + file_exclude = '' + if hook.exclude_file_path is not None: + if os.path.isfile(hook.exclude_file_path): + with open(hook.exclude_file_path) as exclude_file: + file_exclude = '|'.join( + line.strip() for line in exclude_file.readlines() + ) or '$^' + + if file_exclude: + exclude = f'{exclude}|{file_exclude}' + return self.by_types( filter_by_include_exclude( self.filenames, hook.files, - hook.exclude, + exclude, ), hook.types, hook.types_or, @@ -115,6 +128,7 @@ class Classifier: filenames: Iterable[str], include: str, exclude: str, + exclude_file_path: str, ) -> Classifier: # on windows we normalize all filenames to use forward slashes # this makes it easier to filter using the `files:` regex @@ -122,6 +136,15 @@ class Classifier: # see #1173 if os.altsep == '/' and os.sep == '\\': filenames = (f.replace(os.sep, os.altsep) for f in filenames) + file_exclude = '' + if exclude_file_path is not None: + if os.path.isfile(exclude_file_path): + with open(exclude_file_path) as exclude_file: + file_exclude = '|'.join( + line.strip() for line in exclude_file.readlines() + ) or '$^' + if file_exclude: + exclude = f'{exclude}|{file_exclude}' filenames = filter_by_include_exclude(filenames, include, exclude) return Classifier(filenames) @@ -288,7 +311,7 @@ def _run_hooks( """Actually run the hooks.""" cols = _compute_cols(hooks) classifier = Classifier.from_config( - _all_filenames(args), config['files'], config['exclude'], + _all_filenames(args), config['files'], config['exclude'], config['exclude_file_path'], ) retval = 0 prior_diff = _get_diff() diff --git a/pre_commit/hook.py b/pre_commit/hook.py index 309cd5be..38143439 100644 --- a/pre_commit/hook.py +++ b/pre_commit/hook.py @@ -20,6 +20,7 @@ class Hook(NamedTuple): alias: str files: str exclude: str + exclude_file_path: str types: Sequence[str] types_or: Sequence[str] exclude_types: Sequence[str] diff --git a/pre_commit/meta_hooks/check_hooks_apply.py b/pre_commit/meta_hooks/check_hooks_apply.py index 84c142b4..52683196 100644 --- a/pre_commit/meta_hooks/check_hooks_apply.py +++ b/pre_commit/meta_hooks/check_hooks_apply.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import os from collections.abc import Sequence import pre_commit.constants as C @@ -13,6 +14,12 @@ from pre_commit.store import Store def check_all_hooks_match_files(config_file: str) -> int: config = load_config(config_file) + exclude_file_path = config['exclude_file_path'] + if exclude_file_path is not None: + if os.path.isfile(exclude_file_path): + with open(exclude_file_path, 'r') as f: + config['exclude'] = f.read().splitlines() + classifier = Classifier.from_config( git.get_all_files(), config['files'], config['exclude'], )