Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f56b75d

Browse files
authored
Merge pull request pre-commit#2998 from pre-commit/short-circuit
Short-circuit `check-hooks-apply` and `check-useless-excludes` hooks
2 parents 0845e4e + 5d692d7 commit f56b75d

4 files changed

Lines changed: 42 additions & 40 deletions

File tree

pre_commit/commands/run.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import time
1111
import unicodedata
1212
from typing import Any
13-
from typing import Collection
13+
from typing import Generator
14+
from typing import Iterable
1415
from typing import MutableMapping
1516
from typing import Sequence
1617

@@ -57,20 +58,20 @@ def _full_msg(
5758

5859

5960
def filter_by_include_exclude(
60-
names: Collection[str],
61+
names: Iterable[str],
6162
include: str,
6263
exclude: str,
63-
) -> list[str]:
64+
) -> Generator[str, None, None]:
6465
include_re, exclude_re = re.compile(include), re.compile(exclude)
65-
return [
66+
return (
6667
filename for filename in names
6768
if include_re.search(filename)
6869
if not exclude_re.search(filename)
69-
]
70+
)
7071

7172

7273
class Classifier:
73-
def __init__(self, filenames: Collection[str]) -> None:
74+
def __init__(self, filenames: Iterable[str]) -> None:
7475
self.filenames = [f for f in filenames if os.path.lexists(f)]
7576

7677
@functools.lru_cache(maxsize=None)
@@ -79,40 +80,39 @@ def _types_for_file(self, filename: str) -> set[str]:
7980

