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

Skip to content

Commit 88c676a

Browse files
committed
Add support for meta hooks
1 parent 39d5205 commit 88c676a

6 files changed

Lines changed: 130 additions & 1 deletion

File tree

pre_commit/clientlib.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ def validate_manifest_main(argv=None):
9898

9999

100100
_LOCAL_SENTINEL = 'local'
101+
_META_SENTINEL = 'meta'
102+
101103
CONFIG_HOOK_DICT = schema.Map(
102104
'Hook', 'id',
103105

@@ -121,7 +123,8 @@ def validate_manifest_main(argv=None):
121123

122124
schema.Conditional(
123125
'sha', schema.check_string,
124-
condition_key='repo', condition_value=schema.Not(_LOCAL_SENTINEL),
126+
condition_key='repo',
127+
condition_value=schema.NotIn((_LOCAL_SENTINEL, _META_SENTINEL)),
125128
ensure_absent=True,
126129
),
127130
)
@@ -138,6 +141,10 @@ def is_local_repo(repo_entry):
138141
return repo_entry['repo'] == _LOCAL_SENTINEL
139142

140143

144+
def is_meta_repo(repo_entry):
145+
return repo_entry['repo'] == _META_SENTINEL
146+
147+
141148
class InvalidConfigError(FatalError):
142149
pass
143150

pre_commit/repository.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import os
77
import shutil
8+
import sys
89
from collections import defaultdict
910

1011
import pkg_resources
@@ -14,6 +15,7 @@
1415
from pre_commit import five
1516
from pre_commit import git
1617
from pre_commit.clientlib import is_local_repo
18+
from pre_commit.clientlib import is_meta_repo
1719
from pre_commit.clientlib import MANIFEST_HOOK_DICT
1820
from pre_commit.languages.all import languages
1921
from pre_commit.languages.helpers import environment_dir
@@ -128,6 +130,8 @@ def __init__(self, repo_config, store):
128130
def create(cls, config, store):
129131
if is_local_repo(config):
130132
return LocalRepository(config, store)
133+
elif is_meta_repo(config):
134+
return MetaRepository(config, store)
131135
else:
132136
return cls(config, store)
133137

@@ -242,6 +246,45 @@ def _venvs(self):
242246
return tuple(ret)
243247

244248

249+
class MetaRepository(LocalRepository):
250+
meta_hooks = {
251+
'test-hook': {
252+
'name': 'Test Hook',
253+
'files': '',
254+
'language': 'system',
255+
'entry': 'echo "Hello World!"',
256+
'always_run': True,
257+
},
258+
}
259+
260+
@cached_property
261+
def hooks(self):
262+
for hook in self.repo_config['hooks']:
263+
if hook['id'] not in self.meta_hooks:
264+
logger.error(
265+
'`{}` is not a valid meta hook. '
266+
'Typo? Perhaps it is introduced in a newer version? '
267+
'Often `pre-commit autoupdate` fixes this.'.format(
268+
hook['id'],
269+
),
270+
)
271+
exit(1)
272+
273+
return tuple(
274+
(
275+
hook['id'],
276+
apply_defaults(
277+
validate(
278+
dict(self.meta_hooks[hook['id']], **hook),
279+
MANIFEST_HOOK_DICT,
280+
),
281+
MANIFEST_HOOK_DICT,
282+
),
283+
)
284+
for hook in self.repo_config['hooks']
285+
)
286+
287+
245288
class _UniqueList(list):
246289
def __init__(self):
247290
self._set = set()

pre_commit/schema.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ def _check_conditional(self, dct):
101101
if isinstance(self.condition_value, Not):
102102
op = 'is'
103103
cond_val = self.condition_value.val
104+
elif isinstance(self.condition_value, NotIn):
105+
op = 'is any of'
106+
cond_val = self.condition_value.values
104107
else:
105108
op = 'is not'
106109
cond_val = self.condition_value
@@ -206,6 +209,14 @@ def __eq__(self, other):
206209
return other is not MISSING and other != self.val
207210

208211

