From ce307a16e0c31cea596c6444b54457666fa4bad6 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 6 Dec 2015 17:40:18 -0800 Subject: [PATCH] Add an option to require a specific pre-commit version --- pre_commit/clientlib/validate_manifest.py | 3 +++ pre_commit/repository.py | 17 ++++++++++++ testing/fixtures.py | 13 +++++++++ tests/manifest_test.py | 2 ++ tests/repository_test.py | 33 +++++++++++++++++++++++ 5 files changed, 68 insertions(+) diff --git a/pre_commit/clientlib/validate_manifest.py b/pre_commit/clientlib/validate_manifest.py index e69e739f..094c8e5d 100644 --- a/pre_commit/clientlib/validate_manifest.py +++ b/pre_commit/clientlib/validate_manifest.py @@ -23,6 +23,9 @@ MANIFEST_JSON_SCHEMA = { 'exclude': {'type': 'string', 'default': '^$'}, 'language': {'type': 'string'}, 'language_version': {'type': 'string', 'default': 'default'}, + 'minimum_pre_commit_version': { + 'type': 'string', 'default': '0.0.0', + }, 'files': {'type': 'string'}, 'stages': { 'type': 'array', diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 4c045c8c..41adc011 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -4,6 +4,7 @@ import logging import shutil from collections import defaultdict +import pkg_resources from cached_property import cached_property from pre_commit import git @@ -18,6 +19,10 @@ 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 +) + class Repository(object): def __init__(self, repo_config, repo_path_getter): @@ -71,6 +76,18 @@ class Repository(object): ) ) 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'] diff --git a/testing/fixtures.py b/testing/fixtures.py index 66739d42..3cc8404d 100644 --- a/testing/fixtures.py +++ b/testing/fixtures.py @@ -1,10 +1,12 @@ from __future__ import absolute_import from __future__ import unicode_literals +import contextlib import io import os.path from aspy.yaml import ordered_dump +from aspy.yaml import ordered_load import pre_commit.constants as C from pre_commit.clientlib.validate_config import CONFIG_JSON_SCHEMA @@ -35,6 +37,17 @@ def make_repo(tempdir_factory, repo_source): return path +@contextlib.contextmanager +def modify_manifest(path): + """Modify the manifest yielded by this context to write to hooks.yaml.""" + manifest_path = os.path.join(path, C.MANIFEST_FILE) + manifest = ordered_load(io.open(manifest_path).read()) + yield manifest + with io.open(manifest_path, 'w') as manifest_file: + manifest_file.write(ordered_dump(manifest, **C.YAML_DUMP_KWARGS)) + cmd_output('git', 'commit', '-am', 'update hooks.yaml', cwd=path) + + def config_with_local_hooks(): return OrderedDict(( ('repo', 'local'), diff --git a/tests/manifest_test.py b/tests/manifest_test.py index ce1beed4..f28862ae 100644 --- a/tests/manifest_test.py +++ b/tests/manifest_test.py @@ -27,6 +27,7 @@ def test_manifest_contents(manifest): 'id': 'bash_hook', 'language': 'script', 'language_version': 'default', + 'minimum_pre_commit_version': '0.0.0', 'name': 'Bash hook', 'stages': [], }] @@ -42,6 +43,7 @@ def test_hooks(manifest): 'id': 'bash_hook', 'language': 'script', 'language_version': 'default', + 'minimum_pre_commit_version': '0.0.0', 'name': 'Bash hook', 'stages': [], } diff --git a/tests/repository_test.py b/tests/repository_test.py index c91ce270..f26b6231 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -5,9 +5,11 @@ import io import logging import os import os.path +import re import shutil import mock +import pkg_resources import pytest from pre_commit import five @@ -24,6 +26,7 @@ from testing.fixtures import config_with_local_hooks 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 skipif_slowtests_false from testing.util import xfailif_no_pcre_support from testing.util import xfailif_windows_no_node @@ -525,3 +528,33 @@ def test_hook_id_not_present(tempdir_factory, store, fake_log_handler): 'Typo? Perhaps it is introduced in a newer version? ' 'Often `pre-commit autoupdate` fixes this.'.format(path) ) + + +def test_too_new_version(tempdir_factory, store, fake_log_handler): + path = make_repo(tempdir_factory, 'script_hooks_repo') + with modify_manifest(path) as manifest: + manifest[0]['minimum_pre_commit_version'] = '999.0.0' + config = make_config_from_repo(path) + repo = Repository.create(config, store) + with pytest.raises(SystemExit): + repo.install() + 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 ' + r'version \d+\.\d+\.\d+ is installed. ' + r'Perhaps run `pip install --upgrade pre-commit`\.$', + msg, + ) + + +@pytest.mark.parametrize( + 'version', + ('0.1.0', pkg_resources.get_distribution('pre-commit').version), +) +def test_versions_ok(tempdir_factory, store, version): + path = make_repo(tempdir_factory, 'script_hooks_repo') + with modify_manifest(path) as manifest: + manifest[0]['minimum_pre_commit_version'] = version + config = make_config_from_repo(path) + # Should succeed + Repository.create(config, store).install()