diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 49a80b7b..de5c8f34 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -106,6 +106,7 @@ def _ns( hook=None, verbose=False, show_diff_on_failure=False, + fail_fast=False, ) diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 793adbdb..8ab505ff 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -298,7 +298,8 @@ def _run_hooks( verbose=args.verbose, use_color=args.color, ) retval |= current_retval - if current_retval and (config['fail_fast'] or hook.fail_fast): + fail_fast = (config['fail_fast'] or hook.fail_fast or args.fail_fast) + if current_retval and fail_fast: break if retval and args.show_diff_on_failure and prior_diff: if args.all_files: diff --git a/pre_commit/main.py b/pre_commit/main.py index 559c927c..fc4531b8 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -76,6 +76,10 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: '--show-diff-on-failure', action='store_true', help='When hooks fail, run `git diff` directly afterward.', ) + parser.add_argument( + '--fail-fast', action='store_true', + help='Stop after the first failing hook.', + ) parser.add_argument( '--hook-stage', choices=clientlib.STAGES, diff --git a/testing/util.py b/testing/util.py index 08d52cbc..1646ccd2 100644 --- a/testing/util.py +++ b/testing/util.py @@ -40,6 +40,7 @@ def run_opts( color=False, verbose=False, hook=None, + fail_fast=False, remote_branch='', local_branch='', from_ref='', @@ -65,6 +66,7 @@ def run_opts( color=color, verbose=verbose, hook=hook, + fail_fast=fail_fast, remote_branch=remote_branch, local_branch=local_branch, from_ref=from_ref, diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 50a20f37..e4af1e16 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -1104,6 +1104,19 @@ def test_fail_fast_not_prev_failures(cap_out, store, repo_with_failing_hook): assert printed.count(b'run me!') == 1 +def test_fail_fast_run_arg(cap_out, store, repo_with_failing_hook): + with modify_config() as config: + # More than one hook to demonstrate early exit + config['repos'][0]['hooks'] *= 2 + stage_a_file() + + ret, printed = _do_run( + cap_out, store, repo_with_failing_hook, run_opts(fail_fast=True), + ) + # it should have only run one hook due to the CLI flag + assert printed.count(b'Failing hook') == 1 + + def test_classifier_removes_dne(): classifier = Classifier(('this_file_does_not_exist',)) assert classifier.filenames == []