Skip to content

Commit c577ed9

Browse files
committed
Refactor pre_commit.repository and factor out cached-property
1 parent 7448e58 commit c577ed9

File tree

14 files changed

+395
-451
lines changed

14 files changed

+395
-451
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ repos:
1919
rev: v1.11.2
2020
hooks:
2121
- id: validate_manifest
22+
- repo: https://github.com/asottile/pyupgrade
23+
rev: v1.11.0
24+
hooks:
25+
- id: pyupgrade
2226
- repo: https://github.com/asottile/reorder_python_imports
2327
rev: v1.3.0
2428
hooks:

.travis.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: python
2-
dist: trusty
3-
sudo: required
2+
dist: xenial
43
services:
54
- docker
65
matrix:
@@ -13,8 +12,6 @@ matrix:
1312
python: pypy2.7-5.10.0
1413
- env: TOXENV=py37
1514
python: 3.7
16-
sudo: required
17-
dist: xenial
1815
install: pip install coveralls tox
1916
script: tox
2017
before_install:

pre_commit/commands/autoupdate.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from __future__ import print_function
22
from __future__ import unicode_literals
33

4+
import os.path
45
import re
5-
from collections import OrderedDict
66

77
import six
88
from aspy.yaml import ordered_dump
@@ -16,8 +16,8 @@
1616
from pre_commit.clientlib import is_local_repo
1717
from pre_commit.clientlib import is_meta_repo
1818
from pre_commit.clientlib import load_config
19+
from pre_commit.clientlib import load_manifest
1920
from pre_commit.commands.migrate_config import migrate_config
20-
from pre_commit.repository import Repository
2121
from pre_commit.util import CalledProcessError
2222
from pre_commit.util import cmd_output
2323

@@ -52,24 +52,24 @@ def _update_repo(repo_config, store, tags_only):
5252
if rev == repo_config['rev']:
5353
return repo_config
5454

55-
# Construct a new config with the head rev
56-
new_config = OrderedDict(repo_config)
57-
new_config['rev'] = rev
58-
5955
try:
60-
new_hooks = Repository.create(new_config, store).manifest_hooks
56+
path = store.clone(repo_config['repo'], rev)
57+
manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE))
6158
except InvalidManifestError as e:
6259
raise RepositoryCannotBeUpdatedError(six.text_type(e))
6360

6461
# See if any of our hooks were deleted with the new commits
6562
hooks = {hook['id'] for hook in repo_config['hooks']}
66-
hooks_missing = hooks - set(new_hooks)
63+
hooks_missing = hooks - {hook['id'] for hook in manifest}
6764
if hooks_missing:
6865
raise RepositoryCannotBeUpdatedError(
6966
'Cannot update because the tip of master is missing these hooks:\n'
7067
'{}'.format(', '.join(sorted(hooks_missing))),
7168
)
7269

70+
# Construct a new config with the head rev
71+
new_config = repo_config.copy()
72+
new_config['rev'] = rev
7373
return new_config
7474

7575

