Skip to content

Commit 2f41998

Browse files
committed
Add --allow-missing-config option to install
When no '.pre-commit-config.yaml' file exists while `pre-commit` hooks are enabled, `pre-commit` returns an error and the action is aborted. This is a very common scenario when pre-commit is added later on a project and the user wants to work on a previous branch where the configuration file does not exist. This commits allow the user to optionally install the `pre-commit` hooks with an option to allow a missing configuration and trigger only the legacy pre-commit hooks (if any) when it is missing.
1 parent 41dcaff commit 2f41998

File tree

5 files changed

+84
-2
lines changed

5 files changed

+84
-2
lines changed

pre_commit/commands/install_uninstall.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ def is_previous_pre_commit(filename):
3737
return any(hash in contents for hash in PREVIOUS_IDENTIFYING_HASHES)
3838

3939

40-
def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'):
40+
def install(
41+
runner, overwrite=False, hooks=False, hook_type='pre-commit',
42+
skip_on_missing_conf=False
43+
):
4144
"""Install the pre-commit hooks."""
4245
hook_path = runner.get_hook_path(hook_type)
4346
legacy_path = hook_path + '.legacy'
@@ -70,10 +73,12 @@ def install(runner, overwrite=False, hooks=False, hook_type='pre-commit'):
7073
else:
7174
pre_push_contents = ''
7275

76+
skip_on_missing_conf = 'true' if skip_on_missing_conf else 'false'
7377
contents = io.open(resource_filename('hook-tmpl')).read().format(
7478
sys_executable=sys.executable,
7579
hook_type=hook_type,
7680
pre_push=pre_push_contents,
81+
skip_on_missing_conf=skip_on_missing_conf
7782
)
7883
pre_commit_file_obj.write(contents)
7984
make_executable(hook_path)

pre_commit/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def main(argv=None):
7575
'-t', '--hook-type', choices=('pre-commit', 'pre-push'),
7676
default='pre-commit',
7777
)
78+
install_parser.add_argument(
79+
'--allow-missing-config', action='store_true', default=False,
80+
help=(
81+
'Whether to allow a missing `pre-config` configuration file '
82+
'or exit with a failure code.'
83+
),
84+
)
7885

