Merge pull request #105 from pre-commit/files_specified_in_hooks

Files specified in hooks
This commit is contained in:
Ken Struys 2014-06-06 09:58:01 -07:00
commit 7edb438d76
37 changed files with 214 additions and 180 deletions

View file

@ -1,24 +1,14 @@
- repo: git@github.com:pre-commit/pre-commit-hooks - repo: git@github.com:pre-commit/pre-commit-hooks
sha: 59f23b7c556ce1cf66eb6dc574e10d2b4274be4b sha: 7c003425b35fff516c0ee88f4040c8c208d474bd
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
files: \.(js|rb|md|py|sh|txt|yaml|yml)$
- id: end-of-file-fixer - id: end-of-file-fixer
files: \.(js|rb|md|py|sh|txt|yaml|yml)$
- id: check-yaml - id: check-yaml
files: \.(yaml|yml)$
- id: debug-statements - id: debug-statements
files: \.py$
- id: name-tests-test - id: name-tests-test
files: tests/.+\.py$
- id: flake8 - id: flake8
files: \.py$
args:
- --max-line-length=131
- repo: git@github.com:pre-commit/pre-commit - repo: git@github.com:pre-commit/pre-commit
sha: c7cfed699245191e6b0d43d3890fd582157e3919 sha: 96174deac671b451ee2a3fbdc647ad9606415e15
hooks: hooks:
- id: validate_config - id: validate_config
files: ^\.pre-commit-config.yaml$
- id: validate_manifest - id: validate_manifest
files: ^hooks.yaml$

View file

@ -15,12 +15,13 @@
- id: my_hook - id: my_hook
name: My Simple Hook name: My Simple Hook
description: This is my simple hook that does blah description: This is my simple hook that does blah
entry: my-simple-hook.py entry: my-simple-hook
language: python language: python
expected_return_value: 0 files: \.py$
- id: my_grep_based_hook - id: my_grep_based_hook
name: My Bash Based Hook name: My Bash Based Hook
description: This is a hook that uses grep to validate some stuff description: This is a hook that uses grep to validate some stuff
entry: ./my_grep_based_hook.sh entry: ./my_grep_based_hook.sh
language: script language: script
files: \.(py|sh)$
expected_return_value: 1 expected_return_value: 1

View file

@ -2,8 +2,5 @@
sha: cd74dc150c142c3be70b24eaf0b02cae9d235f37 sha: cd74dc150c142c3be70b24eaf0b02cae9d235f37
hooks: hooks:
- id: pyflakes - id: pyflakes
files: '\.py$'
- id: jslint - id: jslint
files: '\.js$'
- id: trim_trailing_whitespace - id: trim_trailing_whitespace
files: '\.py$'

View file

@ -1,10 +1,12 @@
- id: validate_manifest
name: Validate Pre-Commit Manifest
description: This validator validates a pre-commit hooks manifest file
entry: validate-manifest
language: python
- id: validate_config - id: validate_config
name: Validate Pre-Commit Config name: Validate Pre-Commit Config
description: This validator validates a pre-commit hooks config file description: This validator validates a pre-commit hooks config file
entry: validate-config entry: validate-config
language: python language: python
files: ^\.pre-commit-config.yaml$
- id: validate_manifest
name: Validate Pre-Commit Manifest
description: This validator validates a pre-commit hooks manifest file
entry: validate-manifest
language: python
files: ^hooks.yaml$

View file

