Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c619c87

Browse files
committed
feat(init): enable setting up pre-commit hook through "cz init"
1 parent 13a6dd0 commit c619c87

File tree

4 files changed

+168
-19
lines changed

4 files changed

+168
-19
lines changed

commitizen/commands/init.py

+53-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import os
2+
13
import questionary
4+
import yaml
25
from packaging.version import Version
36

4-
from commitizen import factory, out
7+
from commitizen import cmd, factory, out
8+
from commitizen.__version__ import __version__
59
from commitizen.config import BaseConfig, TomlConfig
610
from commitizen.cz import registry
711
from commitizen.defaults import config_files
@@ -20,7 +24,6 @@ def __call__(self):
2024
# No config for commitizen exist
2125
if not self.config.path:
2226
config_path = self._ask_config_path()
23-
2427
if "toml" in config_path:
2528
self.config = TomlConfig(data="", path=config_path)
2629

@@ -31,6 +34,10 @@ def __call__(self):
3134
values_to_add["version"] = Version(tag).public
3235
values_to_add["tag_format"] = self._ask_tag_format(tag)
3336
self._update_config_file(values_to_add)
37+
38+
if questionary.confirm("Do you want to install pre-commit hook?").ask():
39+
self._install_pre_commit_hook()
40+
3441
out.write("You can bump the version and create changelog running:\n")
3542
out.info("cz bump --changelog")
3643
out.success("The configuration are all set.")
@@ -98,6 +105,50 @@ def _ask_tag_format(self, latest_tag) -> str:
98105
tag_format = "$version"
99106
return tag_format
100107

108+
def _install_pre_commit_hook(self):
109+
pre_commit_config_filename = ".pre-commit-config.yaml"
110+
cz_hook_config = {
111+
"repo": "https://github.com/commitizen-tools/commitizen",
112+
"rev": f"v{__version__}",
113+
"hooks": [{"id": "commitizen", "stages": ["commit-msg"]}],
114+
}
115+
116+
config_data = {}
117+
if not os.path.isfile(pre_commit_config_filename):
118+
# .pre-commit-config does not exist
119+
config_data["repos"] = [cz_hook_config]
120+
else:
121+
# breakpoint()
122+
with open(pre_commit_config_filename) as config_file:
123+
yaml_data = yaml.safe_load(config_file)
124+
if yaml_data:
125+
config_data = yaml_data
126+
127+
if "repos" in config_data:
128+
for pre_commit_hook in config_data["repos"]:
129+
if "commitizen" in pre_commit_hook["repo"]:
130+
out.write("commitizen already in pre-commit config")
131+
break
132+
else:
133+
config_data["repos"].append(cz_hook_config)
134+
else:
135+
# .pre-commit-config exists but there's no "repos" key
136+
config_data["repos"] = [cz_hook_config]
137+
138+
with open(pre_commit_config_filename, "w") as config_file:
139+
yaml.safe_dump(config_data, stream=config_file)
140+
141+
c = cmd.run("pre-commit install --hook-type commit-msg")
142+
if c.return_code == 127:
143+
out.error(
144+
"pre-commit is not installed in current environement.\n"
145+
"Run 'pre-commit install --hook-type commit-msg' again after it's installed"
146+
)
147+
elif c.return_code != 0:
148+
out.error(c.err)
149+
else:
150+
out.write("commitizen pre-commit hook is now installed in your '.git'\n")
151+
101152
def _update_config_file(self, values):
102153
for key, value in values.items():
103154
self.config.set_key(key, value)

tests/commands/test_init_command.py

+107-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import os
2+
13
import pytest
4+
import yaml
25

36
from commitizen import commands
7+
from commitizen.__version__ import __version__
48
from commitizen.exceptions import NoAnswersError
59

610

@@ -12,31 +16,42 @@ def ask(self):
1216
return self.expected_return
1317

1418

15-
def test_init(tmpdir, mocker, config):
19+
pre_commit_config_filename = ".pre-commit-config.yaml"
20+
cz_hook_config = {
21+
"repo": "https://github.com/commitizen-tools/commitizen",
22+
"rev": f"v{__version__}",
23+
"hooks": [{"id": "commitizen", "stages": ["commit-msg"]}],
24+
}
25+
26+
expected_config = (
27+
"[tool.commitizen]\n"
28+
'name = "cz_conventional_commits"\n'
29+
'version = "0.0.1"\n'
30+
'tag_format = "$version"\n'
31+
)
32+
33+
34+
def test_init_without_setup_pre_commit_hook(tmpdir, mocker, config):
1635
mocker.patch(
1736
"questionary.select",
1837
side_effect=[
1938
FakeQuestion("pyproject.toml"),
2039
FakeQuestion("cz_conventional_commits"),
2140
],
2241
)
23-
mocker.patch("questionary.confirm", return_value=FakeQuestion("y"))
24-
mocker.patch("questionary.text", return_value=FakeQuestion("y"))
25-
expected_config = (
26-
"[tool.commitizen]\n"
27-
'name = "cz_conventional_commits"\n'
28-
'version = "0.0.1"\n'
29-
'tag_format = "y"\n'
30-
)
42+
mocker.patch("questionary.confirm", return_value=FakeQuestion(True))
43+
mocker.patch("questionary.text", return_value=FakeQuestion("$version"))
44+
mocker.patch("questionary.confirm", return_value=FakeQuestion(False))
3145

