diff --git a/pre_commit/clientlib/validate_manifest.py b/pre_commit/clientlib/validate_manifest.py index 4295014f..e5a6e0e3 100644 --- a/pre_commit/clientlib/validate_manifest.py +++ b/pre_commit/clientlib/validate_manifest.py @@ -25,6 +25,13 @@ MANIFEST_JSON_SCHEMA = { 'language_version': {'type': 'string', 'default': 'default'}, 'files': {'type': 'string'}, 'expected_return_value': {'type': 'number', 'default': 0}, + 'stages': { + 'type': 'array', + 'default': [], + 'items': { + 'type': 'string', + }, + }, 'args': { 'type': 'array', 'default': [], diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index d7a03c09..d4c2ad51 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -20,10 +20,11 @@ PREVIOUS_IDENTIFYING_HASHES = ( '4d9958c90bc262f47553e2c073f14cfe', 'd8ee923c46731b42cd95cc869add4062', '49fd668cb42069aa1b6048464be5d395', + '79f09a650522a87b0da915d0d983b2de' ) -IDENTIFYING_HASH = '79f09a650522a87b0da915d0d983b2de' +IDENTIFYING_HASH = 'e358c9dae00eac5d06b38dfdb1e33a8c' def is_our_pre_commit(filename): diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index cdd11f53..067fbc06 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -175,6 +175,7 @@ def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): with ctx: repo_hooks = list(get_repo_hooks(runner)) + if args.hook: repo_hooks = [ (repo, hook) for repo, hook in repo_hooks @@ -183,4 +184,11 @@ def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): if not repo_hooks: write('No hook with id `{0}`\n'.format(args.hook)) return 1 + + # Filter hooks for stages + repo_hooks = [ + (repo, hook) for repo, hook in repo_hooks + if not hook['stages'] or args.hook_stage in hook['stages'] + ] + return _run_hooks(repo_hooks, args, write, environ) diff --git a/pre_commit/main.py b/pre_commit/main.py index 25a6a3f6..cd6af081 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -87,7 +87,6 @@ def main(argv=None): run_parser.add_argument( '--verbose', '-v', action='store_true', default=False, ) - run_parser.add_argument( '--origin', '-o', help='The origin branch\'s commit_id when using `git push`', @@ -101,6 +100,10 @@ def main(argv=None): help='Allow an unstaged config to be present. Note that this will' 'be stashed before parsing unless --no-stash is specified' ) + run_parser.add_argument( + '--hook-stage', choices=('commit', 'push'), default='commit', + help='The stage during which the hook is fired e.g. commit or push' + ) run_mutex_group = run_parser.add_mutually_exclusive_group(required=False) run_mutex_group.add_argument( '--all-files', '-a', action='store_true', default=False, diff --git a/pre_commit/resources/hook-tmpl b/pre_commit/resources/hook-tmpl index e65f60e6..9256675c 100755 --- a/pre_commit/resources/hook-tmpl +++ b/pre_commit/resources/hook-tmpl @@ -1,6 +1,6 @@ #!/usr/bin/env bash # This is a randomish md5 to identify this script -# 79f09a650522a87b0da915d0d983b2de +# e358c9dae00eac5d06b38dfdb1e33a8c pushd `dirname $0` > /dev/null HERE=`pwd` diff --git a/pre_commit/resources/pre-push-tmpl b/pre_commit/resources/pre-push-tmpl index cfbba996..d92f3095 100644 --- a/pre_commit/resources/pre-push-tmpl +++ b/pre_commit/resources/pre-push-tmpl @@ -10,3 +10,5 @@ do fi fi done + +args="$args --hook-stage push" diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 78a85f13..235cc0f9 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -59,6 +59,7 @@ def _get_opts( origin='', source='', allow_unstaged_config=False, + hook_stage='commit' ): # These are mutually exclusive assert not (all_files and files) @@ -68,6 +69,7 @@ def _get_opts( color=color, verbose=verbose, hook=hook, + hook_stage=hook_stage, no_stash=no_stash, origin=origin, source=source, @@ -89,6 +91,7 @@ def _test_run(repo, options, expected_outputs, expected_ret, stage): stage_a_file() args = _get_opts(**options) ret, printed = _do_run(repo, args) + assert ret == expected_ret, (ret, expected_ret, printed) for expected_output_part in expected_outputs: assert expected_output_part in printed @@ -371,6 +374,66 @@ def test_lots_of_files(mock_out_store_directory, tempdir_factory): ) +@pytest.mark.parametrize( + ('hook_stage', 'stage_for_first_hook', 'stage_for_second_hook', + 'expected_output'), + ( + ('push', ['commit'], ['commit'], [b'', b'']), + ('push', ['commit', 'push'], ['commit', 'push'], + [b'hook 1', b'hook 2']), + ('push', [], [], [b'hook 1', b'hook 2']), + ('push', [], ['commit'], [b'hook 1', b'']), + ('push', ['push'], ['commit'], [b'hook 1', b'']), + ('push', ['commit'], ['push'], [b'', b'hook 2']), + ('commit', ['commit', 'push'], ['commit', 'push'], + [b'hook 1', b'hook 2']), + ('commit', ['commit'], ['commit'], [b'hook 1', b'hook 2']), + ('commit', [], [], [b'hook 1', b'hook 2']), + ('commit', [], ['commit'], [b'', b'hook 2']), + ('commit', ['push'], ['commit'], [b'', b'hook 2']), + ('commit', ['commit'], ['push'], [b'hook 1', b'']), + ) +) +def test_local_hook_for_stages( + repo_with_passing_hook, mock_out_store_directory, + stage_for_first_hook, + stage_for_second_hook, + hook_stage, + expected_output +): + config = OrderedDict(( + ('repo', 'local'), + ('hooks', (OrderedDict(( + ('id', 'pylint'), + ('name', 'hook 1'), + ('entry', 'python -m pylint.__main__'), + ('language', 'system'), + ('files', r'\.py$'), + ('stages', stage_for_first_hook) + )), OrderedDict(( + ('id', 'do_not_commit'), + ('name', 'hook 2'), + ('entry', 'DO NOT COMMIT'), + ('language', 'pcre'), + ('files', '^(.*)$'), + ('stages', stage_for_second_hook) + )))) + )) + add_config_to_repo(repo_with_passing_hook, config) + + with io.open('dummy.py', 'w') as staged_file: + staged_file.write('"""TODO: something"""\n') + cmd_output('git', 'add', 'dummy.py') + + _test_run( + repo_with_passing_hook, + {'hook_stage': hook_stage}, + expected_outputs=expected_output, + expected_ret=0, + stage=False + ) + + def test_local_hook_passes( repo_with_passing_hook, mock_out_store_directory, ): diff --git a/tests/manifest_test.py b/tests/manifest_test.py index 5fc226ae..7e09f338 100644 --- a/tests/manifest_test.py +++ b/tests/manifest_test.py @@ -29,6 +29,7 @@ def test_manifest_contents(manifest): 'language': 'script', 'language_version': 'default', 'name': 'Bash hook', + 'stages': [], }] @@ -44,4 +45,5 @@ def test_hooks(manifest): 'language': 'script', 'language_version': 'default', 'name': 'Bash hook', + 'stages': [], }