diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index 293c81fc..01a361c8 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -32,7 +32,7 @@ def _update_repo(repo_config, runner, tags_only): """ repo = Repository.create(repo_config, runner.store) - with cwd(repo.repo_path_getter.repo_path): + with cwd(repo._repo_path): cmd_output('git', 'fetch') tag_cmd = ('git', 'describe', 'origin/master', '--tags') if tags_only: diff --git a/pre_commit/manifest.py b/pre_commit/manifest.py index 55f7c1ae..2b4614c7 100644 --- a/pre_commit/manifest.py +++ b/pre_commit/manifest.py @@ -13,15 +13,14 @@ logger = logging.getLogger('pre_commit') class Manifest(object): - def __init__(self, repo_path_getter, repo_url): - self.repo_path_getter = repo_path_getter + def __init__(self, repo_path, repo_url): + self.repo_path = repo_path self.repo_url = repo_url @cached_property def manifest_contents(self): - repo_path = self.repo_path_getter.repo_path - default_path = os.path.join(repo_path, C.MANIFEST_FILE) - legacy_path = os.path.join(repo_path, C.MANIFEST_FILE_LEGACY) + default_path = os.path.join(self.repo_path, C.MANIFEST_FILE) + legacy_path = os.path.join(self.repo_path, C.MANIFEST_FILE_LEGACY) if os.path.exists(legacy_path) and not os.path.exists(default_path): logger.warning( '{} uses legacy {} to provide hooks.\n' diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 8ec6302b..c5091683 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -31,41 +31,99 @@ _pre_commit_version = pkg_resources.parse_version( INSTALLED_STATE_VERSION = '1' +def _state(additional_deps): + return {'additional_dependencies': sorted(additional_deps)} + + +def _state_filename(cmd_runner, venv): + return cmd_runner.path(venv, '.install_state_v' + INSTALLED_STATE_VERSION) + + +def _read_installed_state(cmd_runner, venv): + filename = _state_filename(cmd_runner, venv) + if not os.path.exists(filename): + return None + else: + return json.loads(io.open(filename).read()) + + +def _write_installed_state(cmd_runner, venv, state): + state_filename = _state_filename(cmd_runner, venv) + staging = state_filename + 'staging' + with io.open(staging, 'w') as state_file: + state_file.write(five.to_text(json.dumps(state))) + # Move the file into place atomically to indicate we've installed + os.rename(staging, state_filename) + + +def _installed(cmd_runner, language_name, language_version, additional_deps): + language = languages[language_name] + venv = environment_dir(language.ENVIRONMENT_DIR, language_version) + return ( + venv is None or + _read_installed_state(cmd_runner, venv) == _state(additional_deps) + ) + + +def _install_all(venvs, repo_url): + """Tuple of (cmd_runner, language, version, deps)""" + need_installed = tuple( + (cmd_runner, language_name, version, deps) + for cmd_runner, language_name, version, deps in venvs + if not _installed(cmd_runner, language_name, version, deps) + ) + + if need_installed: + logger.info( + 'Installing environment for {}.'.format(repo_url) + ) + logger.info('Once installed this environment will be reused.') + logger.info('This may take a few minutes...') + + for cmd_runner, language_name, version, deps in need_installed: + language = languages[language_name] + venv = environment_dir(language.ENVIRONMENT_DIR, version) + + # There's potentially incomplete cleanup from previous runs + # Clean it up! + if cmd_runner.exists(venv): + shutil.rmtree(cmd_runner.path(venv)) + + language.install_environment(cmd_runner, version, deps) + # Write our state to indicate we're installed + state = _state(deps) + _write_installed_state(cmd_runner, venv, state) + + class Repository(object): - def __init__(self, repo_config, repo_path_getter): + def __init__(self, repo_config, store): self.repo_config = repo_config - self.repo_path_getter = repo_path_getter + self.store = store self.__installed = False @classmethod def create(cls, config, store): if is_local_hooks(config): - return LocalRepository(config) + return LocalRepository(config, store) else: - repo_path_getter = store.get_repo_path_getter( - config['repo'], config['sha'] - ) - return cls(config, repo_path_getter) + return cls(config, store) @cached_property - def repo_url(self): - return self.repo_config['repo'] + def _repo_path(self): + return self.store.clone( + self.repo_config['repo'], self.repo_config['sha'], + ) @cached_property - def languages(self): - return { - (hook['language'], hook['language_version']) - for _, hook in self.hooks - } + def _cmd_runner(self): + return PrefixedCommandRunner(self._repo_path) + + def _cmd_runner_from_deps(self, language_name, deps): + return self._cmd_runner @cached_property - def additional_dependencies(self): - dep_dict = defaultdict(lambda: defaultdict(_UniqueList)) - for _, hook in self.hooks: - dep_dict[hook['language']][hook['language_version']].update( - hook.get('additional_dependencies', []), - ) - return dep_dict + def manifest(self): + return Manifest(self._repo_path, self.repo_config['repo']) @cached_property def hooks(self): @@ -97,108 +155,49 @@ class Repository(object): ) @cached_property - def manifest(self): - return Manifest(self.repo_path_getter, self.repo_url) - - @cached_property - def cmd_runner(self): - return PrefixedCommandRunner(self.repo_path_getter.repo_path) + def _venvs(self): + deps_dict = defaultdict(_UniqueList) + for _, hook in self.hooks: + deps_dict[(hook['language'], hook['language_version'])].update( + hook.get('additional_dependencies', []), + ) + ret = [] + for (language, version), deps in deps_dict.items(): + ret.append((self._cmd_runner, language, version, deps)) + return tuple(ret) 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 {}.'.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) + if not self.__installed: + _install_all(self._venvs, self.repo_config['repo']) + self.__installed = True def run_hook(self, hook, file_args): """Run a hook. - Args: - hook - Hook dictionary - file_args - List of files to run + :param dict hook: + :param tuple file_args: all the files to run the hook on """ self.require_installed() - return languages[hook['language']].run_hook( - self.cmd_runner, hook, file_args, - ) + language_name = hook['language'] + deps = hook.get('additional_dependencies', []) + cmd_runner = self._cmd_runner_from_deps(language_name, deps) + return languages[language_name].run_hook(cmd_runner, hook, file_args) class LocalRepository(Repository): - def __init__(self, repo_config): - super(LocalRepository, self).__init__(repo_config, None) + def _cmd_runner_from_deps(self, language_name, deps): + """local repositories have a cmd runner per hook""" + language = languages[language_name] + # pcre / script / system do not have environments so they work out + # of the current directory + if language.ENVIRONMENT_DIR is None: + return PrefixedCommandRunner(git.get_root()) + else: + return PrefixedCommandRunner(self.store.make_local(deps)) + + @cached_property + def manifest(self): + raise NotImplementedError @cached_property def hooks(self): @@ -208,12 +207,17 @@ class LocalRepository(Repository): ) @cached_property - def cmd_runner(self): - return PrefixedCommandRunner(git.get_root()) - - @cached_property - def manifest(self): - raise NotImplementedError + def _venvs(self): + ret = [] + for _, hook in self.hooks: + language = hook['language'] + version = hook['language_version'] + deps = hook.get('additional_dependencies', []) + ret.append(( + self._cmd_runner_from_deps(language, deps), + language, version, deps, + )) + return tuple(ret) class _UniqueList(list): diff --git a/pre_commit/resources/empty_template/.npmignore b/pre_commit/resources/empty_template/.npmignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/pre_commit/resources/empty_template/.npmignore @@ -0,0 +1 @@ +* diff --git a/pre_commit/resources/empty_template/main.go b/pre_commit/resources/empty_template/main.go new file mode 100644 index 00000000..38dd16da --- /dev/null +++ b/pre_commit/resources/empty_template/main.go @@ -0,0 +1,3 @@ +package main + +func main() {} diff --git a/pre_commit/resources/empty_template/package.json b/pre_commit/resources/empty_template/package.json new file mode 100644 index 00000000..ac7b7259 --- /dev/null +++ b/pre_commit/resources/empty_template/package.json @@ -0,0 +1,4 @@ +{ + "name": "pre_commit_dummy_package", + "version": "0.0.0" +} diff --git a/pre_commit/resources/empty_template/pre_commit_dummy_package.gemspec b/pre_commit/resources/empty_template/pre_commit_dummy_package.gemspec new file mode 100644 index 00000000..5c361d5c --- /dev/null +++ b/pre_commit/resources/empty_template/pre_commit_dummy_package.gemspec @@ -0,0 +1,5 @@ +Gem::Specification.new do |s| + s.name = 'pre_commit_dummy_package' + s.version = '0.0.0' + s.authors = ['Anthony Sottile'] +end diff --git a/pre_commit/resources/empty_template/setup.py b/pre_commit/resources/empty_template/setup.py new file mode 100644 index 00000000..68860648 --- /dev/null +++ b/pre_commit/resources/empty_template/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + + +setup(name='pre-commit-dummy-package', version='0.0.0') diff --git a/pre_commit/store.py b/pre_commit/store.py index aecc7dc1..3f24d0b0 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -12,8 +12,10 @@ from cached_property import cached_property from pre_commit.prefixed_command_runner import PrefixedCommandRunner from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output +from pre_commit.util import copy_tree_to_path from pre_commit.util import cwd from pre_commit.util import no_git_env +from pre_commit.util import resource_filename logger = logging.getLogger('pre_commit') @@ -35,16 +37,6 @@ def _get_default_directory(): class Store(object): get_default_directory = staticmethod(_get_default_directory) - class RepoPathGetter(object): - def __init__(self, repo, ref, store): - self._repo = repo - self._ref = ref - self._store = store - - @cached_property - def repo_path(self): - return self._store.clone(self._repo, self._ref) - def __init__(self, directory=None): if directory is None: directory = self.get_default_directory() @@ -91,45 +83,55 @@ class Store(object): def require_created(self): """Require the pre-commit file store to be created.""" - if self.__created: - return + if not self.__created: + self._create() + self.__created = True - self._create() - self.__created = True - - def clone(self, url, ref): - """Clone the given url and checkout the specific ref.""" + def _new_repo(self, repo, ref, make_strategy): self.require_created() # Check if we already exist with sqlite3.connect(self.db_path) as db: result = db.execute( 'SELECT path FROM repos WHERE repo = ? AND ref = ?', - [url, ref], + [repo, ref], ).fetchone() if result: return result[0] - logger.info('Initializing environment for {}.'.format(url)) + logger.info('Initializing environment for {}.'.format(repo)) - dir = tempfile.mkdtemp(prefix='repo', dir=self.directory) - with clean_path_on_failure(dir): - cmd_output( - 'git', 'clone', '--no-checkout', url, dir, env=no_git_env(), - ) - with cwd(dir): - cmd_output('git', 'reset', ref, '--hard', env=no_git_env()) + directory = tempfile.mkdtemp(prefix='repo', dir=self.directory) + with clean_path_on_failure(directory): + make_strategy(directory) # Update our db with the created repo with sqlite3.connect(self.db_path) as db: db.execute( 'INSERT INTO repos (repo, ref, path) VALUES (?, ?, ?)', - [url, ref, dir], + [repo, ref, directory], ) - return dir + return directory - def get_repo_path_getter(self, repo, ref): - return self.RepoPathGetter(repo, ref, self) + def clone(self, repo, ref): + """Clone the given url and checkout the specific ref.""" + def clone_strategy(directory): + cmd_output( + 'git', 'clone', '--no-checkout', repo, directory, + env=no_git_env(), + ) + with cwd(directory): + cmd_output('git', 'reset', ref, '--hard', env=no_git_env()) + + return self._new_repo(repo, ref, clone_strategy) + + def make_local(self, deps): + def make_local_strategy(directory): + copy_tree_to_path(resource_filename('empty_template'), directory) + return self._new_repo( + 'local:{}'.format(','.join(sorted(deps))), 'N/A', + make_local_strategy, + ) @cached_property def cmd_runner(self): diff --git a/pre_commit/util.py b/pre_commit/util.py index 9cf4c164..73719d1b 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -204,3 +204,21 @@ def rmtree(path): else: raise shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly) + + +def copy_tree_to_path(src_dir, dest_dir): + """Copies all of the things inside src_dir to an already existing dest_dir. + + This looks eerily similar to shutil.copytree, but copytree has no option + for not creating dest_dir. + """ + names = os.listdir(src_dir) + + for name in names: + srcname = os.path.join(src_dir, name) + destname = os.path.join(dest_dir, name) + + if os.path.isdir(srcname): + shutil.copytree(srcname, destname) + else: + shutil.copy(srcname, destname) diff --git a/setup.py b/setup.py index 25ac40f2..6f7baf25 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,9 @@ setup( 'resources/rbenv.tar.gz', 'resources/ruby-build.tar.gz', 'resources/ruby-download.tar.gz', - ] + 'resources/empty_template/*', + 'resources/empty_template/.npmignore', + ], }, install_requires=[ 'aspy.yaml', diff --git a/testing/fixtures.py b/testing/fixtures.py index aaa4203d..16cda572 100644 --- a/testing/fixtures.py +++ b/testing/fixtures.py @@ -15,8 +15,8 @@ from pre_commit.clientlib.validate_config import validate_config_extra from pre_commit.clientlib.validate_manifest import load_manifest from pre_commit.jsonschema_extensions import apply_defaults from pre_commit.util import cmd_output +from pre_commit.util import copy_tree_to_path from pre_commit.util import cwd -from testing.util import copy_tree_to_path from testing.util import get_head_sha from testing.util import get_resource_path diff --git a/testing/util.py b/testing/util.py index 311376a6..4cb6f0d8 100644 --- a/testing/util.py +++ b/testing/util.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import os.path -import shutil import jsonschema import pytest @@ -20,24 +19,6 @@ def get_resource_path(path): return os.path.join(TESTING_DIR, 'resources', path) -def copy_tree_to_path(src_dir, dest_dir): - """Copies all of the things inside src_dir to an already existing dest_dir. - - This looks eerily similar to shutil.copytree, but copytree has no option - for not creating dest_dir. - """ - names = os.listdir(src_dir) - - for name in names: - srcname = os.path.join(src_dir, name) - destname = os.path.join(dest_dir, name) - - if os.path.isdir(srcname): - shutil.copytree(srcname, destname) - else: - shutil.copy(srcname, destname) - - def get_head_sha(dir): with cwd(dir): return cmd_output('git', 'rev-parse', 'HEAD')[1].strip() diff --git a/tests/manifest_test.py b/tests/manifest_test.py index e9e39dd4..58242214 100644 --- a/tests/manifest_test.py +++ b/tests/manifest_test.py @@ -12,8 +12,8 @@ from testing.util import get_head_sha def manifest(store, tempdir_factory): path = make_repo(tempdir_factory, 'script_hooks_repo') head_sha = get_head_sha(path) - repo_path_getter = store.get_repo_path_getter(path, head_sha) - yield Manifest(repo_path_getter, path) + repo_path = store.clone(path, head_sha) + yield Manifest(repo_path, path) def test_manifest_contents(manifest): @@ -54,9 +54,8 @@ def test_hooks(manifest): def test_legacy_manifest_warn(store, tempdir_factory, log_warning_mock): path = make_repo(tempdir_factory, 'legacy_hooks_yaml_repo') head_sha = get_head_sha(path) - repo_path_getter = store.get_repo_path_getter(path, head_sha) - - Manifest(repo_path_getter, path).manifest_contents + repo_path = store.clone(path, head_sha) + Manifest(repo_path, path).manifest_contents # Should have printed a warning assert log_warning_mock.call_args_list[0][0][0] == ( diff --git a/tests/repository_test.py b/tests/repository_test.py index 984973f5..bfe4517b 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -11,11 +11,10 @@ import mock import pkg_resources import pytest +from pre_commit import constants as C from pre_commit import five from pre_commit import parse_shebang -from pre_commit.clientlib.validate_config import CONFIG_JSON_SCHEMA -from pre_commit.clientlib.validate_config import validate_config_extra -from pre_commit.jsonschema_extensions import apply_defaults +from pre_commit.clientlib.validate_manifest import load_manifest from pre_commit.languages import golang from pre_commit.languages import helpers from pre_commit.languages import node @@ -30,6 +29,7 @@ from testing.fixtures import git_dir from testing.fixtures import make_config_from_repo from testing.fixtures import make_repo from testing.fixtures import modify_manifest +from testing.util import get_resource_path from testing.util import skipif_cant_run_docker from testing.util import skipif_cant_run_swift from testing.util import skipif_slowtests_false @@ -51,9 +51,9 @@ def _test_hook_repo( path = make_repo(tempdir_factory, repo_path) config = make_config_from_repo(path, **(config_kwargs or {})) repo = Repository.create(config, store) - hook_dict = [ + hook_dict, = [ hook for repo_hook_id, hook in repo.hooks if repo_hook_id == hook_id - ][0] + ] ret = repo.run_hook(hook_dict, args) assert ret[0] == expected_return_code assert ret[1].replace(b'\r\n', b'\n') == expected @@ -438,32 +438,13 @@ def test_lots_of_files(tempdir_factory, store): ) -@pytest.fixture -def mock_repo_config(): - config = { - 'repo': 'git@github.com:pre-commit/pre-commit-hooks', - 'sha': '5e713f8878b7d100c0e059f8cc34be4fc2e8f897', - 'hooks': [{ - 'id': 'pyflakes', - 'files': '\\.py$', - }], - } - config_wrapped = apply_defaults([config], CONFIG_JSON_SCHEMA) - validate_config_extra(config_wrapped) - return config_wrapped[0] - - -def test_repo_url(mock_repo_config): - repo = Repository(mock_repo_config, None) - assert repo.repo_url == 'git@github.com:pre-commit/pre-commit-hooks' - - @pytest.mark.integration -def test_languages(tempdir_factory, store): +def test_venvs(tempdir_factory, store): path = make_repo(tempdir_factory, 'python_hooks_repo') config = make_config_from_repo(path) repo = Repository.create(config, store) - assert repo.languages == {('python', 'default')} + venv, = repo._venvs + assert venv == (mock.ANY, 'python', 'default', []) @pytest.mark.integration @@ -472,7 +453,8 @@ def test_additional_dependencies(tempdir_factory, store): config = make_config_from_repo(path) config['hooks'][0]['additional_dependencies'] = ['pep8'] repo = Repository.create(config, store) - assert repo.additional_dependencies['python']['default'] == ['pep8'] + venv, = repo._venvs + assert venv == (mock.ANY, 'python', 'default', ['pep8']) @pytest.mark.integration @@ -481,11 +463,11 @@ def test_additional_dependencies_duplicated( ): path = make_repo(tempdir_factory, 'ruby_hooks_repo') config = make_config_from_repo(path) - config['hooks'][0]['additional_dependencies'] = [ - 'thread_safe', 'tins', 'thread_safe'] + deps = ['thread_safe', 'tins', 'thread_safe'] + config['hooks'][0]['additional_dependencies'] = deps repo = Repository.create(config, store) - assert repo.additional_dependencies['ruby']['default'] == [ - 'thread_safe', 'tins'] + venv, = repo._venvs + assert venv == (mock.ANY, 'ruby', 'default', ['thread_safe', 'tins']) @pytest.mark.integration @@ -495,7 +477,7 @@ def test_additional_python_dependencies_installed(tempdir_factory, store): config['hooks'][0]['additional_dependencies'] = ['mccabe'] repo = Repository.create(config, store) repo.require_installed() - with python.in_env(repo.cmd_runner, 'default'): + with python.in_env(repo._cmd_runner, 'default'): output = cmd_output('pip', 'freeze', '-l')[1] assert 'mccabe' in output @@ -512,7 +494,7 @@ def test_additional_dependencies_roll_forward(tempdir_factory, store): repo = Repository.create(config, store) repo.require_installed() # We should see our additional dependency installed - with python.in_env(repo.cmd_runner, 'default'): + with python.in_env(repo._cmd_runner, 'default'): output = cmd_output('pip', 'freeze', '-l')[1] assert 'mccabe' in output @@ -528,7 +510,7 @@ def test_additional_ruby_dependencies_installed( config['hooks'][0]['additional_dependencies'] = ['thread_safe', 'tins'] repo = Repository.create(config, store) repo.require_installed() - with ruby.in_env(repo.cmd_runner, 'default'): + with ruby.in_env(repo._cmd_runner, 'default'): output = cmd_output('gem', 'list', '--local')[1] assert 'thread_safe' in output assert 'tins' in output @@ -546,7 +528,7 @@ def test_additional_node_dependencies_installed( config['hooks'][0]['additional_dependencies'] = ['lodash'] repo = Repository.create(config, store) repo.require_installed() - with node.in_env(repo.cmd_runner, 'default'): + with node.in_env(repo._cmd_runner, 'default'): cmd_output('npm', 'config', 'set', 'global', 'true') output = cmd_output('npm', 'ls')[1] assert 'lodash' in output @@ -563,7 +545,7 @@ def test_additional_golang_dependencies_installed( config['hooks'][0]['additional_dependencies'] = deps repo = Repository.create(config, store) repo.require_installed() - binaries = os.listdir(repo.cmd_runner.path( + binaries = os.listdir(repo._cmd_runner.path( helpers.environment_dir(golang.ENVIRONMENT_DIR, 'default'), 'bin', )) # normalize for windows @@ -611,7 +593,7 @@ def test_control_c_control_c_on_install(tempdir_factory, store): repo.run_hook(hook, []) # Should have made an environment, however this environment is broken! - assert os.path.exists(repo.cmd_runner.path('py_env-default')) + assert os.path.exists(repo._cmd_runner.path('py_env-default')) # However, it should be perfectly runnable (reinstall after botched # install) @@ -684,6 +666,21 @@ def test_local_repository(): assert len(local_repo.hooks) == 1 +def test_local_python_repo(store): + # Make a "local" hooks repo that just installs our other hooks repo + repo_path = get_resource_path('python_hooks_repo') + manifest = load_manifest(os.path.join(repo_path, C.MANIFEST_FILE)) + hooks = [ + dict(hook, additional_dependencies=[repo_path]) for hook in manifest + ] + config = {'repo': 'local', 'hooks': hooks} + repo = Repository.create(config, store) + (_, hook), = repo.hooks + ret = repo.run_hook(hook, ('filename',)) + assert ret[0] == 0 + assert ret[1].replace(b'\r\n', b'\n') == b"['filename']\nHello World\n" + + @pytest.yield_fixture def fake_log_handler(): handler = mock.Mock(level=logging.INFO) @@ -699,7 +696,7 @@ def test_hook_id_not_present(tempdir_factory, store, fake_log_handler): config['hooks'][0]['id'] = 'i-dont-exist' repo = Repository.create(config, store) with pytest.raises(SystemExit): - repo.install() + repo.require_installed() assert fake_log_handler.handle.call_args[0][0].msg == ( '`i-dont-exist` is not present in repository {}. ' 'Typo? Perhaps it is introduced in a newer version? ' @@ -714,7 +711,7 @@ def test_too_new_version(tempdir_factory, store, fake_log_handler): config = make_config_from_repo(path) repo = Repository.create(config, store) with pytest.raises(SystemExit): - repo.install() + repo.require_installed() msg = fake_log_handler.handle.call_args[0][0].msg assert re.match( r'^The hook `bash_hook` requires pre-commit version 999\.0\.0 but ' @@ -734,4 +731,4 @@ def test_versions_ok(tempdir_factory, store, version): manifest[0]['minimum_pre_commit_version'] = version config = make_config_from_repo(path) # Should succeed - Repository.create(config, store).install() + Repository.create(config, store).require_installed()