pre_commit/commands/install_uninstall.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from pre_commit import output
1111
from pre_commit.clientlib import load_config
1212
from pre_commit.languages import python
13-
from pre_commit.repository import repositories
13+
from pre_commit.repository import all_hooks
14+
from pre_commit.repository import install_hook_envs
1415
from pre_commit.util import cmd_output
1516
from pre_commit.util import make_executable
1617
from pre_commit.util import mkdirp
@@ -116,8 +117,7 @@ def install(
116117

117118

118119
def install_hooks(config_file, store):
119-
for repository in repositories(load_config(config_file), store):
120-
repository.require_installed()
120+
install_hook_envs(all_hooks(load_config(config_file), store), store)
121121

122122

123123
def uninstall(hook_type='pre-commit'):

pre_commit/commands/run.py

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from pre_commit import output
1414
from pre_commit.clientlib import load_config
1515
from pre_commit.output import get_hook_message
16-
from pre_commit.repository import repositories
16+
from pre_commit.repository import all_hooks
17+
from pre_commit.repository import install_hook_envs
1718
from pre_commit.staged_files_only import staged_files_only
1819
from pre_commit.util import cmd_output
1920
from pre_commit.util import memoize_by_cwd
@@ -32,9 +33,7 @@ def _get_skips(environ):
3233

3334

3435
def _hook_msg_start(hook, verbose):
35-
return '{}{}'.format(
36-
'[{}] '.format(hook['id']) if verbose else '', hook['name'],
37-
)
36+
return '{}{}'.format('[{}] '.format(hook.id) if verbose else '', hook.name)
3837

3938

4039
def _filter_by_include_exclude(filenames, include, exclude):
@@ -63,21 +62,21 @@ def _filter_by_types(filenames, types, exclude_types):
6362
NO_FILES = '(no files to check)'
6463

6564

66-
def _run_single_hook(filenames, hook, repo, args, skips, cols):
67-
include, exclude = hook['files'], hook['exclude']
65+
def _run_single_hook(filenames, hook, args, skips, cols):
66+
include, exclude = hook.files, hook.exclude
6867
filenames = _filter_by_include_exclude(filenames, include, exclude)
69-
types, exclude_types = hook['types'], hook['exclude_types']
68+
types, exclude_types = hook.types, hook.exclude_types
7069
filenames = _filter_by_types(filenames, types, exclude_types)
7170

72-
if hook['language'] == 'pcre':
71+
if hook.language == 'pcre':
7372
logger.warning(
7473
'`{}` (from {}) uses the deprecated pcre language.\n'
7574
'The pcre language is scheduled for removal in pre-commit 2.x.\n'
7675
'The pygrep language is a more portable (and usually drop-in) '
77-
'replacement.'.format(hook['id'], repo.repo_config['repo']),
76+
'replacement.'.format(hook.id, hook.src),
7877
)
7978

80-
if hook['id'] in skips or hook['alias'] in skips:
79+
if hook.id in skips or hook.alias in skips:
8180
output.write(get_hook_message(
8281
_hook_msg_start(hook, args.verbose),
8382
end_msg=SKIPPED,
@@ -86,7 +85,7 @@ def _run_single_hook(filenames, hook, repo, args, skips, cols):
8685
cols=cols,
8786
))
8887
return 0
89-
elif not filenames and not hook['always_run']:
88+
elif not filenames and not hook.always_run:
9089
output.write(get_hook_message(
9190
_hook_msg_start(hook, args.verbose),
9291
postfix=NO_FILES,
@@ -107,8 +106,8 @@ def _run_single_hook(filenames, hook, repo, args, skips, cols):
107106
diff_before = cmd_output(
108107
'git', 'diff', '--no-ext-diff', retcode=None, encoding=None,
109108
)
110-
retcode, stdout, stderr = repo.run_hook(
111-
hook, tuple(filenames) if hook['pass_filenames'] else (),
109+
retcode, stdout, stderr = hook.run(
110+
tuple(filenames) if hook.pass_filenames else (),
112111
)
113112
diff_after = cmd_output(
114113
'git', 'diff', '--no-ext-diff', retcode=None, encoding=None,
@@ -133,9 +132,9 @@ def _run_single_hook(filenames, hook, repo, args, skips, cols):
133132

134133
if (
135134
(stdout or stderr or file_modifications) and
136-
(retcode or args.verbose or hook['verbose'])
135+
(retcode or args.verbose or hook.verbose)
137136
):
138-
output.write_line('hookid: {}\n'.format(hook['id']))
137+
output.write_line('hookid: {}\n'.format(hook.id))
139138

140139
# Print a message if failing due to file modifications
141140
if file_modifications:
@@ -149,7 +148,7 @@ def _run_single_hook(filenames, hook, repo, args, skips, cols):
149148
for out in (stdout, stderr):
150149
assert type(out) is bytes, type(out)
151150
if out.strip():
152-
output.write_line(out.strip(), logfile_name=hook['log_file'])
151+
output.write_line(out.strip(), logfile_name=hook.log_file)
153152
output.write_line()
154153

155154
return retcode
@@ -189,15 +188,15 @@ def _all_filenames(args):
189188
return git.get_staged_files()
190189

191190

192-
def _run_hooks(config, repo_hooks, args, environ):
191+
def _run_hooks(config, hooks, args, environ):
193192
"""Actually run the hooks."""
194193
skips = _get_skips(environ)
195-
cols = _compute_cols([hook for _, hook in repo_hooks], args.verbose)
194+
cols = _compute_cols(hooks, args.verbose)
196195
filenames = _all_filenames(args)
197196
filenames = _filter_by_include_exclude(filenames, '', config['exclude'])
198197
retval = 0
199-
for repo, hook in repo_hooks:
200-
retval |= _run_single_hook(filenames, hook, repo, args, skips, cols)
198+
for hook in hooks:
199+
retval |= _run_single_hook(filenames, hook, args, skips, cols)
201200
if retval and config['fail_fast']:
202201
break
203202
if (
@@ -252,28 +251,18 @@ def run(config_file, store, args, environ=os.environ):
252251
ctx = staged_files_only(store.directory)
253252

254253
with ctx:
255-
repo_hooks = []
256254
config = load_config(config_file)
257-
for repo in repositories(config, store):
258-
for _, hook in repo.hooks:
259-
if (
260-
(
261-
not args.hook or
262-
hook['id'] == args.hook or
263-
hook['alias'] == args.hook
264-
) and
265-
(
266-
not hook['stages'] or
267-
args.hook_stage in hook['stages']
268-
)
269-
):
270-
repo_hooks.append((repo, hook))
271-
272-
if args.hook and not repo_hooks:
255+
hooks = [
256+
hook
257+
for hook in all_hooks(config, store)
258+
if not args.hook or hook.id == args.hook or hook.alias == args.hook
259+
if not hook.stages or args.hook_stage in hook.stages
260+
]
261+
262+
if args.hook and not hooks:
273263
output.write_line('No hook with id `{}`'.format(args.hook))
274264
return 1
275265

276-
for repo in {repo for repo, _ in repo_hooks}:
277-
repo.require_installed()
266+
install_hook_envs(hooks, store)
278267

279-
return _run_hooks(config, repo_hooks, args, environ)
268+
return _run_hooks(config, hooks, args, environ)

pre_commit/meta_hooks/check_hooks_apply.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,33 @@
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.repository import repositories
8+
from pre_commit.meta_hooks.helpers import make_meta_entry
9+
from pre_commit.repository import all_hooks
910
from pre_commit.store import Store
1011

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+
1120

1221
def check_all_hooks_match_files(config_file):
1322
files = git.get_all_files()
1423
retv = 0
1524

16-
for repo in repositories(load_config(config_file), Store()):
17-
for hook_id, hook in repo.hooks:
18-
if hook['always_run'] or hook['language'] == 'fail':
19-
continue
20-
include, exclude = hook['files'], hook['exclude']
21-
filtered = _filter_by_include_exclude(files, include, exclude)
22-
types, exclude_types = hook['types'], hook['exclude_types']
23-
filtered = _filter_by_types(filtered, types, exclude_types)
24-
if not filtered:
25-
print('{} does not apply to this repository'.format(hook_id))
26-
retv = 1
25+
for hook in all_hooks(load_config(config_file), Store()):
26+
if hook.always_run or hook.language == 'fail':
27+
continue
28+
include, exclude = hook.files, hook.exclude
29+
filtered = _filter_by_include_exclude(files, include, exclude)
30+
types, exclude_types = hook.types, hook.exclude_types
31+
filtered = _filter_by_types(filtered, types, exclude_types)
32+
if not filtered:
33+
print('{} does not apply to this repository'.format(hook.id))
34+
retv = 1
2735

2836
return retv
2937

pre_commit/meta_hooks/check_useless_excludes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
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+
}
1322

1423

1524
def exclude_matches_any(filenames, include, exclude):

pre_commit/meta_hooks/helpers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pipes
2+
import sys
3+
4+
5+
def make_meta_entry(modname):
6+
"""the hook `entry` is passed through `shlex.split()` by the command
7+
runner, so to prevent issues with spaces and backslashes (on Windows)
8+
it must be quoted here.
9+
"""
10+
return '{} -m {}'.format(pipes.quote(sys.executable), modname)

pre_commit/meta_hooks/identity.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
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+
}
413

514

615
def main(argv=None):

0 commit comments

Comments
 (0)