From 744b883b0181ca84e0429b4c4c6eace12099f257 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 19 Apr 2014 11:00:38 -0700 Subject: [PATCH] Add mechanism to skip hooks. Closes #40. --- pre_commit/commands.py | 76 +++++++++++++++++++++++++++++------------- tests/commands_test.py | 36 ++++++++++++++++++-- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/pre_commit/commands.py b/pre_commit/commands.py index d9cb0d4e..0afb7bce 100644 --- a/pre_commit/commands.py +++ b/pre_commit/commands.py @@ -144,7 +144,42 @@ def clean(runner): return 0 -def _run_single_hook(runner, repository, hook_id, args, write): +def _get_skips(environ): + skips = environ.get('SKIP', '') + return set(skip.strip() for skip in skips.split(',') if skip.strip()) + + +def _print_no_files_skipped(hook, write, args): + no_files_msg = '(no files to check) ' + skipped_msg = 'Skipped' + write( + '{0}{1}{2}{3}\n'.format( + hook['name'], + '.' * ( + COLS - + len(hook['name']) - + len(no_files_msg) - + len(skipped_msg) - + 6 + ), + no_files_msg, + color.format_color(skipped_msg, color.TURQUOISE, args.color), + ) + ) + + +def _print_user_skipped(hook, write, args): + skipped_msg = 'Skipped' + write( + '{0}{1}{2}\n'.format( + hook['name'], + '.' * (COLS - len(hook['name']) - len(skipped_msg) - 6), + color.format_color(skipped_msg, color.YELLOW, args.color), + ), + ) + + +def _run_single_hook(runner, repository, hook_id, args, write, skips=set()): if args.all_files: get_filenames = git.get_all_files_matching elif git.is_in_merge_conflict(): @@ -155,23 +190,11 @@ def _run_single_hook(runner, repository, hook_id, args, write): hook = repository.hooks[hook_id] filenames = get_filenames(hook['files'], hook['exclude']) - if not filenames: - no_files_msg = '(no files to check) ' - skipped_msg = 'Skipped' - write( - '{0}{1}{2}{3}\n'.format( - hook['name'], - '.' * ( - COLS - - len(hook['name']) - - len(no_files_msg) - - len(skipped_msg) - - 6 - ), - no_files_msg, - color.format_color(skipped_msg, color.TURQUOISE, args.color), - ) - ) + if hook_id in skips: + _print_user_skipped(hook, write, args) + return 0 + elif not filenames: + _print_no_files_skipped(hook, write, args) return 0 # Print the hook and the dots first in case the hook takes hella long to @@ -211,18 +234,23 @@ def _run_single_hook(runner, repository, hook_id, args, write): return retcode -def _run_hooks(runner, args, write): +def _run_hooks(runner, args, write, environ): """Actually run the hooks.""" retval = 0 + skips = _get_skips(environ) + for repo in runner.repositories: for hook_id in repo.hooks: - retval |= _run_single_hook(runner, repo, hook_id, args, write=write) + retval |= _run_single_hook( + runner, repo, hook_id, args, write, skips=skips, + ) return retval -def _run_hook(runner, hook_id, args, write): +def _run_hook(runner, args, write): + hook_id = args.hook for repo in runner.repositories: if hook_id in repo.hooks: return _run_single_hook(runner, repo, hook_id, args, write=write) @@ -236,7 +264,7 @@ def _has_unmerged_paths(runner): return bool(stdout.strip()) -def run(runner, args, write=sys.stdout.write): +def run(runner, args, write=sys.stdout.write, environ=os.environ): # Set up our logging handler logger.addHandler(LoggingHandler(args.color, write=write)) logger.setLevel(logging.INFO) @@ -253,6 +281,6 @@ def run(runner, args, write=sys.stdout.write): with ctx: if args.hook: - return _run_hook(runner, args.hook, args, write=write) + return _run_hook(runner, args, write=write) else: - return _run_hooks(runner, args, write=write) + return _run_hooks(runner, args, write=write, environ=environ) diff --git a/tests/commands_test.py b/tests/commands_test.py index c9f553c3..b5682939 100644 --- a/tests/commands_test.py +++ b/tests/commands_test.py @@ -201,10 +201,10 @@ def _get_opts(all_files=False, color=False, verbose=False, hook=None, no_stash=F ) -def _do_run(repo, args): +def _do_run(repo, args, environ={}): runner = Runner(repo) write_mock = mock.Mock() - ret = commands.run(runner, args, write=write_mock) + ret = commands.run(runner, args, write=write_mock, environ=environ) printed = get_write_mock_output(write_mock) return ret, printed @@ -298,3 +298,35 @@ def test_merge_conflict_modified(in_merge_conflict): ret, printed = _do_run(in_merge_conflict, _get_opts()) assert ret == 1 assert 'Unmerged files. Resolve before committing.' in printed + + +def test_merge_conflict_resolved(in_merge_conflict): + local['git']['add', '.']() + ret, printed = _do_run(in_merge_conflict, _get_opts()) + for msg in ('Checking merge-conflict files only.', 'Bash hook', 'Passed'): + assert msg in printed + + +@pytest.mark.parametrize( + ('environ', 'expected_output'), + ( + ({}, set([])), + ({'SKIP': ''}, set([])), + ({'SKIP': ','}, set([])), + ({'SKIP': ',foo'}, set(['foo'])), + ({'SKIP': 'foo'}, set(['foo'])), + ({'SKIP': 'foo,bar'}, set(['foo', 'bar'])), + ({'SKIP': ' foo , bar'}, set(['foo', 'bar'])), + ), +) +def test_get_skips(environ, expected_output): + ret = commands._get_skips(environ) + assert ret == expected_output + + +def test_skip_hook(repo_with_passing_hook): + ret, printed = _do_run( + repo_with_passing_hook, _get_opts(), {'SKIP': 'bash_hook'}, + ) + for msg in ('Bash hook', 'Skipped'): + assert msg in printed