Skip to content

Commit 7602abc

Browse files
authored
Merge pull request #2322 from pre-commit/default-install-hook-types
implement default_install_hook_types
2 parents e11163d + fd0177a commit 7602abc

File tree

7 files changed

+86
-55
lines changed

7 files changed

+86
-55
lines changed

pre_commit/clientlib.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,11 @@ def check(self, dct: dict[str, Any]) -> None:
336336
'Config', None,
337337

338338
cfgv.RequiredRecurse('repos', cfgv.Array(CONFIG_REPO_DICT)),
339+
cfgv.Optional(
340+
'default_install_hook_types',
341+
cfgv.check_array(cfgv.check_one_of(C.HOOK_TYPES)),
342+
['pre-commit'],
343+
),
339344
cfgv.OptionalRecurse(
340345
'default_language_version', DEFAULT_LANGUAGE_VERSION, {},
341346
),
@@ -355,6 +360,7 @@ def check(self, dct: dict[str, Any]) -> None:
355360
cfgv.WarnAdditionalKeys(
356361
(
357362
'repos',
363+
'default_install_hook_types',
358364
'default_language_version',
359365
'default_stages',
360366
'files',

pre_commit/commands/init_templatedir.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import logging
44
import os.path
5-
from typing import Sequence
65

76
from pre_commit.commands.install_uninstall import install
87
from pre_commit.store import Store
@@ -16,7 +15,7 @@ def init_templatedir(
1615
config_file: str,
1716
store: Store,
1817
directory: str,
19-
hook_types: Sequence[str],
18+
hook_types: list[str] | None,
2019
skip_on_missing_config: bool = True,
2120
) -> int:
2221
install(

pre_commit/commands/install_uninstall.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import shlex
66
import shutil
77
import sys
8-
from typing import Sequence
98

109
from pre_commit import git
1110
from pre_commit import output
11+
from pre_commit.clientlib import InvalidConfigError
1212
from pre_commit.clientlib import load_config
1313
from pre_commit.repository import all_hooks
1414
from pre_commit.repository import install_hook_envs
@@ -32,6 +32,18 @@
3232
TEMPLATE_END = '# end templated\n'
3333

3434

35+
def _hook_types(cfg_filename: str, hook_types: list[str] | None) -> list[str]:
36+
if hook_types is not None:
37+
return hook_types
38+
else:
39+
try:
40+
cfg = load_config(cfg_filename)
41+
except InvalidConfigError:
42+
return ['pre-commit']
43+
else:
44+
return cfg['default_install_hook_types']
45+
46+
3547
def _hook_paths(
3648
hook_type: str,
3749
git_dir: str | None = None,
@@ -103,7 +115,7 @@ def _install_hook_script(
103115
def install(
104116
config_file: str,
105117
store: Store,
106-
hook_types: Sequence[str],
118+
hook_types: list[str] | None,
107119
overwrite: bool = False,
108120
hooks: bool = False,
109121
skip_on_missing_config: bool = False,
@@ -116,7 +128,7 @@ def install(
116128
)
117129
return 1
118130

119-
for hook_type in hook_types:
131+
for hook_type in _hook_types(config_file, hook_types):
120132
_install_hook_script(
121133
config_file, hook_type,
122134
overwrite=overwrite,
@@ -150,7 +162,7 @@ def _uninstall_hook_script(hook_type: str) -> None:
150162
output.write_line(f'Restored previous hooks to {hook_path}')
151163

152164

153-
def uninstall(hook_types: Sequence[str]) -> int:
154-
for hook_type in hook_types:
165+
def uninstall(config_file: str, hook_types: list[str] | None) -> int:
166+
for hook_type in _hook_types(config_file, hook_types):
155167
_uninstall_hook_script(hook_type)
156168
return 0

pre_commit/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@
2424
'post-rewrite',
2525
)
2626

27+
HOOK_TYPES = (
28+
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
29+
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
30+
'post-rewrite',
31+
)
32+
2733
DEFAULT = 'default'

pre_commit/main.py

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import logging
55
import os
66
import sys
7-
from typing import Any
87
from typing import Sequence
98

109
import pre_commit.constants as C
@@ -46,34 +45,10 @@ def _add_config_option(parser: argparse.ArgumentParser) -> None:
4645
)
4746

4847

49-
class AppendReplaceDefault(argparse.Action):
50-
def __init__(self, *args: Any, **kwargs: Any) -> None:
51-
super().__init__(*args, **kwargs)
52-
self.appended = False
53-
54-
def __call__(
55-
self,
56-
parser: argparse.ArgumentParser,
57-
namespace: argparse.Namespace,
58-
values: str | Sequence[str] | None,
59-
option_string: str | None = None,
60-
) -> None:
61-
if not self.appended:
62-
setattr(namespace, self.dest, [])
63-
self.appended = True
64-
getattr(namespace, self.dest).append(values)
65-
66-
6748
def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
6849
parser.add_argument(
69-
'-t', '--hook-type', choices=(
70-
'pre-commit', 'pre-merge-commit', 'pre-push', 'prepare-commit-msg',
71-
'commit-msg', 'post-commit', 'post-checkout', 'post-merge',
72-
'post-rewrite',
73-
),
74-
action=AppendReplaceDefault,
75-
default=['pre-commit'],
76-
dest='hook_types',
50+
'-t', '--hook-type',
51+
choices=C.HOOK_TYPES, action='append', dest='hook_types',
7752
)
7853

7954

@@ -399,7 +374,10 @@ def main(argv: Sequence[str] | None = None) -> int:
399374
elif args.command == 'try-repo':
400375
return try_repo(args)
401376
elif args.command == 'uninstall':
402-
return uninstall(hook_types=args.hook_types)
377+
return uninstall(
378+
config_file=args.config,
379+
hook_types=args.hook_types,
380+
)
403381
else:
404382
raise NotImplementedError(
405383
f'Command {args.command} not implemented.',

tests/commands/install_uninstall_test.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pre_commit.constants as C
99
from pre_commit import git
10+
from pre_commit.commands.install_uninstall import _hook_types
1011
from pre_commit.commands.install_uninstall import CURRENT_HASH
1112
from pre_commit.commands.install_uninstall import install
1213
from pre_commit.commands.install_uninstall import install_hooks
@@ -27,6 +28,36 @@
2728
from testing.util import git_commit
2829

2930

31+
def test_hook_types_explicitly_listed():
32+
assert _hook_types(os.devnull, ['pre-push']) == ['pre-push']
33+
34+
35+
def test_hook_types_default_value_when_not_specified():
36+
assert _hook_types(os.devnull, None) == ['pre-commit']
37+
38+
39+
def test_hook_types_configured(tmpdir):
40+
cfg = tmpdir.join('t.cfg')
41+
cfg.write('default_install_hook_types: [pre-push]\nrepos: []\n')
42+
43+
assert _hook_types(str(cfg), None) == ['pre-push']
44+
45+
46+
def test_hook_types_configured_nonsense(tmpdir):
47+
cfg = tmpdir.join('t.cfg')
48+
cfg.write('default_install_hook_types: []\nrepos: []\n')
49+
50+
# hopefully the user doesn't do this, but the code allows it!
51+
assert _hook_types(str(cfg), None) == []
52+
53+
54+
def test_hook_types_configuration_has_error(tmpdir):
55+
cfg = tmpdir.join('t.cfg')
56+
cfg.write('[')
57+
58+
assert _hook_types(str(cfg), None) == ['pre-commit']
59+
60+
3061
def test_is_not_script():
3162
assert is_our_script('setup.py') is False
3263

@@ -61,7 +92,7 @@ def test_install_multiple_hooks_at_once(in_git_dir, store):
6192
install(C.CONFIG_FILE, store, hook_types=['pre-commit', 'pre-push'])
6293
assert in_git_dir.join('.git/hooks/pre-commit').exists()
6394
assert in_git_dir.join('.git/hooks/pre-push').exists()
64-
uninstall(hook_types=['pre-commit', 'pre-push'])
95+
uninstall(C.CONFIG_FILE, hook_types=['pre-commit', 'pre-push'])
6596
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
6697
assert not in_git_dir.join('.git/hooks/pre-push').exists()
6798

@@ -79,14 +110,14 @@ def test_install_hooks_dead_symlink(in_git_dir, store):
79110

80111

81112
def test_uninstall_does_not_blow_up_when_not_there(in_git_dir):
82-
assert uninstall(hook_types=['pre-commit']) == 0
113+
assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
83114

84115

85116
def test_uninstall(in_git_dir, store):
86117
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
87118
install(C.CONFIG_FILE, store, hook_types=['pre-commit'])
88119
assert in_git_dir.join('.git/hooks/pre-commit').exists()
89-
uninstall(hook_types=['pre-commit'])
120+
uninstall(C.CONFIG_FILE, hook_types=['pre-commit'])
90121
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
91122

92123

@@ -416,7 +447,7 @@ def test_uninstall_restores_legacy_hooks(tempdir_factory, store):
416447

417448
# Now install and uninstall pre-commit
418449
assert install(C.CONFIG_FILE, store, hook_types=['pre-commit']) == 0
419-
assert uninstall(hook_types=['pre-commit']) == 0
450+
assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
420451

421452
# Make sure we installed the "old" hook correctly
422453
ret, output = _get_commit_output(tempdir_factory, touch_file='baz')
@@ -451,7 +482,7 @@ def test_uninstall_doesnt_remove_not_our_hooks(in_git_dir):
451482
pre_commit.write('#!/usr/bin/env bash\necho 1\n')
452483
make_executable(pre_commit.strpath)
453484

454-
assert uninstall(hook_types=['pre-commit']) == 0
485+
assert uninstall(C.CONFIG_FILE, hook_types=['pre-commit']) == 0
455486

456487
assert pre_commit.exists()
457488

@@ -1007,3 +1038,16 @@ def test_install_temporarily_allow_mising_config(tempdir_factory, store):
10071038
'Skipping `pre-commit`.'
10081039
)
10091040
assert expected in output
1041+
1042+
1043+
def test_install_uninstall_default_hook_types(in_git_dir, store):
1044+
cfg_src = 'default_install_hook_types: [pre-commit, pre-push]\nrepos: []\n'
1045+
in_git_dir.join(C.CONFIG_FILE).write(cfg_src)
1046+
1047+
assert not install(C.CONFIG_FILE, store, hook_types=None)
1048+
assert os.access(in_git_dir.join('.git/hooks/pre-commit').strpath, os.X_OK)
1049+
assert os.access(in_git_dir.join('.git/hooks/pre-push').strpath, os.X_OK)
1050+
1051+
assert not uninstall(C.CONFIG_FILE, hook_types=None)
1052+
assert not in_git_dir.join('.git/hooks/pre-commit').exists()
1053+
assert not in_git_dir.join('.git/hooks/pre-push').exists()

tests/main_test.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,6 @@
1414
from testing.util import cwd
1515

1616

17-
@pytest.mark.parametrize(
18-
('argv', 'expected'),
19-
(
20-
((), ['f']),
21-
(('--f', 'x'), ['x']),
22-
(('--f', 'x', '--f', 'y'), ['x', 'y']),
23-
),
24-
)
25-
def test_append_replace_default(argv, expected):
26-
parser = argparse.ArgumentParser()
27-
parser.add_argument('--f', action=main.AppendReplaceDefault, default=['f'])
28-
assert parser.parse_args(argv).f == expected
29-
30-
3117
def _args(**kwargs):
3218
kwargs.setdefault('command', 'help')
3319
kwargs.setdefault('config', C.CONFIG_FILE)
@@ -172,7 +158,7 @@ def test_init_templatedir(mock_store_dir):
172158

173159
assert patch.call_count == 1
174160
assert 'tdir' in patch.call_args[0]
175-
assert patch.call_args[1]['hook_types'] == ['pre-commit']
161+
assert patch.call_args[1]['hook_types'] is None
176162
assert patch.call_args[1]['skip_on_missing_config'] is True
177163

178164

0 commit comments

Comments
 (0)