@ -4,12 +4,21 @@ import argparse
import jsonschema import jsonschema
import jsonschema.exceptions import jsonschema.exceptions
import os.path import os.path
import re
import yaml import yaml
from pre_commit.jsonschema_extensions import apply_defaults from pre_commit.jsonschema_extensions import apply_defaults
from pre_commit.util import entry from pre_commit.util import entry
def is_regex_valid(regex):
try:
re.compile(regex)
return True
except re.error:
return False
def get_validator( def get_validator(
json_schema, json_schema,
exception_type, exception_type,

View file

@ -1,8 +1,8 @@
import re
import sys import sys
from pre_commit.clientlib.validate_base import get_run_function from pre_commit.clientlib.validate_base import get_run_function
from pre_commit.clientlib.validate_base import get_validator from pre_commit.clientlib.validate_base import get_validator
from pre_commit.clientlib.validate_base import is_regex_valid
class InvalidConfigError(ValueError): class InvalidConfigError(ValueError):
@ -32,7 +32,7 @@ CONFIG_JSON_SCHEMA = {
'items': {'type': 'string'}, 'items': {'type': 'string'},
}, },
}, },
'required': ['id', 'files'], 'required': ['id'],
} }
} }
}, },
@ -42,9 +42,7 @@ CONFIG_JSON_SCHEMA = {
def try_regex(repo, hook, value, field_name): def try_regex(repo, hook, value, field_name):
try: if not is_regex_valid(value):
re.compile(value)
except re.error:
raise InvalidConfigError( raise InvalidConfigError(
'Invalid {0} regex at {1}, {2}: {3}'.format( 'Invalid {0} regex at {1}, {2}: {3}'.format(
field_name, repo, hook, value, field_name, repo, hook, value,
@ -55,7 +53,7 @@ def try_regex(repo, hook, value, field_name):
def validate_config_extra(config): def validate_config_extra(config):
for repo in config: for repo in config:
for hook in repo['hooks']: for hook in repo['hooks']:
try_regex(repo, hook['id'], hook['files'], 'files') try_regex(repo, hook['id'], hook.get('files', ''), 'files')
try_regex(repo, hook['id'], hook['exclude'], 'exclude') try_regex(repo, hook['id'], hook['exclude'], 'exclude')

View file

@ -2,6 +2,7 @@ import sys
from pre_commit.clientlib.validate_base import get_run_function from pre_commit.clientlib.validate_base import get_run_function
from pre_commit.clientlib.validate_base import get_validator from pre_commit.clientlib.validate_base import get_validator
from pre_commit.clientlib.validate_base import is_regex_valid
from pre_commit.languages.all import all_languages from pre_commit.languages.all import all_languages
@ -21,25 +22,39 @@ MANIFEST_JSON_SCHEMA = {
'entry': {'type': 'string'}, 'entry': {'type': 'string'},
'language': {'type': 'string'}, 'language': {'type': 'string'},
'language_version': {'type': 'string', 'default': 'default'}, 'language_version': {'type': 'string', 'default': 'default'},
'files': {'type': 'string'},
'expected_return_value': {'type': 'number', 'default': 0}, 'expected_return_value': {'type': 'number', 'default': 0},
}, },
'required': ['id', 'name', 'entry', 'language'], 'required': ['id', 'name', 'entry', 'language', 'files'],
}, },
} }
def validate_languages(hook_config):
if hook_config['language'] not in all_languages:
raise InvalidManifestError(
'Expected language {0} for {1} to be one of {2!r}'.format(
hook_config['id'],
hook_config['language'],
all_languages,
)
)
def validate_files(hook_config):
if not is_regex_valid(hook_config['files']):
raise InvalidManifestError(
'Invalid files regex at {0}: {1}'.format(
hook_config['id'],
hook_config['files'],
)
)
def additional_manifest_check(obj): def additional_manifest_check(obj):
for hook_config in obj: for hook_config in obj:
language = hook_config['language'] validate_languages(hook_config)
validate_files(hook_config)
if language not in all_languages:
raise InvalidManifestError(
'Expected language {0} for {1} to be one of {2!r}'.format(
hook_config['id'],
hook_config['language'],
all_languages,
)
)
load_manifest = get_validator( load_manifest = get_validator(

View file

@ -29,7 +29,9 @@ logger = logging.getLogger('pre_commit')
def install(runner): def install(runner):
"""Install the pre-commit hooks.""" """Install the pre-commit hooks."""
pre_commit_file = pkg_resources.resource_filename('pre_commit', 'resources/pre-commit.sh') pre_commit_file = pkg_resources.resource_filename(
'pre_commit', 'resources/pre-commit.sh',
)
with open(runner.pre_commit_path, 'w') as pre_commit_file_obj: with open(runner.pre_commit_path, 'w') as pre_commit_file_obj:
pre_commit_file_obj.write(open(pre_commit_file).read()) pre_commit_file_obj.write(open(pre_commit_file).read())

View file

@ -8,71 +8,3 @@ if PY2:
text = unicode # flake8: noqa text = unicode # flake8: noqa
else: else:
text = str text = str
def n(obj):
"""Produce a native string.
Similar in behavior to str(), but uses US-ASCII encoding when necessary.
"""
if isinstance(obj, str):
return obj
elif PY2 and isinstance(obj, text):
return obj.encode('US-ASCII')
elif PY3 and isinstance(obj, bytes):
return obj.decode('US-ASCII')
else:
return str(obj)
def u(obj):
"""Produces text.
Similar in behavior to str() in python3 or unicode() in python2,
but uses US-ASCII encoding when necessary.
"""
if isinstance(obj, text):
return obj
elif isinstance(obj, bytes):
return obj.decode('US-ASCII')
else:
return text(obj)
def b(obj):
"""Produces bytes.
Similar in behavior to bytes(), but uses US-ASCII encoding when necessary.
"""
if isinstance(obj, bytes):
return obj
elif isinstance(obj, text):
return obj.encode('US-ASCII')
else:
return bytes(obj)
def udict(*args, **kwargs):
"""Similar to dict(), but keyword-keys are text."""
kwargs = dict([
(u(key), val)
for key, val in kwargs.items()
])
return dict(*args, **kwargs)
def ndict(*args, **kwargs):
"""Similar to dict(), but keyword-keys are forced to native strings."""
# I hate this :(
kwargs = dict([
(n(key), val)
for key, val in kwargs.items()
])
return dict(*args, **kwargs)
def open(*args, **kwargs):
"""Override the builtin open() to return text and use utf8 by default."""
from io import open
kwargs.setdefault('encoding', 'UTF-8')
return open(*args, **kwargs)

View file

@ -29,7 +29,9 @@ def is_in_merge_conflict():
def parse_merge_msg_for_conflicts(merge_msg): def parse_merge_msg_for_conflicts(merge_msg):
# Conflicted files start with tabs # Conflicted files start with tabs
return [ return [
line.strip() for line in merge_msg.splitlines() if line.startswith('\t') line.strip()
for line in merge_msg.splitlines()
if line.startswith('\t')
] ]

View file

@ -9,16 +9,19 @@ from pre_commit.languages import system
# # Use None for no environment # # Use None for no environment
# ENVIRONMENT_DIR = 'foo_env' # ENVIRONMENT_DIR = 'foo_env'
# #
# def install_environment(repo_cmd_runner): # def install_environment(repo_cmd_runner, version='default'):
# """Installs a repository in the given repository. Note that the current # """Installs a repository in the given repository. Note that the current
# working directory will already be inside the repository. # working directory will already be inside the repository.
# #
# Args: # Args:
# repo_cmd_runner - `PrefixedCommandRunner` bound to the repository. # repo_cmd_runner - `PrefixedCommandRunner` bound to the repository.
# version - A version specified in the hook configuration or
# 'default'.
# """ # """
# #
# def run_hook(repo_cmd_runner, hook, file_args): # def run_hook(repo_cmd_runner, hook, file_args):
# """Runs a hook and returns the returncode and output of running that hook. # """Runs a hook and returns the returncode and output of running that
# hook.
# #
# Args: # Args:
# repo_cmd_runner - `PrefixedCommandRunner` bound to the repository. # repo_cmd_runner - `PrefixedCommandRunner` bound to the repository.

View file

@ -26,8 +26,8 @@ def get_hook_message(
>>> print_hook_message('start', end_len=6) >>> print_hook_message('start', end_len=6)
start............................................................... start...............................................................
# Print `start` followed by dots with the end message colored if coloring is # Print `start` followed by dots with the end message colored if coloring
# specified and a newline afterwards # is specified and a newline afterwards
>>> print_hook_message( >>> print_hook_message(
'start', 'start',
end_msg='end', end_msg='end',

View file

@ -2,8 +2,6 @@ import os
import os.path import os.path
import subprocess import subprocess
from pre_commit import five
class CalledProcessError(RuntimeError): class CalledProcessError(RuntimeError):
def __init__(self, returncode, cmd, expected_returncode, output=None): def __init__(self, returncode, cmd, expected_returncode, output=None):
@ -42,7 +40,12 @@ class PrefixedCommandRunner(object):
will run ['/tmp/foo/foo.sh', 'bar', 'baz'] will run ['/tmp/foo/foo.sh', 'bar', 'baz']
""" """
def __init__(self, prefix_dir, popen=subprocess.Popen, makedirs=os.makedirs): def __init__(
self,
prefix_dir,
popen=subprocess.Popen,
makedirs=os.makedirs
):
self.prefix_dir = prefix_dir.rstrip(os.sep) + os.sep self.prefix_dir = prefix_dir.rstrip(os.sep) + os.sep
self.__popen = popen self.__popen = popen
self.__makedirs = makedirs self.__makedirs = makedirs
@ -65,11 +68,10 @@ class PrefixedCommandRunner(object):
replaced_cmd = _replace_cmd(cmd, prefix=self.prefix_dir) replaced_cmd = _replace_cmd(cmd, prefix=self.prefix_dir)
proc = self.__popen(replaced_cmd, **popen_kwargs) proc = self.__popen(replaced_cmd, **popen_kwargs)
stdout, stderr = proc.communicate(stdin) stdout, stderr = proc.communicate(stdin)
# TODO: stdout, stderr = from_bytes(stdout), from_bytes(stderr)
if isinstance(stdout, bytes): if isinstance(stdout, bytes):
stdout = five.text(stdout, 'utf-8') stdout = stdout.decode('UTF-8')
if isinstance(stderr, bytes): if isinstance(stderr, bytes):
stderr = five.text(stderr, 'utf-8') stderr = stderr.decode('UTF-8')
returncode = proc.returncode returncode = proc.returncode
if retcode is not None and retcode != returncode: if retcode is not None and retcode != returncode:

View file

@ -38,7 +38,7 @@ class Repository(object):
def hooks(self): def hooks(self):
# TODO: merging in manifest dicts is a smell imo # TODO: merging in manifest dicts is a smell imo
return OrderedDict( return OrderedDict(
(hook['id'], dict(hook, **self.manifest.hooks[hook['id']])) (hook['id'], dict(self.manifest.hooks[hook['id']], **hook))
for hook in self.repo_config['hooks'] for hook in self.repo_config['hooks']
) )

View file

@ -35,9 +35,13 @@ def run(argv):
'--no-stash', default=False, action='store_true', '--no-stash', default=False, action='store_true',
help='Use this option to prevent auto stashing of unstaged files.', help='Use this option to prevent auto stashing of unstaged files.',
) )
run_parser.add_argument('--verbose', '-v', action='store_true', default=False) run_parser.add_argument(
'--verbose', '-v', action='store_true', default=False,
)
help = subparsers.add_parser('help', help='Show help for a specific command.') help = subparsers.add_parser(
'help', help='Show help for a specific command.'
)
help.add_argument('help_cmd', nargs='?', help='Command to show help for.') help.add_argument('help_cmd', nargs='?', help='Command to show help for.')
# Argparse doesn't really provide a way to use a `default` subparser # Argparse doesn't really provide a way to use a `default` subparser

View file

@ -4,7 +4,10 @@ from setuptools import setup
setup( setup(
name='pre_commit', name='pre_commit',
description='A framework for managing and maintaining multi-language pre-commit hooks.', description=(
'A framework for managing and maintaining multi-language pre-commit '
'hooks.'
),
url='https://github.com/pre-commit/pre-commit', url='https://github.com/pre-commit/pre-commit',
version='0.0.0', version='0.0.0',

View file

@ -2,19 +2,12 @@
sha: bec87f6c87284ea15dbcf7801810404c8036bab4 sha: bec87f6c87284ea15dbcf7801810404c8036bab4
hooks: hooks:
- id: pyflakes - id: pyflakes
files: \.py$
- id: debug-statements - id: debug-statements
files: \.py$
- id: trailing-whitespace - id: trailing-whitespace
files: \.(py|sh|yaml)$
- id: name-tests-test - id: name-tests-test
files: tests/.+\.py$
- id: end-of-file-fixer - id: end-of-file-fixer
files: \.(py|sh|yaml)$
- repo: git@github.com:pre-commit/pre-commit - repo: git@github.com:pre-commit/pre-commit
sha: c62c1a3b513ab9e057e85a5e950bd7c438371076 sha: c62c1a3b513ab9e057e85a5e950bd7c438371076
hooks: hooks:
- id: validate_manifest - id: validate_manifest
files: ^hooks.yaml$
- id: validate_config - id: validate_config
files: ^\.pre-commit-config.yaml$

View file

@ -2,3 +2,4 @@
name: Failing hook name: Failing hook
entry: bin/hook.sh entry: bin/hook.sh
language: script language: script
files: .

View file

@ -2,3 +2,4 @@
name: Bar name: Bar
entry: bar entry: bar
language: python language: python
files: \.py$

View file

@ -3,3 +3,4 @@
entry: node-11-8-hook entry: node-11-8-hook
language: node language: node
language_version: 0.11.8 language_version: 0.11.8
files: \.js$

View file

@ -2,3 +2,4 @@
name: Foo name: Foo
entry: foo entry: foo
language: node language: node
files: \.js$

View file

@ -2,3 +2,4 @@
name: Prints Cwd name: Prints Cwd
entry: pwd entry: pwd
language: system language: system
files: \.sh$

View file

@ -3,3 +3,4 @@
entry: python3-hook entry: python3-hook
language: python language: python
language_version: python3.3 language_version: python3.3
files: \.py$

View file

@ -2,3 +2,4 @@
name: Foo name: Foo
entry: foo entry: foo
language: python language: python
files: \.py$

View file

@ -3,3 +3,4 @@
entry: ruby_hook entry: ruby_hook
language: ruby language: ruby
language_version: 1.9.3-p547 language_version: 1.9.3-p547
files: \.rb$

View file

@ -2,3 +2,4 @@
name: Ruby Hook name: Ruby Hook
entry: ruby_hook entry: ruby_hook
language: ruby language: ruby
files: \.rb$

View file

@ -2,3 +2,4 @@
name: Bash hook name: Bash hook
entry: bin/hook.sh entry: bin/hook.sh
language: script language: script
files: ''

View file

@ -2,3 +2,4 @@
name: System hook with spaces name: System hook with spaces
entry: /usr/bin/python -c 'import sys; print("Hello World")' entry: /usr/bin/python -c 'import sys; print("Hello World")'
language: system language: system
files: \.sh$

View file

@ -3,4 +3,3 @@
- id: pyflakes - id: pyflakes
- id: jslint - id: jslint
- id: trim_trailing_whitespace - id: trim_trailing_whitespace
files: '*.py'

View file

@ -44,7 +44,9 @@ def test_raises_for_invalid_yaml_file(noop_validator):
def test_raises_for_failing_schema(array_validator): def test_raises_for_failing_schema(array_validator):
with pytest.raises(ValueError): with pytest.raises(ValueError):
array_validator(get_resource_path('valid_yaml_but_invalid_manifest.yaml')) array_validator(
get_resource_path('valid_yaml_but_invalid_manifest.yaml')
)
def test_passes_array_schema(array_validator): def test_passes_array_schema(array_validator):

View file

@ -28,38 +28,57 @@ def test_additional_manifest_check_raises_for_bad_language():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'obj', ([{'language': 'python'}], [{'language': 'ruby'}]), 'obj',
(
[{'language': 'python', 'files': ''}],
[{'language': 'ruby', 'files': ''}]
),
) )
def test_additional_manifest_check_languages(obj): def test_additional_manifest_check_passing(obj):
additional_manifest_check(obj) additional_manifest_check(obj)
@pytest.mark.parametrize( @pytest.mark.parametrize(
'obj', 'obj',
( (
[{'id': 'a', 'language': 'not a language'}], [{'id': 'a', 'language': 'not a language', 'files': ''}],
[{'id': 'a', 'language': 'python3'}], [{'id': 'a', 'language': 'python3', 'files': ''}],
[{'id': 'a', 'language': 'python', 'files': 'invalid regex('}],
), ),
) )
def test_additional_manifest_check_languages_failing(obj): def test_additional_manifest_failing(obj):
with pytest.raises(InvalidManifestError): with pytest.raises(InvalidManifestError):
additional_manifest_check(obj) additional_manifest_check(obj)
@pytest.mark.parametrize(('manifest_obj', 'expected'), ( @pytest.mark.parametrize(
([], False), ('manifest_obj', 'expected'),
([{'id': 'a', 'name': 'b', 'entry': 'c', 'language': 'python'}], True),
( (
[{ ([], False),
'id': 'a', (
'name': 'b', [{
'entry': 'c', 'id': 'a',
'language': 'python', 'name': 'b',
'expected_return_value': 0, 'entry': 'c',
}], 'language': 'python',
True, 'files': r'\.py$'
), }],
)) True,
),
(
[{
'id': 'a',
'name': 'b',
'entry': 'c',
'language': 'python',
'language_version': 'python3.3',
'files': r'\.py$',
'expected_return_value': 0,
}],
True,
),
)
)
def test_is_valid_according_to_schema(manifest_obj, expected): def test_is_valid_according_to_schema(manifest_obj, expected):
ret = is_valid_according_to_schema(manifest_obj, MANIFEST_JSON_SCHEMA) ret = is_valid_according_to_schema(manifest_obj, MANIFEST_JSON_SCHEMA)
assert ret is expected assert ret is expected

View file

@ -31,7 +31,9 @@ def test_install_pre_commit(empty_git_dir):
assert ret == 0 assert ret == 0
assert os.path.exists(runner.pre_commit_path) assert os.path.exists(runner.pre_commit_path)
pre_commit_contents = open(runner.pre_commit_path).read() pre_commit_contents = open(runner.pre_commit_path).read()
pre_commit_sh = pkg_resources.resource_filename('pre_commit', 'resources/pre-commit.sh') pre_commit_sh = pkg_resources.resource_filename(
'pre_commit', 'resources/pre-commit.sh',
)
expected_contents = open(pre_commit_sh).read() expected_contents = open(pre_commit_sh).read()
assert pre_commit_contents == expected_contents assert pre_commit_contents == expected_contents
stat_result = os.stat(runner.pre_commit_path) stat_result = os.stat(runner.pre_commit_path)
@ -58,7 +60,7 @@ def up_to_date_repo(python_hooks_repo):
config = OrderedDict(( config = OrderedDict((
('repo', python_hooks_repo), ('repo', python_hooks_repo),
('sha', get_head_sha(python_hooks_repo)), ('sha', get_head_sha(python_hooks_repo)),
('hooks', [OrderedDict((('id', 'foo'), ('files', '')))]), ('hooks', [OrderedDict((('id', 'foo'),))]),
)) ))
wrapped_config = apply_defaults([config], CONFIG_JSON_SCHEMA) wrapped_config = apply_defaults([config], CONFIG_JSON_SCHEMA)
validate_config_extra(wrapped_config) validate_config_extra(wrapped_config)
@ -132,7 +134,9 @@ def test_removes_defaults(out_of_date_repo, runner_with_mocked_store):
assert 'expected_return_value' not in ret['hooks'][0] assert 'expected_return_value' not in ret['hooks'][0]
def test_autoupdate_out_of_date_repo(out_of_date_repo, mock_out_store_directory): def test_autoupdate_out_of_date_repo(
out_of_date_repo, mock_out_store_directory
):
before = open(C.CONFIG_FILE).read() before = open(C.CONFIG_FILE).read()
runner = Runner(out_of_date_repo.python_hooks_repo) runner = Runner(out_of_date_repo.python_hooks_repo)
ret = commands.autoupdate(runner) ret = commands.autoupdate(runner)
@ -147,12 +151,15 @@ def hook_disappearing_repo(python_hooks_repo):
config = OrderedDict(( config = OrderedDict((
('repo', python_hooks_repo), ('repo', python_hooks_repo),
('sha', get_head_sha(python_hooks_repo)), ('sha', get_head_sha(python_hooks_repo)),
('hooks', [OrderedDict((('id', 'foo'), ('files', '')))]), ('hooks', [OrderedDict((('id', 'foo'),))]),
)) ))
config_wrapped = apply_defaults([config], CONFIG_JSON_SCHEMA) config_wrapped = apply_defaults([config], CONFIG_JSON_SCHEMA)
validate_config_extra(config_wrapped) validate_config_extra(config_wrapped)
config = config_wrapped[0] config = config_wrapped[0]
shutil.copy(get_resource_path('manifest_without_foo.yaml'), C.MANIFEST_FILE) shutil.copy(
get_resource_path('manifest_without_foo.yaml'),
C.MANIFEST_FILE,
)
local['git']['add', '.']() local['git']['add', '.']()
local['git']['commit', '-m', 'Remove foo']() local['git']['commit', '-m', 'Remove foo']()
@ -167,14 +174,18 @@ def hook_disappearing_repo(python_hooks_repo):
) )
def test_hook_disppearing_repo_raises(hook_disappearing_repo, runner_with_mocked_store): def test_hook_disppearing_repo_raises(
hook_disappearing_repo, runner_with_mocked_store
):
with pytest.raises(commands.RepositoryCannotBeUpdatedError): with pytest.raises(commands.RepositoryCannotBeUpdatedError):
commands._update_repository( commands._update_repository(
hook_disappearing_repo.repo_config, runner_with_mocked_store, hook_disappearing_repo.repo_config, runner_with_mocked_store,
) )
def test_autoupdate_hook_disappearing_repo(hook_disappearing_repo, mock_out_store_directory): def test_autoupdate_hook_disappearing_repo(
hook_disappearing_repo, mock_out_store_directory
):
before = open(C.CONFIG_FILE).read() before = open(C.CONFIG_FILE).read()
runner = Runner(hook_disappearing_repo.python_hooks_repo) runner = Runner(hook_disappearing_repo.python_hooks_repo)
ret = commands.autoupdate(runner) ret = commands.autoupdate(runner)
@ -206,7 +217,13 @@ def get_write_mock_output(write_mock):
return ''.join(call[0][0] for call in write_mock.call_args_list) return ''.join(call[0][0] for call in write_mock.call_args_list)
def _get_opts(all_files=False, color=False, verbose=False, hook=None, no_stash=False): def _get_opts(
all_files=False,
color=False,
verbose=False,
hook=None,
no_stash=False,
):
return auto_namedtuple( return auto_namedtuple(
all_files=all_files, all_files=all_files,
color=color, color=color,
@ -234,7 +251,9 @@ def _test_run(repo, options, expected_outputs, expected_ret, stage):
assert expected_output_part in printed assert expected_output_part in printed
def test_run_all_hooks_failing(repo_with_failing_hook, mock_out_store_directory): def test_run_all_hooks_failing(
repo_with_failing_hook, mock_out_store_directory
):
_test_run( _test_run(
repo_with_failing_hook, repo_with_failing_hook,
{}, {},
@ -262,7 +281,14 @@ def test_run_all_hooks_failing(repo_with_failing_hook, mock_out_store_directory)
({}, ('Bash hook', '(no files to check)', 'Skipped'), 0, False), ({}, ('Bash hook', '(no files to check)', 'Skipped'), 0, False),
) )
) )
def test_run(repo_with_passing_hook, options, outputs, expected_ret, stage, mock_out_store_directory): def test_run(
repo_with_passing_hook,
options,
outputs,
expected_ret,
stage,
mock_out_store_directory,
):
_test_run(repo_with_passing_hook, options, outputs, expected_ret, stage) _test_run(repo_with_passing_hook, options, outputs, expected_ret, stage)
@ -275,7 +301,13 @@ def test_run(repo_with_passing_hook, options, outputs, expected_ret, stage, mock
(False, False, True), (False, False, True),
), ),
) )
def test_no_stash(repo_with_passing_hook, no_stash, all_files, expect_stash, mock_out_store_directory): def test_no_stash(
repo_with_passing_hook,
no_stash,
all_files,
expect_stash,
mock_out_store_directory,
):
stage_a_file() stage_a_file()
# Make unstaged changes # Make unstaged changes
with open('foo.py', 'w') as foo_file: with open('foo.py', 'w') as foo_file:
@ -347,11 +379,15 @@ def test_skip_hook(repo_with_passing_hook, mock_out_store_directory):
assert msg in printed assert msg in printed
def test_hook_id_not_in_non_verbose_output(repo_with_passing_hook, mock_out_store_directory): def test_hook_id_not_in_non_verbose_output(
repo_with_passing_hook, mock_out_store_directory
):
ret, printed = _do_run(repo_with_passing_hook, _get_opts(verbose=False)) ret, printed = _do_run(repo_with_passing_hook, _get_opts(verbose=False))
assert '[bash_hook]' not in printed assert '[bash_hook]' not in printed
def test_hook_id_in_verbose_output(repo_with_passing_hook, mock_out_store_directory): def test_hook_id_in_verbose_output(
repo_with_passing_hook, mock_out_store_directory
):
ret, printed = _do_run(repo_with_passing_hook, _get_opts(verbose=True)) ret, printed = _do_run(repo_with_passing_hook, _get_opts(verbose=True))
assert '[bash_hook] Bash hook' in printed assert '[bash_hook] Bash hook' in printed

