diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 069fb9dc..72e49660 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -37,7 +37,10 @@ def is_previous_pre_commit(filename): return any(hash in contents for hash in PREVIOUS_IDENTIFYING_HASHES) -def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'): +def install( + runner, overwrite=False, hooks=False, hook_type='pre-commit', + skip_on_missing_conf=False +): """Install the pre-commit hooks.""" hook_path = runner.get_hook_path(hook_type) legacy_path = hook_path + '.legacy' @@ -70,10 +73,12 @@ def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'): else: pre_push_contents = '' + skip_on_missing_conf = 'true' if skip_on_missing_conf else 'false' contents = io.open(resource_filename('hook-tmpl')).read().format( sys_executable=sys.executable, hook_type=hook_type, pre_push=pre_push_contents, + skip_on_missing_conf=skip_on_missing_conf ) pre_commit_file_obj.write(contents) make_executable(hook_path) diff --git a/pre_commit/main.py b/pre_commit/main.py index 5fb261c0..7819346d 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -75,6 +75,13 @@ def main(argv=None): '-t', '--hook-type', choices=('pre-commit', 'pre-push'), default='pre-commit', ) + install_parser.add_argument( + '--allow-missing-config', action='store_true', default=False, + help=( + 'Whether to allow a missing `pre-config` configuration file ' + 'or exit with a failure code.' + ), + ) install_hooks_parser = subparsers.add_parser( 'install-hooks', @@ -182,6 +189,7 @@ def main(argv=None): return install( runner, overwrite=args.overwrite, hooks=args.install_hooks, hook_type=args.hook_type, + skip_on_missing_conf=args.allow_missing_config ) elif args.command == 'install-hooks': return install_hooks(runner) diff --git a/pre_commit/resources/hook-tmpl b/pre_commit/resources/hook-tmpl index ac205890..88d772c9 100644 --- a/pre_commit/resources/hook-tmpl +++ b/pre_commit/resources/hook-tmpl @@ -10,6 +10,7 @@ retv=0 args="" ENV_PYTHON='{sys_executable}' +SKIP_ON_MISSING_CONF={skip_on_missing_conf} which pre-commit >& /dev/null WHICH_RETV=$? @@ -37,6 +38,20 @@ if [ -x "$HERE"/{hook_type}.legacy ]; then fi fi +CONF_FILE=$(git rev-parse --show-toplevel)"/.pre-commit-config.yaml" +if [ ! -f $CONF_FILE ]; then + if [ $SKIP_ON_MISSING_CONF = true ] || [ ! -z $PRE_COMMIT_ALLOW_NO_CONFIG ]; then + echo '`.pre-commit-config.yaml` config file not found. Skipping `pre-commit`.' + exit $retv + else + echo 'No .pre-commit-config.yaml file was found\n'\ + '- To temporarily silence this, run `PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n'\ + '- To permanently silence this, install pre-commit with the `--allow-missing-config` option\n'\ + '- To uninstall pre-commit run `pre-commit uninstall`' + exit 1 + fi +fi + {pre_push} # Run pre-commit diff --git a/testing/fixtures.py b/testing/fixtures.py index 16cda572..599558b8 100644 --- a/testing/fixtures.py +++ b/testing/fixtures.py @@ -123,6 +123,14 @@ def add_config_to_repo(git_path, config, config_file=C.CONFIG_FILE): return git_path +def remove_config_from_repo(git_path, config_file=C.CONFIG_FILE): + os.unlink(os.path.join(git_path, config_file)) + with cwd(git_path): + cmd_output('git', 'add', config_file) + cmd_output('git', 'commit', '-m', 'Remove hooks config') + return git_path + + def make_consuming_repo(tempdir_factory, repo_source): path = make_repo(tempdir_factory, repo_source) config = make_config_from_repo(path) diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index 9136a0c8..6bec60b1 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -26,6 +26,7 @@ from pre_commit.util import mkdirp from pre_commit.util import resource_filename from testing.fixtures import git_dir from testing.fixtures import make_consuming_repo +from testing.fixtures import remove_config_from_repo from testing.util import cmd_output_mocked_pre_commit_home from testing.util import xfailif_no_symlink @@ -64,7 +65,8 @@ def test_install_pre_commit(tempdir_factory): expected_contents = io.open(pre_commit_script).read().format( sys_executable=sys.executable, hook_type='pre-commit', - pre_push='' + pre_push='', + skip_on_missing_conf='false' ) assert pre_commit_contents == expected_contents assert os.access(runner.pre_commit_path, os.X_OK) @@ -79,6 +81,7 @@ def test_install_pre_commit(tempdir_factory): sys_executable=sys.executable, hook_type='pre-push', pre_push=pre_push_template_contents, + skip_on_missing_conf='false' ) assert pre_push_contents == expected_contents @@ -552,3 +555,46 @@ def test_pre_push_integration_empty_push(tempdir_factory): retc, output = _get_push_output(tempdir_factory) assert output == 'Everything up-to-date\n' assert retc == 0 + + +def test_install_disallow_mising_config(tempdir_factory): + path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') + with cwd(path): + runner = Runner(path, C.CONFIG_FILE) + + remove_config_from_repo(path) + assert install(runner, overwrite=True, skip_on_missing_conf=False) == 0 + + ret, output = _get_commit_output(tempdir_factory) + assert ret == 1 + + +def test_install_allow_mising_config(tempdir_factory): + path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') + with cwd(path): + runner = Runner(path, C.CONFIG_FILE) + + remove_config_from_repo(path) + assert install(runner, overwrite=True, skip_on_missing_conf=True) == 0 + + ret, output = _get_commit_output(tempdir_factory) + assert ret == 0 + assert '`.pre-commit-config.yaml` config file not found. '\ + 'Skipping `pre-commit`.' in output + + +def test_install_temporarily_allow_mising_config(tempdir_factory): + path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') + with cwd(path): + runner = Runner(path, C.CONFIG_FILE) + + remove_config_from_repo(path) + assert install(runner, overwrite=True, skip_on_missing_conf=False) == 0 + + extra_env = {'PRE_COMMIT_ALLOW_NO_CONFIG': '1'} + env = os.environ.copy() + env.update(extra_env) + ret, output = _get_commit_output(tempdir_factory, env=env) + assert ret == 0 + assert '`.pre-commit-config.yaml` config file not found. '\ + 'Skipping `pre-commit`.' in output