Skip to content

Commit 2da0f39

Browse files
BogayLee-W
authored andcommitted
feat(init): allow user to select which type of pre commit hooks to install
See more: https://commitizen-tools.github.io/commitizen/#integrating-with-pre-commit
1 parent 767dbfe commit 2da0f39

File tree

2 files changed

+69
-73
lines changed

2 files changed

+69
-73
lines changed

commitizen/commands/init.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import shutil
3-
from typing import Any, Dict
3+
from typing import Any, Dict, List, Optional
44

55
import questionary
66
import yaml
@@ -42,8 +42,15 @@ def __call__(self):
4242
values_to_add["tag_format"] = self._ask_tag_format(tag)
4343
self._update_config_file(values_to_add)
4444

45-
if questionary.confirm("Do you want to install pre-commit hook?").ask():
46-
if not self._install_pre_commit_hook():
45+
hook_types = questionary.checkbox(
46+
"What types of pre-commit hook you want to install? (Leave blank if you don't want to install)",
47+
choices=[
48+
questionary.Choice("commit-msg", checked=True),
49+
questionary.Choice("pre-push", checked=True),
50+
],
51+
).ask()
52+
if hook_types:
53+
if not self._install_pre_commit_hook(hook_types):
4754
raise InitFailedError(
4855
"Installation failed. See error outputs for more information."
4956
)
@@ -116,8 +123,12 @@ def _ask_tag_format(self, latest_tag) -> str:
116123
def _search_pre_commit(self):
117124
return shutil.which("pre-commit") is not None
118125

119-
def _exec_install_pre_commit_hook(self):
120-
cmd_str = "pre-commit install --hook-type commit-msg"
126+
def _exec_install_pre_commit_hook(self, hook_types: List[str]):
127+
if not hook_types:
128+
raise ValueError("At least 1 hook type should be provided.")
129+
cmd_str = "pre-commit install " + "".join(
130+
f"--hook-type {ty}" for ty in hook_types
131+
)
121132
c = cmd.run(cmd_str)
122133
if c.return_code != 0:
123134
out.error(f"Error running {cmd_str}. Outputs are attached below:")
@@ -126,12 +137,15 @@ def _exec_install_pre_commit_hook(self):
126137
return False
127138
return True
128139

129-
def _install_pre_commit_hook(self) -> bool:
140+
def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None) -> bool:
130141
pre_commit_config_filename = ".pre-commit-config.yaml"
131142
cz_hook_config = {
132143
"repo": "https://github.com/commitizen-tools/commitizen",
133144
"rev": f"v{__version__}",
134-
"hooks": [{"id": "commitizen"}],
145+
"hooks": [
146+
{"id": "commitizen"},
147+
{"id": "commitizen-branch", "stages": ["push"]},
148+
],
135149
}
136150

137151
config_data = {}
@@ -162,7 +176,9 @@ def _install_pre_commit_hook(self) -> bool:
162176
out.error("pre-commit is not installed in current environement.")
163177
return False
164178

165-
if not self._exec_install_pre_commit_hook():
179+
if hook_types is None:
180+
hook_types = ["commit-msg", "pre-push"]
181+
if not self._exec_install_pre_commit_hook(hook_types):
166182
return False
167183

168184
out.write("commitizen pre-commit hook is now installed in your '.git'\n")

tests/commands/test_init_command.py

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import os
3+
from typing import Any, Dict, List
34

45
import pytest
56
import yaml
@@ -22,7 +23,10 @@ def ask(self):
2223
cz_hook_config = {
2324
"repo": "https://github.com/commitizen-tools/commitizen",
2425
"rev": f"v{__version__}",
25-
"hooks": [{"id": "commitizen"}],
26+
"hooks": [
27+
{"id": "commitizen"},
28+
{"id": "commitizen-branch", "stages": ["push"]},
29+
],
2630
}
2731

2832
expected_config = (
@@ -51,7 +55,8 @@ def test_init_without_setup_pre_commit_hook(tmpdir, mocker: MockFixture, config)
5155
)
5256
mocker.patch("questionary.confirm", return_value=FakeQuestion(True))
5357
mocker.patch("questionary.text", return_value=FakeQuestion("$version"))
54-
mocker.patch("questionary.confirm", return_value=FakeQuestion(False))
58+
# Return None to skip hook installation
59+
mocker.patch("questionary.checkbox", return_value=FakeQuestion(None))
5560

5661
with tmpdir.as_cwd():
5762
commands.Init(config)()
@@ -118,51 +123,55 @@ def default_choice(request, mocker: MockFixture):
118123
)
119124
mocker.patch("questionary.confirm", return_value=FakeQuestion(True))
120125
mocker.patch("questionary.text", return_value=FakeQuestion("$version"))
126+
mocker.patch(
127+
"questionary.checkbox",
128+
return_value=FakeQuestion(["commit-msg", "pre-push"]),
129+
)
121130
yield request.param
122131

123132

