diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py index 90bb33b8..c24339c0 100644 --- a/pre_commit/commands/hook_impl.py +++ b/pre_commit/commands/hook_impl.py @@ -69,6 +69,7 @@ def _ns( color: bool, *, all_files: bool = False, + fail_fast: bool = False, remote_branch: Optional[str] = None, local_branch: Optional[str] = None, from_ref: Optional[str] = None, @@ -91,6 +92,7 @@ def _ns( remote_url=remote_url, commit_msg_filename=commit_msg_filename, all_files=all_files, + fail_fast=fail_fast, checkout_type=checkout_type, is_squash_merge=is_squash_merge, rewrite_command=rewrite_command, diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 95ad5e96..e97a2634 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -290,7 +290,7 @@ def _run_hooks( verbose=args.verbose, use_color=args.color, ) retval |= current_retval - if retval and config['fail_fast']: + if retval and (config['fail_fast'] or args.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 2b50c91b..e7c0eee8 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -85,6 +85,10 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: '--all-files', '-a', action='store_true', default=False, help='Run on all the files in the repo.', ) + mutex_group.add_argument( + '--fail-fast', action='store_true', + help='Stop running hooks after the first failure', + ) mutex_group.add_argument( '--files', nargs='*', default=[], help='Specific filenames to run hooks on.', diff --git a/testing/util.py b/testing/util.py index 791a2b95..67786af9 100644 --- a/testing/util.py +++ b/testing/util.py @@ -57,6 +57,7 @@ xfailif_windows = pytest.mark.xfail(os.name == 'nt', reason='windows') def run_opts( all_files=False, + fail_fast=False, files=(), color=False, verbose=False, @@ -78,6 +79,7 @@ def run_opts( assert not (all_files and files) return auto_namedtuple( all_files=all_files, + fail_fast=fail_fast, files=files, color=color, verbose=verbose, diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 8c153957..a2385138 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -973,7 +973,7 @@ def test_pass_filenames( assert (b'foo.py' in printed) == pass_filenames -def test_fail_fast(cap_out, store, repo_with_failing_hook): +def test_fail_fast_config(cap_out, store, repo_with_failing_hook): with modify_config() as config: # More than one hook config['fail_fast'] = True @@ -985,6 +985,19 @@ def test_fail_fast(cap_out, store, repo_with_failing_hook): assert printed.count(b'Failing hook') == 1 +def test_fail_fast_args(cap_out, store, repo_with_failing_hook): + with modify_config() as config: + # More than one hook + 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 + assert printed.count(b'Failing hook') == 1 + + def test_classifier_removes_dne(): classifier = Classifier(('this_file_does_not_exist',)) assert classifier.filenames == []