8081
def by_types(
8182
self,
82-
names: Sequence[str],
83-
types: Collection[str],
84-
types_or: Collection[str],
85-
exclude_types: Collection[str],
86-
) -> list[str]:
83+
names: Iterable[str],
84+
types: Iterable[str],
85+
types_or: Iterable[str],
86+
exclude_types: Iterable[str],
87+
) -> Generator[str, None, None]:
8788
types = frozenset(types)
8889
types_or = frozenset(types_or)
8990
exclude_types = frozenset(exclude_types)
90-
ret = []
9191
for filename in names:
9292
tags = self._types_for_file(filename)
9393
if (
9494
tags >= types and
9595
(not types_or or tags & types_or) and
9696
not tags & exclude_types
9797
):
98-
ret.append(filename)
99-
return ret
100-
101-
def filenames_for_hook(self, hook: Hook) -> tuple[str, ...]:
102-
names = self.filenames
103-
names = filter_by_include_exclude(names, hook.files, hook.exclude)
104-
names = self.by_types(
105-
names,
98+
yield filename
99+
100+
def filenames_for_hook(self, hook: Hook) -> Generator[str, None, None]:
101+
return self.by_types(
102+
filter_by_include_exclude(
103+
self.filenames,
104+
hook.files,
105+
hook.exclude,
106+
),
106107
hook.types,
107108
hook.types_or,
108109
hook.exclude_types,
109110
)
110-
return tuple(names)
111111

112112
@classmethod
113113
def from_config(
114114
cls,
115-
filenames: Collection[str],
115+
filenames: Iterable[str],
116116
include: str,
117117
exclude: str,
118118
) -> Classifier:
@@ -121,7 +121,7 @@ def from_config(
121121
# this also makes improperly quoted shell-based hooks work better
122122
# see #1173
123123
if os.altsep == '/' and os.sep == '\\':
124-
filenames = [f.replace(os.sep, os.altsep) for f in filenames]
124+
filenames = (f.replace(os.sep, os.altsep) for f in filenames)
125125
filenames = filter_by_include_exclude(filenames, include, exclude)
126126
return Classifier(filenames)
127127

@@ -148,7 +148,7 @@ def _run_single_hook(
148148
verbose: bool,
149149
use_color: bool,
150150
) -> tuple[bool, bytes]:
151-
filenames = classifier.filenames_for_hook(hook)
151+
filenames = tuple(classifier.filenames_for_hook(hook))
152152

153153
if hook.id in skips or hook.alias in skips:
154154
output.write(
@@ -250,7 +250,7 @@ def _compute_cols(hooks: Sequence[Hook]) -> int:
250250
return max(cols, 80)
251251

252252

253-
def _all_filenames(args: argparse.Namespace) -> Collection[str]:
253+
def _all_filenames(args: argparse.Namespace) -> Iterable[str]:
254254
# these hooks do not operate on files
255255
if args.hook_stage in {
256256
'post-checkout', 'post-commit', 'post-merge', 'post-rewrite',

pre_commit/meta_hooks/check_hooks_apply.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def check_all_hooks_match_files(config_file: str) -> int:
2121
for hook in all_hooks(config, Store()):
2222
if hook.always_run or hook.language == 'fail':
2323
continue
24-
elif not classifier.filenames_for_hook(hook):
24+
elif not any(classifier.filenames_for_hook(hook)):
2525
print(f'{hook.id} does not apply to this repository')
2626
retv = 1
2727

pre_commit/meta_hooks/check_useless_excludes.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import argparse
44
import re
5+
from typing import Iterable
56
from typing import Sequence
67

78
from cfgv import apply_defaults
@@ -14,7 +15,7 @@
1415

1516

1617
def exclude_matches_any(
17-
filenames: Sequence[str],
18+
filenames: Iterable[str],
1819
include: str,
1920
exclude: str,
2021
) -> bool:
@@ -50,11 +51,12 @@ def check_useless_excludes(config_file: str) -> int:
5051
# Not actually a manifest dict, but this more accurately reflects
5152
# the defaults applied during runtime
5253
hook = apply_defaults(hook, MANIFEST_HOOK_DICT)
53-
names = classifier.filenames
54-
types = hook['types']
55-
types_or = hook['types_or']
56-
exclude_types = hook['exclude_types']
57-
names = classifier.by_types(names, types, types_or, exclude_types)
54+
names = classifier.by_types(
55+
classifier.filenames,
56+
hook['types'],
57+
hook['types_or'],
58+
hook['exclude_types'],
59+
)
5860
include, exclude = hook['files'], hook['exclude']
5961
if not exclude_matches_any(names, include, exclude):
6062
print(

tests/commands/run_test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,8 +1127,8 @@ def test_classifier_empty_types_or(tmpdir):
11271127
types_or=[],
11281128
exclude_types=[],
11291129
)
1130-
assert for_symlink == ['foo']
1131-
assert for_file == ['bar']
1130+
assert tuple(for_symlink) == ('foo',)
1131+
assert tuple(for_file) == ('bar',)
11321132

11331133

11341134
@pytest.fixture
@@ -1142,33 +1142,33 @@ def some_filenames():
11421142

11431143
def test_include_exclude_base_case(some_filenames):
11441144
ret = filter_by_include_exclude(some_filenames, '', '^$')
1145-
assert ret == [
1145+
assert tuple(ret) == (
11461146
'.pre-commit-hooks.yaml',
11471147
'pre_commit/git.py',
11481148
'pre_commit/main.py',
1149-
]
1149+
)
11501150

11511151

11521152
def test_matches_broken_symlink(tmpdir):
11531153
with tmpdir.as_cwd():
11541154
os.symlink('does-not-exist', 'link')
11551155
ret = filter_by_include_exclude({'link'}, '', '^$')
1156-
assert ret == ['link']
1156+
assert tuple(ret) == ('link',)
11571157

11581158

11591159
def test_include_exclude_total_match(some_filenames):
11601160
ret = filter_by_include_exclude(some_filenames, r'^.*\.py$', '^$')
1161-
assert ret == ['pre_commit/git.py', 'pre_commit/main.py']
1161+
assert tuple(ret) == ('pre_commit/git.py', 'pre_commit/main.py')
11621162

11631163

11641164
def test_include_exclude_does_search_instead_of_match(some_filenames):
11651165
ret = filter_by_include_exclude(some_filenames, r'\.yaml$', '^$')
1166-
assert ret == ['.pre-commit-hooks.yaml']
1166+
assert tuple(ret) == ('.pre-commit-hooks.yaml',)
11671167

11681168

11691169
def test_include_exclude_exclude_removes_files(some_filenames):
11701170
ret = filter_by_include_exclude(some_filenames, '', r'\.py$')
1171-
assert ret == ['.pre-commit-hooks.yaml']
1171+
assert tuple(ret) == ('.pre-commit-hooks.yaml',)
11721172

11731173

11741174
def test_args_hook_only(cap_out, store, repo_with_passing_hook):

0 commit comments

Comments
 (0)