133+
def check_cz_config(config: str):
134+
"""
135+
Cehck the content of commitizen config is as expected
136+
137+
Args:
138+
config: The config path
139+
"""
140+
with open(config, "r") as file:
141+
if "json" in config:
142+
assert json.load(file) == EXPECTED_DICT_CONFIG
143+
elif "yaml" in config:
144+
assert yaml.load(file, Loader=yaml.FullLoader) == EXPECTED_DICT_CONFIG
145+
else:
146+
config_data = file.read()
147+
assert config_data == expected_config
148+
149+
150+
def check_pre_commit_config(expected: List[Dict[str, Any]]):
151+
"""
152+
Check the content of pre-commit config is as expected
153+
"""
154+
with open(pre_commit_config_filename, "r") as pre_commit_file:
155+
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
156+
assert pre_commit_config_data == {"repos": expected}
157+
158+
124159
@pytest.mark.usefixtures("pre_commit_installed")
125160
class TestPreCommitCases:
126161
def test_no_existing_pre_commit_conifg(_, default_choice, tmpdir, config):
127162
with tmpdir.as_cwd():
128163
commands.Init(config)()
129-
130-
with open(default_choice, "r") as file:
131-
if "json" in default_choice:
132-
assert json.load(file) == EXPECTED_DICT_CONFIG
133-
elif "yaml" in default_choice:
134-
assert (
135-
yaml.load(file, Loader=yaml.FullLoader) == EXPECTED_DICT_CONFIG
136-
)
137-
else:
138-
config_data = file.read()
139-
assert config_data == expected_config
140-
141-
with open(pre_commit_config_filename, "r") as pre_commit_file:
142-
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
143-
assert pre_commit_config_data == {"repos": [cz_hook_config]}
164+
check_cz_config(default_choice)
165+
check_pre_commit_config([cz_hook_config])
144166

145167
def test_empty_pre_commit_config(_, default_choice, tmpdir, config):
146168
with tmpdir.as_cwd():
147169
p = tmpdir.join(pre_commit_config_filename)
148170
p.write("")
149171

150172
commands.Init(config)()
151-
152-
with open(default_choice, "r") as file:
153-
if "json" in default_choice:
154-
assert json.load(file) == EXPECTED_DICT_CONFIG
155-
elif "yaml" in default_choice:
156-
assert (
157-
yaml.load(file, Loader=yaml.FullLoader) == EXPECTED_DICT_CONFIG
158-
)
159-
else:
160-
config_data = file.read()
161-
assert config_data == expected_config
162-
163-
with open(pre_commit_config_filename, "r") as pre_commit_file:
164-
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
165-
assert pre_commit_config_data == {"repos": [cz_hook_config]}
173+
check_cz_config(default_choice)
174+
check_pre_commit_config([cz_hook_config])
166175

167176
def test_pre_commit_config_without_cz_hook(_, default_choice, tmpdir, config):
168177
existing_hook_config = {
@@ -176,47 +185,18 @@ def test_pre_commit_config_without_cz_hook(_, default_choice, tmpdir, config):
176185
p.write(yaml.safe_dump({"repos": [existing_hook_config]}))
177186

178187
commands.Init(config)()
179-
180-
with open(default_choice, "r") as file:
181-
if "json" in default_choice:
182-
assert json.load(file) == EXPECTED_DICT_CONFIG
183-
elif "yaml" in default_choice:
184-
assert (
185-
yaml.load(file, Loader=yaml.FullLoader) == EXPECTED_DICT_CONFIG
186-
)
187-
else:
188-
config_data = file.read()
189-
assert config_data == expected_config
190-
191-
with open(pre_commit_config_filename, "r") as pre_commit_file:
192-
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
193-
assert pre_commit_config_data == {
194-
"repos": [existing_hook_config, cz_hook_config]
195-
}
188+
check_cz_config(default_choice)
189+
check_pre_commit_config([existing_hook_config, cz_hook_config])
196190

197191
def test_cz_hook_exists_in_pre_commit_config(_, default_choice, tmpdir, config):
198192
with tmpdir.as_cwd():
199193
p = tmpdir.join(pre_commit_config_filename)
200194
p.write(yaml.safe_dump({"repos": [cz_hook_config]}))
201195

202196
commands.Init(config)()
203-
204-
with open(default_choice, "r") as file:
205-
if "json" in default_choice:
206-
assert json.load(file) == EXPECTED_DICT_CONFIG
207-
elif "yaml" in default_choice:
208-
assert (
209-
yaml.load(file, Loader=yaml.FullLoader) == EXPECTED_DICT_CONFIG
210-
)
211-
else:
212-
config_data = file.read()
213-
assert config_data == expected_config
214-
215-
with open(pre_commit_config_filename, "r") as pre_commit_file:
216-
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
217-
197+
check_cz_config(default_choice)
218198
# check that config is not duplicated
219-
assert pre_commit_config_data == {"repos": [cz_hook_config]}
199+
check_pre_commit_config([cz_hook_config])
220200

221201

222202
class TestNoPreCommitInstalled:

0 commit comments

Comments
 (0)