44import json
55import logging
66import os
7+ import pipes
78import shutil
9+ import sys
810from collections import defaultdict
911
1012import pkg_resources
1416from pre_commit import five
1517from pre_commit import git
1618from pre_commit .clientlib import is_local_repo
19+ from pre_commit .clientlib import is_meta_repo
1720from pre_commit .clientlib import load_manifest
1821from pre_commit .clientlib import MANIFEST_HOOK_DICT
1922from pre_commit .languages .all import languages
@@ -125,6 +128,12 @@ def _hook(*hook_dicts):
125128 return ret
126129
127130
131+ def _hook_from_manifest_dct (dct ):
132+ dct = validate (apply_defaults (dct , MANIFEST_HOOK_DICT ), MANIFEST_HOOK_DICT )
133+ dct = _hook (dct )
134+ return dct
135+
136+
128137class Repository (object ):
129138 def __init__ (self , repo_config , store ):
130139 self .repo_config = repo_config
@@ -135,6 +144,8 @@ def __init__(self, repo_config, store):
135144 def create (cls , config , store ):
136145 if is_local_repo (config ):
137146 return LocalRepository (config , store )
147+ elif is_meta_repo (config ):
148+ return MetaRepository (config , store )
138149 else :
139150 return cls (config , store )
140151
@@ -221,14 +232,8 @@ def manifest(self):
221232
222233 @cached_property
223234 def hooks (self ):
224- def _from_manifest_dct (dct ):
225- dct = validate (dct , MANIFEST_HOOK_DICT )
226- dct = apply_defaults (dct , MANIFEST_HOOK_DICT )
227- dct = _hook (dct )
228- return dct
229-
230235 return tuple (
231- (hook ['id' ], _from_manifest_dct (hook ))
236+ (hook ['id' ], _hook_from_manifest_dct (hook ))
232237 for hook in self .repo_config ['hooks' ]
233238 )
234239
@@ -246,6 +251,60 @@ def _venvs(self):
246251 return tuple (ret )
247252
248253
254+ class MetaRepository (LocalRepository ):
255+ @cached_property
256+ def manifest_hooks (self ):
257+ # The hooks are imported here to prevent circular imports.
258+ from pre_commit .meta_hooks import check_files_matches_any
259+ from pre_commit .meta_hooks import check_useless_excludes
260+
261+ def _make_entry (mod ):
262+ """the hook `entry` is passed through `shlex.split()` by the
263+ command runner, so to prevent issues with spaces and backslashes
264+ (on Windows) it must be quoted here.
265+ """
266+ return '{} -m {}' .format (pipes .quote (sys .executable ), mod .__name__ )
267+
268+ meta_hooks = [
269+ {
270+ 'id' : 'check-files-matches-any' ,
271+ 'name' : 'Check hooks match any files' ,
272+ 'files' : '.pre-commit-config.yaml' ,
273+ 'language' : 'system' ,
274+ 'entry' : _make_entry (check_files_matches_any ),
275+ },
276+ {
277+ 'id' : 'check-useless-excludes' ,
278+ 'name' : 'Check for useless excludes' ,
279+ 'files' : '.pre-commit-config.yaml' ,
280+ 'language' : 'system' ,
281+ 'entry' : _make_entry (check_useless_excludes ),
282+ },
283+ ]
284+
285+ return {
286+ hook ['id' ]: _hook_from_manifest_dct (hook )
287+ for hook in meta_hooks
288+ }
289+
290+ @cached_property
291+ def hooks (self ):
292+ for hook in self .repo_config ['hooks' ]:
293+ if hook ['id' ] not in self .manifest_hooks :
294+ logger .error (
295+ '`{}` is not a valid meta hook. '
296+ 'Typo? Perhaps it is introduced in a newer version? '
297+ 'Often `pip install --upgrade pre-commit` fixes this.'
298+ .format (hook ['id' ]),
299+ )
300+ exit (1 )
301+
302+ return tuple (
303+ (hook ['id' ], _hook (self .manifest_hooks [hook ['id' ]], hook ))
304+ for hook in self .repo_config ['hooks' ]
305+ )
306+
307+
249308class _UniqueList (list ):
250309 def __init__ (self ):
251310 self ._set = set ()
0 commit comments