212+
class NotIn(object):
213+
def __init__(self, values):
214+
self.values = values
215+
216+
def __eq__(self, other):
217+
return other is not MISSING and other not in self.values
218+
219+
209220
def check_any(_):
210221
pass
211222

tests/commands/run_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,31 @@ def test_local_hook_fails(
645645
)
646646

647647

648+
def test_meta_hook_passes(
649+
cap_out, repo_with_passing_hook, mock_out_store_directory,
650+
):
651+
config = OrderedDict((
652+
('repo', 'meta'),
653+
(
654+
'hooks', (
655+
OrderedDict((
656+
('id', 'test-hook'),
657+
)),
658+
),
659+
),
660+
))
661+
add_config_to_repo(repo_with_passing_hook, config)
662+
663+
_test_run(
664+
cap_out,
665+
repo_with_passing_hook,
666+
opts={'verbose': True},
667+
expected_outputs=[b'Hello World!'],
668+
expected_ret=0,
669+
stage=False,
670+
)
671+
672+
648673
@pytest.yield_fixture
649674
def modified_config_repo(repo_with_passing_hook):
650675
with modify_config(repo_with_passing_hook, commit=False) as config:

tests/repository_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,18 @@ def test_hook_id_not_present(tempdir_factory, store, fake_log_handler):
709709
)
710710

711711

712+
def test_meta_hook_not_present(store, fake_log_handler):
713+
config = {'repo': 'meta', 'hooks': [{'id': 'i-dont-exist'}]}
714+
repo = Repository.create(config, store)
715+
with pytest.raises(SystemExit):
716+
repo.require_installed()
717+
assert fake_log_handler.handle.call_args[0][0].msg == (
718+
'`i-dont-exist` is not a valid meta hook. '
719+
'Typo? Perhaps it is introduced in a newer version? '
720+
'Often `pre-commit autoupdate` fixes this.'
721+
)
722+
723+
712724
def test_too_new_version(tempdir_factory, store, fake_log_handler):
713725
path = make_repo(tempdir_factory, 'script_hooks_repo')
714726
with modify_manifest(path) as manifest:

tests/schema_test.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pre_commit.schema import Map
2020
from pre_commit.schema import MISSING
2121
from pre_commit.schema import Not
22+
from pre_commit.schema import NotIn
2223
from pre_commit.schema import Optional
2324
from pre_commit.schema import OptionalNoDefault
2425
from pre_commit.schema import remove_defaults
@@ -107,6 +108,16 @@ def test_not(val, expected):
107108
assert (compared == val) is expected
108109

109110

111+
@pytest.mark.parametrize(
112+
('values', 'expected'),
113+
(('bar', True), ('foo', False), (MISSING, False)),
114+
)
115+
def test_not_in(values, expected):
116+
compared = NotIn(('baz', 'foo'))
117+
assert (values == compared) is expected
118+
assert (compared == values) is expected
119+
120+
110121
trivial_array_schema = Array(Map('foo', 'id'))
111122

112123

@@ -196,6 +207,13 @@ def test_optional_key_missing(schema):
196207
condition_key='key', condition_value=Not(True), ensure_absent=True,
197208
),
198209
)
210+
map_conditional_absent_not_in = Map(
211+
'foo', 'key',
212+
Conditional(
213+
'key2', check_bool,
214+
condition_key='key', condition_value=NotIn((1, 2)), ensure_absent=True,
215+
),
216+
)
199217

200218

201219
@pytest.mark.parametrize('schema', (map_conditional, map_conditional_not))
@@ -248,6 +266,19 @@ def test_ensure_absent_conditional_not():
248266
)
249267

250268

269+
def test_ensure_absent_conditional_not_in():
270+
with pytest.raises(ValidationError) as excinfo:
271+
validate({'key': 1, 'key2': True}, map_conditional_absent_not_in)
272+
_assert_exception_trace(
273+
excinfo.value,
274+
(
275+
'At foo(key=1)',
276+
'Expected key2 to be absent when key is any of (1, 2), '
277+
'found key2: True',
278+
),
279+
)
280+
281+
251282
def test_no_error_conditional_absent():
252283
validate({}, map_conditional_absent)
253284
validate({}, map_conditional_absent_not)

0 commit comments

Comments
 (0)