Skip to content

Commit b827694

Browse files
committed
Each set of additional dependencies gets its own env
1 parent 29715c9 commit b827694

File tree

5 files changed

+61
-111
lines changed

5 files changed

+61
-111
lines changed

pre_commit/commands/autoupdate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ def _update_repo(repo_config, runner, tags_only):
3333
Args:
3434
repo_config - A config for a repository
3535
"""
36-
repo = Repository.create(repo_config, runner.store)
36+
repo_path = runner.store.clone(repo_config['repo'], repo_config['sha'])
3737

38-
with cwd(repo._repo_path):
38+
with cwd(repo_path):
3939
cmd_output('git', 'fetch')
4040
tag_cmd = ('git', 'describe', 'origin/master', '--tags')
4141
if tags_only:
@@ -57,7 +57,7 @@ def _update_repo(repo_config, runner, tags_only):
5757
new_repo = Repository.create(new_config, runner.store)
5858

5959
# See if any of our hooks were deleted with the new commits
60-
hooks = {hook['id'] for hook in repo.repo_config['hooks']}
60+
hooks = {hook['id'] for hook in repo_config['hooks']}
6161
hooks_missing = hooks - (hooks & set(new_repo.manifest_hooks))
6262
if hooks_missing:
6363
raise RepositoryCannotBeUpdatedError(

pre_commit/repository.py

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import pipes
88
import shutil
99
import sys
10-
from collections import defaultdict
1110

1211
import pkg_resources
1312
from cached_property import cached_property
@@ -149,22 +148,11 @@ def create(cls, config, store):
149148
else:
150149
return cls(config, store)
151150

152-
@cached_property
153-
def _repo_path(self):
154-
return self.store.clone(
155-
self.repo_config['repo'], self.repo_config['sha'],
156-
)
157-
158-
@cached_property
159-
def _prefix(self):
160-
return Prefix(self._repo_path)
161-
162-
def _prefix_from_deps(self, language_name, deps):
163-
return self._prefix
164-
165151
@cached_property
166152
def manifest_hooks(self):
167-
manifest_path = os.path.join(self._repo_path, C.MANIFEST_FILE)
153+
repo, sha = self.repo_config['repo'], self.repo_config['sha']
154+
repo_path = self.store.clone(repo, sha)
155+
manifest_path = os.path.join(repo_path, C.MANIFEST_FILE)
168156
return {hook['id']: hook for hook in load_manifest(manifest_path)}
169157

170158
@cached_property
@@ -185,21 +173,25 @@ def hooks(self):
185173
for hook in self.repo_config['hooks']
186174
)
187175

188-
@cached_property
176+
def _prefix_from_deps(self, language_name, deps):
177+
repo, sha = self.repo_config['repo'], self.repo_config['sha']
178+
return Prefix(self.store.clone(repo, sha, deps))
179+
189180
def _venvs(self):
190-
deps_dict = defaultdict(_UniqueList)
191-
for _, hook in self.hooks:
192-
deps_dict[(hook['language'], hook['language_version'])].update(
193-
hook['additional_dependencies'],
194-
)
195181
ret = []
196-
for (language, version), deps in deps_dict.items():
197-
ret.append((self._prefix, language, version, deps))
182+
for _, hook in self.hooks:
183+
language = hook['language']
184+
version = hook['language_version']
185+
deps = hook['additional_dependencies']
186+
ret.append((
187+
self._prefix_from_deps(language, deps),
188+
language, version, deps,
189+
))
198190
return tuple(ret)
199191

200192
def require_installed(self):
201193
if not self.__installed:
202-
_install_all(self._venvs, self.repo_config['repo'], self.store)
194+
_install_all(self._venvs(), self.repo_config['repo'], self.store)
203195
self.__installed = True
204196

205197
def run_hook(self, hook, file_args):
@@ -237,19 +229,6 @@ def hooks(self):
237229
for hook in self.repo_config['hooks']
238230
)
239231

240-
@cached_property
241-
def _venvs(self):
242-
ret = []
243-
for _, hook in self.hooks:
244-
language = hook['language']
245-
version = hook['language_version']
246-
deps = hook['additional_dependencies']
247-
ret.append((
248-
self._prefix_from_deps(language, deps),
249-
language, version, deps,
250-
))
251-
return tuple(ret)
252-
253232

254233
class MetaRepository(LocalRepository):
255234
@cached_property
@@ -303,14 +282,3 @@ def hooks(self):
303282
(hook['id'], _hook(self.manifest_hooks[hook['id']], hook))
304283
for hook in self.repo_config['hooks']
305284
)
306-
307-
308-
class _UniqueList(list):
309-
def __init__(self):
310-
self._set = set()
311-
312-
def update(self, obj):
313-
for item in obj:
314-
if item not in self._set:
315-
self._set.add(item)
316-
self.append(item)

pre_commit/store.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ def _write_sqlite_db(self):
7272
with contextlib.closing(sqlite3.connect(tmpfile)) as db:
7373
db.executescript(
7474
'CREATE TABLE repos ('
75-
' repo CHAR(255) NOT NULL,'
76-
' ref CHAR(255) NOT NULL,'
77-
' path CHAR(255) NOT NULL,'
75+
' repo TEXT NOT NULL,'
76+
' ref TEXT NOT NULL,'
77+
' path TEXT NOT NULL,'
7878
' PRIMARY KEY (repo, ref)'
7979
');',
8080
)
@@ -101,15 +101,17 @@ def require_created(self):
101101
self._create()
102102
self.__created = True
103103

104-
def _new_repo(self, repo, ref, make_strategy):
104+
def _new_repo(self, repo, ref, deps, make_strategy):
105105
self.require_created()
106+
if deps:
107+
repo = '{}:{}'.format(repo, ','.join(sorted(deps)))
106108

107109
def _get_result():
108110
# Check if we already exist
109111
with sqlite3.connect(self.db_path) as db:
110112
result = db.execute(
111113
'SELECT path FROM repos WHERE repo = ? AND ref = ?',
112-
[repo, ref],
114+
(repo, ref),
113115
).fetchone()
114116
if result:
115117
return result[0]
@@ -137,7 +139,7 @@ def _get_result():
137139
)
138140
return directory
139141

140-
def clone(self, repo, ref):
142+
def clone(self, repo, ref, deps=()):
141143
"""Clone the given url and checkout the specific ref."""
142144
def clone_strategy(directory):
143145
cmd_output(
@@ -151,7 +153,7 @@ def clone_strategy(directory):
151153
env=no_git_env(),
152154
)
153155

154-
return self._new_repo(repo, ref, clone_strategy)
156+
return self._new_repo(repo, ref, deps, clone_strategy)
155157

156158
def make_local(self, deps):
157159
def make_local_strategy(directory):
@@ -172,8 +174,7 @@ def _git_cmd(*args):
172174
_git_cmd('commit', '--no-edit', '--no-gpg-sign', '-n', '-minit')
173175

174176
return self._new_repo(
175-
'local:{}'.format(','.join(sorted(deps))), C.LOCAL_REPO_VERSION,
176-
make_local_strategy,
177+
'local', C.LOCAL_REPO_VERSION, deps, make_local_strategy,
177178
)
178179

179180
@cached_property

tests/conftest.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,6 @@ def log_info_mock():
165165
yield mck
166166

167167

168-
@pytest.fixture
169-
def log_warning_mock():
170-
with mock.patch.object(logging.getLogger('pre_commit'), 'warning') as mck:
171-
yield mck
172-
173-
174168
class FakeStream(object):
175169
def __init__(self):
176170
self.data = io.BytesIO()

tests/repository_test.py

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ def test_venvs(tempdir_factory, store):
433433
path = make_repo(tempdir_factory, 'python_hooks_repo')
434434
config = make_config_from_repo(path)
435435
repo = Repository.create(config, store)
436-
venv, = repo._venvs
436+
venv, = repo._venvs()
437437
assert venv == (mock.ANY, 'python', python.get_default_version(), [])
438438

439439

@@ -443,50 +443,33 @@ def test_additional_dependencies(tempdir_factory, store):
443443
config = make_config_from_repo(path)
444444
config['hooks'][0]['additional_dependencies'] = ['pep8']
445445
repo = Repository.create(config, store)
446-
venv, = repo._venvs
446+
venv, = repo._venvs()
447447
assert venv == (mock.ANY, 'python', python.get_default_version(), ['pep8'])
448448

449449

450450
@pytest.mark.integration
451-
def test_additional_dependencies_duplicated(
452-
tempdir_factory, store, log_warning_mock,
453-
):
454-
path = make_repo(tempdir_factory, 'ruby_hooks_repo')
455-
config = make_config_from_repo(path)
456-
deps = ['thread_safe', 'tins', 'thread_safe']
457-
config['hooks'][0]['additional_dependencies'] = deps
458-
repo = Repository.create(config, store)
459-
venv, = repo._venvs
460-
assert venv == (mock.ANY, 'ruby', 'default', ['thread_safe', 'tins'])
461-
462-
463-
@pytest.mark.integration
464-
def test_additional_python_dependencies_installed(tempdir_factory, store):
451+
def test_additional_dependencies_roll_forward(tempdir_factory, store):
465452
path = make_repo(tempdir_factory, 'python_hooks_repo')
466-
config = make_config_from_repo(path)
467-
config['hooks'][0]['additional_dependencies'] = ['mccabe']
468-
repo = Repository.create(config, store)
469-
repo.require_installed()
470-
with python.in_env(repo._prefix, 'default'):
471-
output = cmd_output('pip', 'freeze', '-l')[1]
472-
assert 'mccabe' in output
473453

454+
config1 = make_config_from_repo(path)
455+
repo1 = Repository.create(config1, store)
456+
repo1.require_installed()
457+
(prefix1, _, version1, _), = repo1._venvs()
458+
with python.in_env(prefix1, version1):
459+
assert 'mccabe' not in cmd_output('pip', 'freeze', '-l')[1]
474460

475-
@pytest.mark.integration
476-
def test_additional_dependencies_roll_forward(tempdir_factory, store):
477-
path = make_repo(tempdir_factory, 'python_hooks_repo')
478-
config = make_config_from_repo(path)
479-
# Run the repo once without additional_dependencies
480-
repo = Repository.create(config, store)
481-
repo.require_installed()
482-
# Now run it with additional_dependencies
483-
config['hooks'][0]['additional_dependencies'] = ['mccabe']
484-
repo = Repository.create(config, store)
485-
repo.require_installed()
486-
# We should see our additional dependency installed
487-
with python.in_env(repo._prefix, 'default'):
488-
output = cmd_output('pip', 'freeze', '-l')[1]
489-
assert 'mccabe' in output
461+
# Make another repo with additional dependencies
462+
config2 = make_config_from_repo(path)
463+
config2['hooks'][0]['additional_dependencies'] = ['mccabe']
464+
repo2 = Repository.create(config2, store)
465+
repo2.require_installed()
466+
(prefix2, _, version2, _), = repo2._venvs()
467+
with python.in_env(prefix2, version2):
468+
assert 'mccabe' in cmd_output('pip', 'freeze', '-l')[1]
469+
470+
# should not have affected original
471+
with python.in_env(prefix1, version1):
472+
assert 'mccabe' not in cmd_output('pip', 'freeze', '-l')[1]
490473

491474

492475
@xfailif_windows_no_ruby
@@ -499,7 +482,8 @@ def test_additional_ruby_dependencies_installed(
499482
config['hooks'][0]['additional_dependencies'] = ['thread_safe', 'tins']
500483
repo = Repository.create(config, store)
501484
repo.require_installed()
502-
with ruby.in_env(repo._prefix, 'default'):
485+
(prefix, _, version, _), = repo._venvs()
486+
with ruby.in_env(prefix, version):
503487
output = cmd_output('gem', 'list', '--local')[1]
504488
assert 'thread_safe' in output
505489
assert 'tins' in output
@@ -516,7 +500,8 @@ def test_additional_node_dependencies_installed(
516500
config['hooks'][0]['additional_dependencies'] = ['lodash']
517501
repo = Repository.create(config, store)
518502
repo.require_installed()
519-
with node.in_env(repo._prefix, 'default'):
503+
(prefix, _, version, _), = repo._venvs()
504+
with node.in_env(prefix, version):
520505
output = cmd_output('npm', 'ls', '-g')[1]
521506
assert 'lodash' in output
522507

@@ -532,7 +517,8 @@ def test_additional_golang_dependencies_installed(
532517
config['hooks'][0]['additional_dependencies'] = deps
533518
repo = Repository.create(config, store)
534519
repo.require_installed()
535-
binaries = os.listdir(repo._prefix.path(
520+
(prefix, _, _, _), = repo._venvs()
521+
binaries = os.listdir(prefix.path(
536522
helpers.environment_dir(golang.ENVIRONMENT_DIR, 'default'), 'bin',
537523
))
538524
# normalize for windows
@@ -598,8 +584,9 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
598584
repo.run_hook(hook, [])
599585

600586
# Should have made an environment, however this environment is broken!
601-
envdir = 'py_env-{}'.format(python.get_default_version())
602-
assert repo._prefix.exists(envdir)
587+
(prefix, _, version, _), = repo._venvs()
588+
envdir = 'py_env-{}'.format(version)
589+
assert prefix.exists(envdir)
603590

604591
# However, it should be perfectly runnable (reinstall after botched
605592
# install)
@@ -616,8 +603,8 @@ def test_invalidated_virtualenv(tempdir_factory, store):
616603

617604
# Simulate breaking of the virtualenv
618605
repo.require_installed()
619-
version = python.get_default_version()
620-
libdir = repo._prefix.path('py_env-{}'.format(version), 'lib', version)
606+
(prefix, _, version, _), = repo._venvs()
607+
libdir = prefix.path('py_env-{}'.format(version), 'lib', version)
621608
paths = [
622609
os.path.join(libdir, p) for p in ('site.py', 'site.pyc', '__pycache__')
623610
]

0 commit comments

Comments
 (0)