forked from pre-commit/pre-commit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrepository.py
More file actions
226 lines (189 loc) · 7.35 KB
/
repository.py
File metadata and controls
226 lines (189 loc) · 7.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
from __future__ import unicode_literals
import io
import json
import logging
import os
import shutil
from collections import defaultdict
import pkg_resources
from cached_property import cached_property
from pre_commit import five
from pre_commit import git
from pre_commit.clientlib.validate_config import is_local_hooks
from pre_commit.clientlib.validate_manifest import MANIFEST_JSON_SCHEMA
from pre_commit.jsonschema_extensions import apply_defaults
from pre_commit.languages.all import languages
from pre_commit.languages.helpers import environment_dir
from pre_commit.manifest import Manifest
from pre_commit.prefixed_command_runner import PrefixedCommandRunner
logger = logging.getLogger('pre_commit')
_pre_commit_version = pkg_resources.parse_version(
pkg_resources.get_distribution('pre-commit').version
)
# Bump when installation changes in a backwards / forwards incompatible way
INSTALLED_STATE_VERSION = '1'
class Repository(object):
def __init__(self, repo_config, repo_path_getter):
self.repo_config = repo_config
self.repo_path_getter = repo_path_getter
self.__installed = False
@classmethod
def create(cls, config, store):
if is_local_hooks(config):
return LocalRepository(config)
else:
repo_path_getter = store.get_repo_path_getter(
config['repo'], config['sha']
)
return cls(config, repo_path_getter)
@cached_property
def repo_url(self):
return self.repo_config['repo']
@cached_property
def sha(self):
return self.repo_config['sha']
@cached_property
def languages(self):
return set(
(hook['language'], hook['language_version'])
for _, hook in self.hooks
)
@cached_property
def additional_dependencies(self):
dep_dict = defaultdict(lambda: defaultdict(set))
for _, hook in self.hooks:
dep_dict[hook['language']][hook['language_version']].update(
hook.get('additional_dependencies', []),
)
return dep_dict
@cached_property
def hooks(self):
for hook in self.repo_config['hooks']:
if hook['id'] not in self.manifest.hooks:
logger.error(
'`{0}` is not present in repository {1}. '
'Typo? Perhaps it is introduced in a newer version? '
'Often you can fix this by removing the hook, running '
'`pre-commit autoupdate`, '
'and then adding the hook.'.format(
hook['id'], self.repo_config['repo'],
)
)
exit(1)
hook_version = pkg_resources.parse_version(
self.manifest.hooks[hook['id']]['minimum_pre_commit_version'],
)
if hook_version > _pre_commit_version:
logger.error(
'The hook `{0}` requires pre-commit version {1} but '
'version {2} is installed. '
'Perhaps run `pip install --upgrade pre-commit`.'.format(
hook['id'], hook_version, _pre_commit_version,
)
)
exit(1)
return tuple(
(hook['id'], dict(self.manifest.hooks[hook['id']], **hook))
for hook in self.repo_config['hooks']
)
@cached_property
def manifest(self):
return Manifest(self.repo_path_getter)
@cached_property
def cmd_runner(self):
return PrefixedCommandRunner(self.repo_path_getter.repo_path)
def require_installed(self):
if self.__installed:
return
self.install()
self.__installed = True
def install(self):
"""Install the hook repository."""
def state(language_name, language_version):
return {
'additional_dependencies': sorted(
self.additional_dependencies[
language_name
][language_version],
)
}
def state_filename(venv, suffix=''):
return self.cmd_runner.path(
venv, '.install_state_v' + INSTALLED_STATE_VERSION + suffix,
)
def read_state(venv):
if not os.path.exists(state_filename(venv)):
return None
else:
return json.loads(io.open(state_filename(venv)).read())
def write_state(venv, language_name, language_version):
with io.open(
state_filename(venv, suffix='staging'), 'w',
) as state_file:
state_file.write(five.to_text(json.dumps(
state(language_name, language_version),
)))
# Move the file into place atomically to indicate we've installed
os.rename(
state_filename(venv, suffix='staging'),
state_filename(venv),
)
def language_is_installed(language_name, language_version):
language = languages[language_name]
venv = environment_dir(language.ENVIRONMENT_DIR, language_version)
return (
venv is None or
read_state(venv) == state(language_name, language_version)
)
if not all(
language_is_installed(language_name, language_version)
for language_name, language_version in self.languages
):
logger.info(
'Installing environment for {0}.'.format(self.repo_url)
)
logger.info('Once installed this environment will be reused.')
logger.info('This may take a few minutes...')
for language_name, language_version in self.languages:
if language_is_installed(language_name, language_version):
continue
language = languages[language_name]
venv = environment_dir(language.ENVIRONMENT_DIR, language_version)
# There's potentially incomplete cleanup from previous runs
# Clean it up!
if self.cmd_runner.exists(venv):
shutil.rmtree(self.cmd_runner.path(venv))
language.install_environment(
self.cmd_runner, language_version,
self.additional_dependencies[language_name][language_version],
)
# Write our state to indicate we're installed
write_state(venv, language_name, language_version)
def run_hook(self, hook, file_args):
"""Run a hook.
Args:
hook - Hook dictionary
file_args - List of files to run
"""
self.require_installed()
return languages[hook['language']].run_hook(
self.cmd_runner, hook, file_args,
)
class LocalRepository(Repository):
def __init__(self, repo_config):
super(LocalRepository, self).__init__(repo_config, None)
@cached_property
def hooks(self):
return tuple(
(hook['id'], apply_defaults(hook, MANIFEST_JSON_SCHEMA['items']))
for hook in self.repo_config['hooks']
)
@cached_property
def cmd_runner(self):
return PrefixedCommandRunner(git.get_root())
@cached_property
def sha(self):
raise NotImplementedError
@cached_property
def manifest(self):
raise NotImplementedError