View file

@ -122,11 +122,11 @@ def system_hook_with_spaces_repo(dummy_git_repo):
yield _make_repo(dummy_git_repo, 'system_hook_with_spaces_repo') yield _make_repo(dummy_git_repo, 'system_hook_with_spaces_repo')
def _make_config(path, hook_id, file_regex): def _make_config(path, hook_id):
config = { config = {
'repo': path, 'repo': path,
'sha': get_head_sha(path), 'sha': get_head_sha(path),
'hooks': [{'id': hook_id, 'files': file_regex}], 'hooks': [{'id': hook_id}],
} }
config_wrapped = apply_defaults([config], CONFIG_JSON_SCHEMA) config_wrapped = apply_defaults([config], CONFIG_JSON_SCHEMA)
validate_config_extra(config_wrapped) validate_config_extra(config_wrapped)
@ -135,48 +135,48 @@ def _make_config(path, hook_id, file_regex):
@pytest.yield_fixture @pytest.yield_fixture
def config_for_node_hooks_repo(node_hooks_repo): def config_for_node_hooks_repo(node_hooks_repo):
yield _make_config(node_hooks_repo, 'foo', '\\.js$') yield _make_config(node_hooks_repo, 'foo')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_node_0_11_8_hooks_repo(node_0_11_8_hooks_repo): def config_for_node_0_11_8_hooks_repo(node_0_11_8_hooks_repo):
yield _make_config(node_0_11_8_hooks_repo, 'node-11-8-hook', '\\.js$') yield _make_config(node_0_11_8_hooks_repo, 'node-11-8-hook')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_ruby_hooks_repo(ruby_hooks_repo): def config_for_ruby_hooks_repo(ruby_hooks_repo):
yield _make_config(ruby_hooks_repo, 'ruby_hook', '\\.rb$') yield _make_config(ruby_hooks_repo, 'ruby_hook')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_ruby_1_9_3_p547_hooks_repo(ruby_1_9_3_p547_hooks_repo): def config_for_ruby_1_9_3_p547_hooks_repo(ruby_1_9_3_p547_hooks_repo):
yield _make_config(ruby_1_9_3_p547_hooks_repo, 'ruby_hook', '\\.rb$') yield _make_config(ruby_1_9_3_p547_hooks_repo, 'ruby_hook')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_python_hooks_repo(python_hooks_repo): def config_for_python_hooks_repo(python_hooks_repo):
yield _make_config(python_hooks_repo, 'foo', '\\.py$') yield _make_config(python_hooks_repo, 'foo')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_python3_hooks_repo(python3_hooks_repo): def config_for_python3_hooks_repo(python3_hooks_repo):
yield _make_config(python3_hooks_repo, 'python3-hook', '\\.py$') yield _make_config(python3_hooks_repo, 'python3-hook')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_prints_cwd_repo(prints_cwd_repo): def config_for_prints_cwd_repo(prints_cwd_repo):
yield _make_config(prints_cwd_repo, 'prints_cwd', '^$') yield _make_config(prints_cwd_repo, 'prints_cwd')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_script_hooks_repo(script_hooks_repo): def config_for_script_hooks_repo(script_hooks_repo):
yield _make_config(script_hooks_repo, 'bash_hook', '') yield _make_config(script_hooks_repo, 'bash_hook')
@pytest.yield_fixture @pytest.yield_fixture
def config_for_system_hook_with_spaces(system_hook_with_spaces_repo): def config_for_system_hook_with_spaces(system_hook_with_spaces_repo):
yield _make_config( yield _make_config(
system_hook_with_spaces_repo, 'system-hook-with-spaces', '', system_hook_with_spaces_repo, 'system-hook-with-spaces',
) )
@ -198,7 +198,7 @@ def repo_with_passing_hook(config_for_script_hooks_repo, empty_git_dir):
@pytest.yield_fixture @pytest.yield_fixture
def repo_with_failing_hook(failing_hook_repo, empty_git_dir): def repo_with_failing_hook(failing_hook_repo, empty_git_dir):
_make_repo_from_configs(_make_config(failing_hook_repo, 'failing_hook', '')) _make_repo_from_configs(_make_config(failing_hook_repo, 'failing_hook'))
yield empty_git_dir yield empty_git_dir