7986
install_hooks_parser = subparsers.add_parser(
8087
'install-hooks',
@@ -182,6 +189,7 @@ def main(argv=None):
182189
return install(
183190
runner, overwrite=args.overwrite, hooks=args.install_hooks,
184191
hook_type=args.hook_type,
192+
skip_on_missing_conf=args.allow_missing_config
185193
)
186194
elif args.command == 'install-hooks':
187195
return install_hooks(runner)

pre_commit/resources/hook-tmpl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ retv=0
1010
args=""
1111

1212
ENV_PYTHON='{sys_executable}'
13+
SKIP_ON_MISSING_CONF={skip_on_missing_conf}
1314

1415
which pre-commit >& /dev/null
1516
WHICH_RETV=$?
@@ -37,6 +38,20 @@ if [ -x "$HERE"/{hook_type}.legacy ]; then
3738
fi
3839
fi
3940

41+
CONF_FILE=$(git rev-parse --show-toplevel)"/.pre-commit-config.yaml"
42+
if [ ! -f $CONF_FILE ]; then
43+
if [ $SKIP_ON_MISSING_CONF = true ] || [ ! -z $PRE_COMMIT_ALLOW_NO_CONFIG ]; then
44+
echo '`.pre-commit-config.yaml` config file not found. Skipping `pre-commit`.'
45+
exit $retv
46+
else
47+
echo 'No .pre-commit-config.yaml file was found\n'\
48+
'- To temporarily silence this, run `PRE_COMMIT_ALLOW_NO_CONFIG=1 git ...`\n'\
49+
'- To permanently silence this, install pre-commit with the `--allow-missing-config` option\n'\
50+
'- To uninstall pre-commit run `pre-commit uninstall`'
51+
exit 1
52+
fi
53+
fi
54+
4055
{pre_push}
4156

4257
# Run pre-commit

testing/fixtures.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ def add_config_to_repo(git_path, config, config_file=C.CONFIG_FILE):
123123
return git_path
124124

125125

126+
def remove_config_from_repo(git_path, config_file=C.CONFIG_FILE):
127+
os.unlink(os.path.join(git_path, config_file))
128+
with cwd(git_path):
129+
cmd_output('git', 'add', config_file)
130+
cmd_output('git', 'commit', '-m', 'Remove hooks config')
131+
return git_path
132+
133+
126134
def make_consuming_repo(tempdir_factory, repo_source):
127135
path = make_repo(tempdir_factory, repo_source)
128136
config = make_config_from_repo(path)

tests/commands/install_uninstall_test.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from pre_commit.util import resource_filename
2727
from testing.fixtures import git_dir
2828
from testing.fixtures import make_consuming_repo
29+
from testing.fixtures import remove_config_from_repo
2930
from testing.util import cmd_output_mocked_pre_commit_home
3031
from testing.util import xfailif_no_symlink
3132

@@ -64,7 +65,8 @@ def test_install_pre_commit(tempdir_factory):
6465
expected_contents = io.open(pre_commit_script).read().format(
6566
sys_executable=sys.executable,
6667
hook_type='pre-commit',
67-
pre_push=''
68+
pre_push='',
69+
skip_on_missing_conf='false'
6870
)
6971
assert pre_commit_contents == expected_contents
7072
assert os.access(runner.pre_commit_path, os.X_OK)
@@ -79,6 +81,7 @@ def test_install_pre_commit(tempdir_factory):
7981
sys_executable=sys.executable,
8082
hook_type='pre-push',
8183
pre_push=pre_push_template_contents,
84+
skip_on_missing_conf='false'
8285
)
8386
assert pre_push_contents == expected_contents
8487

@@ -552,3 +555,46 @@ def test_pre_push_integration_empty_push(tempdir_factory):
552555
retc, output = _get_push_output(tempdir_factory)
553556
assert output == 'Everything up-to-date\n'
554557
assert retc == 0
558+
559+
560+
def test_install_disallow_mising_config(tempdir_factory):
561+
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
562+
with cwd(path):
563+
runner = Runner(path, C.CONFIG_FILE)
564+
565+
remove_config_from_repo(path)
566+
assert install(runner, overwrite=True, skip_on_missing_conf=False) == 0
567+
568+
ret, output = _get_commit_output(tempdir_factory)
569+
assert ret == 1
570+
571+
572+
def test_install_allow_mising_config(tempdir_factory):
573+
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
574+
with cwd(path):
575+
runner = Runner(path, C.CONFIG_FILE)
576+
577+
remove_config_from_repo(path)
578+
assert install(runner, overwrite=True, skip_on_missing_conf=True) == 0
579+
580+
ret, output = _get_commit_output(tempdir_factory)
581+
assert ret == 0
582+
assert '`.pre-commit-config.yaml` config file not found. '\
583+
'Skipping `pre-commit`.' in output
584+
585+
586+
def test_install_temporarily_allow_mising_config(tempdir_factory):
587+
path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
588+
with cwd(path):
589+
runner = Runner(path, C.CONFIG_FILE)
590+
591+
remove_config_from_repo(path)
592+
assert install(runner, overwrite=True, skip_on_missing_conf=False) == 0
593+
594+
extra_env = {'PRE_COMMIT_ALLOW_NO_CONFIG': '1'}
595+
env = os.environ.copy()
596+
env.update(extra_env)
597+
ret, output = _get_commit_output(tempdir_factory, env=env)
598+
assert ret == 0
599+
assert '`.pre-commit-config.yaml` config file not found. '\
600+
'Skipping `pre-commit`.' in output

0 commit comments

Comments
 (0)