3246
with tmpdir.as_cwd():
3347
commands.Init(config)()
3448

3549
with open("pyproject.toml", "r") as toml_file:
3650
config_data = toml_file.read()
37-
3851
assert config_data == expected_config
3952

53+
assert not os.path.isfile(pre_commit_config_filename)
54+
4055

4156
def test_init_when_config_already_exists(config, capsys):
4257
# Set config path
@@ -67,3 +82,85 @@ def test_init_without_choosing_tag(config, mocker, tmpdir):
6782
with tmpdir.as_cwd():
6883
with pytest.raises(NoAnswersError):
6984
commands.Init(config)()
85+
86+
87+
class TestPreCommitCases:
88+
@pytest.fixture(scope="function", autouse=True)
89+
def default_choices(_, mocker):
90+
mocker.patch(
91+
"questionary.select",
92+
side_effect=[
93+
FakeQuestion("pyproject.toml"),
94+
FakeQuestion("cz_conventional_commits"),
95+
],
96+
)
97+
mocker.patch("questionary.confirm", return_value=FakeQuestion(True))
98+
mocker.patch("questionary.text", return_value=FakeQuestion("$version"))
99+
mocker.patch("questionary.confirm", return_value=FakeQuestion(True))
100+
101+
def test_no_existing_pre_commit_conifg(_, tmpdir, config):
102+
with tmpdir.as_cwd():
103+
commands.Init(config)()
104+
105+
with open("pyproject.toml", "r") as toml_file:
106+
config_data = toml_file.read()
107+
assert config_data == expected_config
108+
109+
with open(pre_commit_config_filename, "r") as pre_commit_file:
110+
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
111+
assert pre_commit_config_data == {"repos": [cz_hook_config]}
112+
113+
def test_empty_pre_commit_config(_, tmpdir, config):
114+
with tmpdir.as_cwd():
115+
p = tmpdir.join(pre_commit_config_filename)
116+
p.write("")
117+
118+
commands.Init(config)()
119+
120+
with open("pyproject.toml", "r") as toml_file:
121+
config_data = toml_file.read()
122+
assert config_data == expected_config
123+
124+
with open(pre_commit_config_filename, "r") as pre_commit_file:
125+
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
126+
assert pre_commit_config_data == {"repos": [cz_hook_config]}
127+
128+
def test_pre_commit_config_without_cz_hook(_, tmpdir, config):
129+
existing_hook_config = {
130+
"repo": "https://github.com/pre-commit/pre-commit-hooks",
131+
"rev": "v1.2.3",
132+
"hooks": [{"id", "trailing-whitespace"}],
133+
}
134+
135+
with tmpdir.as_cwd():
136+
p = tmpdir.join(pre_commit_config_filename)
137+
p.write(yaml.safe_dump({"repos": [existing_hook_config]}))
138+
139+
commands.Init(config)()
140+
141+
with open("pyproject.toml", "r") as toml_file:
142+
config_data = toml_file.read()
143+
assert config_data == expected_config
144+
145+
with open(pre_commit_config_filename, "r") as pre_commit_file:
146+
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
147+
assert pre_commit_config_data == {
148+
"repos": [existing_hook_config, cz_hook_config]
149+
}
150+
151+
def test_cz_hook_exists_in_pre_commit_config(_, tmpdir, config):
152+
with tmpdir.as_cwd():
153+
p = tmpdir.join(pre_commit_config_filename)
154+
p.write(yaml.safe_dump({"repos": [cz_hook_config]}))
155+
156+
commands.Init(config)()
157+
158+
with open("pyproject.toml", "r") as toml_file:
159+
config_data = toml_file.read()
160+
assert config_data == expected_config
161+
162+
with open(pre_commit_config_filename, "r") as pre_commit_file:
163+
pre_commit_config_data = yaml.safe_load(pre_commit_file.read())
164+
165+
# check that config is not duplicated
166+
assert pre_commit_config_data == {"repos": [cz_hook_config]}

tests/test_git.py

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import pytest
22

33
from commitizen import git
4-
from tests.utils import create_file_and_commit
5-
6-
7-
class FakeCommand:
8-
def __init__(self, out=None, err=None):
9-
self.out = out
10-
self.err = err
4+
from tests.utils import FakeCommand, create_file_and_commit
115

126

137
def test_git_object_eq():

tests/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
from commitizen import cmd, git
66

77

8+
class FakeCommand:
9+
def __init__(self, out=None, err=None, return_code=0):
10+
self.out = out
11+
self.err = err
12+
self.return_code = return_code
13+
14+
815
def create_file_and_commit(message: str, filename: Optional[str] = None):
916
if not filename:
1017
filename = str(uuid.uuid4())

0 commit comments

Comments
 (0)