View file

@ -17,6 +17,7 @@ def test_manifest_contents(manifest):
'description': '', 'description': '',
'entry': 'bin/hook.sh', 'entry': 'bin/hook.sh',
'expected_return_value': 0, 'expected_return_value': 0,
'files': '',
'id': 'bash_hook', 'id': 'bash_hook',
'language': 'script', 'language': 'script',
'language_version': 'default', 'language_version': 'default',
@ -29,6 +30,7 @@ def test_hooks(manifest):
'description': '', 'description': '',
'entry': 'bin/hook.sh', 'entry': 'bin/hook.sh',
'expected_return_value': 0, 'expected_return_value': 0,
'files': '',
'id': 'bash_hook', 'id': 'bash_hook',
'language': 'script', 'language': 'script',
'language_version': 'default', 'language_version': 'default',

View file

@ -56,7 +56,9 @@ def test_init_normalizes_path_endings(input, expected_prefix):
def test_run_substitutes_prefix(popen_mock, makedirs_mock): def test_run_substitutes_prefix(popen_mock, makedirs_mock):
instance = PrefixedCommandRunner('prefix', popen=popen_mock, makedirs=makedirs_mock) instance = PrefixedCommandRunner(
'prefix', popen=popen_mock, makedirs=makedirs_mock,
)
ret = instance.run(['{prefix}bar', 'baz'], retcode=None) ret = instance.run(['{prefix}bar', 'baz'], retcode=None)
popen_mock.assert_called_once_with( popen_mock.assert_called_once_with(
['prefix/bar', 'baz'], ['prefix/bar', 'baz'],
@ -104,7 +106,9 @@ def test_from_command_runner(prefix, path_end, expected_output):
def test_from_command_runner_preserves_popen(popen_mock, makedirs_mock): def test_from_command_runner_preserves_popen(popen_mock, makedirs_mock):
first = PrefixedCommandRunner('foo', popen=popen_mock, makedirs=makedirs_mock) first = PrefixedCommandRunner(
'foo', popen=popen_mock, makedirs=makedirs_mock,
)
second = PrefixedCommandRunner.from_command_runner(first, 'bar') second = PrefixedCommandRunner.from_command_runner(first, 'bar')
second.run(['foo/bar/baz'], retcode=None) second.run(['foo/bar/baz'], retcode=None)
popen_mock.assert_called_once_with( popen_mock.assert_called_once_with(

View file

@ -32,6 +32,7 @@ def test_run_versioned_hook(config_for_python3_hooks_repo, store):
assert ret[1] == "3.3\n['/dev/null']\nHello World\n" assert ret[1] == "3.3\n['/dev/null']\nHello World\n"
@skipif_slowtests_false
@pytest.mark.integration @pytest.mark.integration
def test_run_versioned_node_hook(config_for_node_0_11_8_hooks_repo, store): def test_run_versioned_node_hook(config_for_node_0_11_8_hooks_repo, store):
repo = Repository.create(config_for_node_0_11_8_hooks_repo, store) repo = Repository.create(config_for_node_0_11_8_hooks_repo, store)
@ -152,3 +153,13 @@ def test_really_long_file_paths(config_for_python_hooks_repo, store):
with local.cwd(path): with local.cwd(path):
repo = Repository.create(config_for_python_hooks_repo, store) repo = Repository.create(config_for_python_hooks_repo, store)
repo.require_installed() repo.require_installed()
@pytest.mark.integration
def test_config_overrides_repo_specifics(config_for_script_hooks_repo, store):
repo = Repository.create(config_for_script_hooks_repo, store)
assert repo.hooks['bash_hook']['files'] == ''
# Set the file regex to something else
config_for_script_hooks_repo['hooks'][0]['files'] = '\\.sh$'
repo = Repository.create(config_for_script_hooks_repo, store)
assert repo.hooks['bash_hook']['files'] == '\\.sh$'

View file

@ -23,6 +23,3 @@ deps =
sphinx sphinx
changedir = docs changedir = docs
commands = sphinx-build -b html -d build/doctrees source build/html commands = sphinx-build -b html -d build/doctrees source build/html
[flake8]
max-line-length=131