Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ff45e19

Browse files
authored
Merge pull request #236 from commitizen-tools/add-pre-commit-in-init-cmd
Setup pre-commit hook in "cz init"
2 parents 7638381 + c619c87 commit ff45e19

File tree

5 files changed

+202
-63
lines changed

5 files changed

+202
-63
lines changed

‎commitizen/commands/init.py‎

Lines changed: 53 additions & 3 deletions
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,11 +34,14 @@ 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.")
3744
else:
38-
# TODO: handle the case that config file exist but no value
3945
out.line(f"Config file {self.config.path} already exists")
4046

4147
def _ask_config_path(self) -> str:
@@ -99,6 +105,50 @@ def _ask_tag_format(self, latest_tag) -> str:
99105
tag_format = "$version"
100106
return tag_format
101107

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+
102152
def _update_config_file(self, values):
103153
for key, value in values.items():
104154
self.config.set_key(key, value)

‎tests/commands/test_init_command.py‎

Lines changed: 107 additions & 10 deletions
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_conf.py‎

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -54,63 +54,54 @@
5454

5555

5656
@pytest.fixture
57-
def configure_supported_files():
58-
original = defaults.config_files.copy()
57+
def config_files_manager(request, tmpdir):
58+
with tmpdir.as_cwd():
59+
filename = request.param
60+
with open(filename, "w") as f:
61+
if "toml" in filename:
62+
f.write(PYPROJECT)
63+
yield
5964

60-
# patch the defaults to include tests
61-
defaults.config_files = [os.path.join("tests", f) for f in defaults.config_files]
62-
yield
63-
defaults.config_files = original
64-
65-
66-
@pytest.fixture
67-
def config_files_manager(request):
68-
filename = request.param
69-
filepath = os.path.join("tests", filename)
70-
with open(filepath, "w") as f:
71-
if "toml" in filename:
72-
f.write(PYPROJECT)
73-
yield
74-
os.remove(filepath)
75-
76-
77-
@pytest.fixture
78-
def empty_pyproject_ok_cz():
79-
pyproject = "tests/pyproject.toml"
80-
with open(pyproject, "w") as f:
81-
f.write("")
82-
yield
83-
os.remove(pyproject)
84-
85-
86-
@pytest.mark.parametrize(
87-
"config_files_manager", defaults.config_files.copy(), indirect=True
88-
)
89-
def test_load_conf(config_files_manager, configure_supported_files):
90-
cfg = config.read_cfg()
91-
assert cfg.settings == _settings
9265

66+
def test_find_git_project_root(tmpdir):
67+
assert git.find_git_project_root() == Path(os.getcwd())
9368

94-
def test_conf_returns_default_when_no_files(configure_supported_files):
95-
cfg = config.read_cfg()
96-
assert cfg.settings == defaults.DEFAULT_SETTINGS
69+
with tmpdir.as_cwd() as _:
70+
assert git.find_git_project_root() is None
9771

9872

9973
@pytest.mark.parametrize(
10074
"config_files_manager", defaults.config_files.copy(), indirect=True
10175
)
102-
def test_set_key(configure_supported_files, config_files_manager):
76+
def test_set_key(config_files_manager):
10377
_conf = config.read_cfg()
10478
_conf.set_key("version", "2.0.0")
10579
cfg = config.read_cfg()
10680
assert cfg.settings == _new_settings
10781

10882

109-
def test_find_git_project_root(tmpdir):
110-
assert git.find_git_project_root() == Path(os.getcwd())
111-
112-
with tmpdir.as_cwd() as _:
113-
assert git.find_git_project_root() is None
83+
class TestReadCfg:
84+
@pytest.mark.parametrize(
85+
"config_files_manager", defaults.config_files.copy(), indirect=True
86+
)
87+
def test_load_conf(_, config_files_manager):
88+
cfg = config.read_cfg()
89+
assert cfg.settings == _settings
90+
91+
def test_conf_returns_default_when_no_files(_, tmpdir):
92+
with tmpdir.as_cwd():
93+
cfg = config.read_cfg()
94+
assert cfg.settings == defaults.DEFAULT_SETTINGS
95+
96+
def test_load_empty_pyproject_toml_and_cz_toml_with_config(_, tmpdir):
97+
with tmpdir.as_cwd():
98+
p = tmpdir.join("pyproject.toml")
99+
p.write("")
100+
p = tmpdir.join(".cz.toml")
101+
p.write(PYPROJECT)
102+
103+
cfg = config.read_cfg()
104+
assert cfg.settings == _settings
114105

115106

116107
class TestTomlConfig:

‎tests/test_git.py‎

Lines changed: 1 addition & 7 deletions
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‎

Lines changed: 7 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /