Skip to content

Commit fc84567

Browse files
committed
Default local / meta through cfgv
1 parent 46ae88c commit fc84567

File tree

11 files changed

+109
-112
lines changed

11 files changed

+109
-112
lines changed

pre_commit/clientlib.py

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import argparse
55
import functools
6+
import pipes
7+
import sys
68

79
import cfgv
810
from aspy.yaml import ordered_load
@@ -88,8 +90,8 @@ def validate_manifest_main(argv=None):
8890
return ret
8991

9092

91-
_LOCAL_SENTINEL = 'local'
92-
_META_SENTINEL = 'meta'
93+
_LOCAL = 'local'
94+
_META = 'meta'
9395

9496

9597
class MigrateShaToRev(object):
@@ -98,12 +100,12 @@ def _cond(key):
98100
return cfgv.Conditional(
99101
key, cfgv.check_string,
100102
condition_key='repo',
101-
condition_value=cfgv.NotIn(_LOCAL_SENTINEL, _META_SENTINEL),
103+
condition_value=cfgv.NotIn(_LOCAL, _META),
102104
ensure_absent=True,
103105
)
104106

105107
def check(self, dct):
106-
if dct.get('repo') in {_LOCAL_SENTINEL, _META_SENTINEL}:
108+
if dct.get('repo') in {_LOCAL, _META}:
107109
self._cond('rev').check(dct)
108110
self._cond('sha').check(dct)
109111
elif 'sha' in dct and 'rev' in dct:
@@ -121,6 +123,61 @@ def remove_default(self, dct):
121123
pass
122124

123125

126+
def _entry(modname):
127+
"""the hook `entry` is passed through `shlex.split()` by the command
128+
runner, so to prevent issues with spaces and backslashes (on Windows)
129+
it must be quoted here.
130+
"""
131+
return '{} -m pre_commit.meta_hooks.{}'.format(
132+
pipes.quote(sys.executable), modname,
133+
)
134+
135+
136+
_meta = (
137+
(
138+
'check-hooks-apply', (
139+
('name', 'Check hooks apply to the repository'),
140+
('files', C.CONFIG_FILE),
141+
('entry', _entry('check_hooks_apply')),
142+
),
143+
),
144+
(
145+
'check-useless-excludes', (
146+
('name', 'Check for useless excludes'),
147+
('files', C.CONFIG_FILE),
148+
('entry', _entry('check_useless_excludes')),
149+
),
150+
),
151+
(
152+
'identity', (
153+
('name', 'identity'),
154+
('verbose', True),
155+
('entry', _entry('identity')),
156+
),
157+
),
158+
)
159+
160+
META_HOOK_DICT = cfgv.Map(
161+
'Hook', 'id',
162+
*([
163+
cfgv.Required('id', cfgv.check_string),
164+
cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))),
165+
# language must be system
166+
cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'),
167+
] + [
168+
# default to the hook definition for the meta hooks
169+
cfgv.ConditionalOptional(key, cfgv.check_any, value, 'id', hook_id)
170+
for hook_id, values in _meta
171+
for key, value in values
172+
] + [
173+
# default to the "manifest" parsing
174+
cfgv.OptionalNoDefault(item.key, item.check_fn)
175+
# these will always be defaulted above
176+
if item.key in {'name', 'language', 'entry'} else
177+
item
178+
for item in MANIFEST_HOOK_DICT.items
179+
])
180+
)
124181
CONFIG_HOOK_DICT = cfgv.Map(
125182
'Hook', 'id',
126183

@@ -140,7 +197,19 @@ def remove_default(self, dct):
140197
'Repository', 'repo',
141198

142199
cfgv.Required('repo', cfgv.check_string),
143-
cfgv.RequiredRecurse('hooks', cfgv.Array(CONFIG_HOOK_DICT)),
200+
201+
cfgv.ConditionalRecurse(
202+
'hooks', cfgv.Array(CONFIG_HOOK_DICT),
203+
'repo', cfgv.NotIn(_LOCAL, _META),
204+
),
205+
cfgv.ConditionalRecurse(
206+
'hooks', cfgv.Array(MANIFEST_HOOK_DICT),
207+
'repo', _LOCAL,
208+
),
209+
cfgv.ConditionalRecurse(
210+
'hooks', cfgv.Array(META_HOOK_DICT),
211+
'repo', _META,
212+
),
144213

145214
MigrateShaToRev(),
146215
)
@@ -154,11 +223,11 @@ def remove_default(self, dct):
154223

155224

156225
def is_local_repo(repo_entry):
157-
return repo_entry['repo'] == _LOCAL_SENTINEL
226+
return repo_entry['repo'] == _LOCAL
158227

159228

160229
def is_meta_repo(repo_entry):
161-
return repo_entry['repo'] == _META_SENTINEL
230+
return repo_entry['repo'] == _META
162231

163232

164233
class InvalidConfigError(FatalError):

pre_commit/meta_hooks/check_hooks_apply.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,9 @@
55
from pre_commit.clientlib import load_config
66
from pre_commit.commands.run import _filter_by_include_exclude
77
from pre_commit.commands.run import _filter_by_types
8-
from pre_commit.meta_hooks.helpers import make_meta_entry
98
from pre_commit.repository import all_hooks
109
from pre_commit.store import Store
1110

12-
HOOK_DICT = {
13-
'id': 'check-hooks-apply',
14-
'name': 'Check hooks apply to the repository',
15-
'files': C.CONFIG_FILE,
16-
'language': 'system',
17-
'entry': make_meta_entry(__name__),
18-
}
19-
2011

2112
def check_all_hooks_match_files(config_file):
2213
files = git.get_all_files()

pre_commit/meta_hooks/check_useless_excludes.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@
1010
from pre_commit.clientlib import load_config
1111
from pre_commit.clientlib import MANIFEST_HOOK_DICT
1212
from pre_commit.commands.run import _filter_by_types
13-
from pre_commit.meta_hooks.helpers import make_meta_entry
14-
15-
HOOK_DICT = {
16-
'id': 'check-useless-excludes',
17-
'name': 'Check for useless excludes',
18-
'files': C.CONFIG_FILE,
19-
'language': 'system',
20-
'entry': make_meta_entry(__name__),
21-
}
2213

2314

2415
def exclude_matches_any(filenames, include, exclude):

pre_commit/meta_hooks/helpers.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

pre_commit/meta_hooks/identity.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
import sys
22

33
from pre_commit import output
4-
from pre_commit.meta_hooks.helpers import make_meta_entry
5-
6-
HOOK_DICT = {
7-
'id': 'identity',
8-
'name': 'identity',
9-
'language': 'system',
10-
'verbose': True,
11-
'entry': make_meta_entry(__name__),
12-
}
134

145

156
def main(argv=None):

pre_commit/repository.py

Lines changed: 6 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
import logging
77
import os
88

9-
from cfgv import apply_defaults
10-
from cfgv import validate
11-
129
import pre_commit.constants as C
1310
from pre_commit import five
1411
from pre_commit.clientlib import is_local_repo
@@ -137,15 +134,8 @@ def _hook(*hook_dicts):
137134
return ret
138135

139136

140-
def _hook_from_manifest_dct(dct):
141-
dct = apply_defaults(dct, MANIFEST_HOOK_DICT)
142-
dct = validate(dct, MANIFEST_HOOK_DICT)
143-
dct = _hook(dct)
144-
return dct
145-
146-
147-
def _local_repository_hooks(repo_config, store):
148-
def _local_prefix(language_name, deps):
137+
def _non_cloned_repository_hooks(repo_config, store):
138+
def _prefix(language_name, deps):
149139
language = languages[language_name]
150140
# pcre / pygrep / script / system / docker_image do not have
151141
# environments so they work out of the current directory
@@ -154,45 +144,11 @@ def _local_prefix(language_name, deps):
154144
else:
155145
return Prefix(store.make_local(deps))
156146

157-
hook_dcts = [_hook_from_manifest_dct(h) for h in repo_config['hooks']]
158-
return tuple(
159-
Hook.create(
160-
repo_config['repo'],
161-
_local_prefix(hook['language'], hook['additional_dependencies']),
162-
hook,
163-
)
164-
for hook in hook_dcts
165-
)
166-
167-
168-
def _meta_repository_hooks(repo_config, store):
169-
# imported here to prevent circular imports.
170-
from pre_commit.meta_hooks import check_hooks_apply
171-
from pre_commit.meta_hooks import check_useless_excludes
172-
from pre_commit.meta_hooks import identity
173-
174-
meta_hooks = [
175-
_hook_from_manifest_dct(mod.HOOK_DICT)
176-
for mod in (check_hooks_apply, check_useless_excludes, identity)
177-
]
178-
by_id = {hook['id']: hook for hook in meta_hooks}
179-
180-
for hook in repo_config['hooks']:
181-
if hook['id'] not in by_id:
182-
logger.error(
183-
'`{}` is not a valid meta hook. '
184-
'Typo? Perhaps it is introduced in a newer version? '
185-
'Often `pip install --upgrade pre-commit` fixes this.'
186-
.format(hook['id']),
187-
)
188-
exit(1)
189-
190-
prefix = Prefix(os.getcwd())
191147
return tuple(
192148
Hook.create(
193149
repo_config['repo'],
194-
prefix,
195-
_hook(by_id[hook['id']], hook),
150+
_prefix(hook['language'], hook['additional_dependencies']),
151+
_hook(hook),
196152
)
197153
for hook in repo_config['hooks']
198154
)
@@ -225,10 +181,8 @@ def _cloned_repository_hooks(repo_config, store):
225181

226182

227183
def repository_hooks(repo_config, store):
228-
if is_local_repo(repo_config):
229-
return _local_repository_hooks(repo_config, store)
230-
elif is_meta_repo(repo_config):
231-
return _meta_repository_hooks(repo_config, store)
184+
if is_local_repo(repo_config) or is_meta_repo(repo_config):
185+
return _non_cloned_repository_hooks(repo_config, store)
232186
else:
233187
return _cloned_repository_hooks(repo_config, store)
234188

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
install_requires=[
3838
'aspy.yaml',
39-
'cfgv>=1.0.0',
39+
'cfgv>=1.3.0',
4040
'identify>=1.0.0',
4141
# if this makes it into python3.8 move to extras_require
4242
'importlib-metadata',

tests/clientlib_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pre_commit.clientlib import check_type_tag
77
from pre_commit.clientlib import CONFIG_HOOK_DICT
8+
from pre_commit.clientlib import CONFIG_REPO_DICT
89
from pre_commit.clientlib import CONFIG_SCHEMA
910
from pre_commit.clientlib import is_local_repo
1011
from pre_commit.clientlib import MANIFEST_SCHEMA
@@ -236,3 +237,19 @@ def test_migrate_to_sha_ok():
236237
dct = {'repo': 'a', 'rev': 'b'}
237238
MigrateShaToRev().apply_default(dct)
238239
assert dct == {'repo': 'a', 'rev': 'b'}
240+
241+
242+
@pytest.mark.parametrize(
243+
'config_repo',
244+
(
245+
# i-dont-exist isn't a valid hook
246+
{'repo': 'meta', 'hooks': [{'id': 'i-dont-exist'}]},
247+
# invalid to set a language for a meta hook
248+
{'repo': 'meta', 'hooks': [{'id': 'identity', 'language': 'python'}]},
249+
# name override must be string
250+
{'repo': 'meta', 'hooks': [{'id': 'identity', 'name': False}]},
251+
),
252+
)
253+
def test_meta_hook_invalid_id(config_repo):
254+
with pytest.raises(cfgv.ValidationError):
255+
cfgv.validate(config_repo, CONFIG_REPO_DICT)

tests/commands/autoupdate_test.py

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

99
import pre_commit.constants as C
1010
from pre_commit import git
11-
from pre_commit.clientlib import load_config
1211
from pre_commit.commands.autoupdate import _update_repo
1312
from pre_commit.commands.autoupdate import autoupdate
1413
from pre_commit.commands.autoupdate import RepositoryCannotBeUpdatedError
@@ -17,6 +16,7 @@
1716
from testing.fixtures import add_config_to_repo
1817
from testing.fixtures import make_config_from_repo
1918
from testing.fixtures import make_repo
19+
from testing.fixtures import read_config
2020
from testing.fixtures import sample_local_config
2121
from testing.fixtures import write_config
2222
from testing.util import get_resource_path
@@ -319,7 +319,7 @@ def test_autoupdate_local_hooks(in_git_dir, store):
319319
config = sample_local_config()
320320
add_config_to_repo('.', config)
321321
assert autoupdate(C.CONFIG_FILE, store, tags_only=False) == 0
322-
new_config_writen = load_config(C.CONFIG_FILE)
322+
new_config_writen = read_config('.')
323323
assert len(new_config_writen['repos']) == 1
324324
assert new_config_writen['repos'][0] == config
325325

@@ -334,7 +334,7 @@ def test_autoupdate_local_hooks_with_out_of_date_repo(
334334
config = {'repos': [local_config, stale_config]}
335335
write_config('.', config)
336336
assert autoupdate(C.CONFIG_FILE, store, tags_only=False) == 0
337-
new_config_writen = load_config(C.CONFIG_FILE)
337+
new_config_writen = read_config('.')
338338
assert len(new_config_writen['repos']) == 2
339339
assert new_config_writen['repos'][0] == local_config
340340

tests/commands/gc_test.py

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

33
import pre_commit.constants as C
44
from pre_commit import git
5+
from pre_commit.clientlib import load_config
56
from pre_commit.commands.autoupdate import autoupdate
67
from pre_commit.commands.gc import gc
78
from pre_commit.repository import all_hooks
@@ -91,7 +92,7 @@ def test_gc_unused_local_repo_with_env(store, in_git_dir, cap_out):
9192
store.mark_config_used(C.CONFIG_FILE)
9293

9394
# this causes the repositories to be created
94-
all_hooks({'repos': [config]}, store)
95+
all_hooks(load_config(C.CONFIG_FILE), store)
9596

9697
assert _config_count(store) == 1
9798
assert _repo_count(store) == 1

0 commit comments

Comments
 (0)