Skip to content

Commit ab4e9ab

Browse files
committed
feat(commands/commit): apply prepare-commit-msg hook
add --commit-msg-file argument
1 parent d6a1e23 commit ab4e9ab

File tree

5 files changed

+100
-2
lines changed

5 files changed

+100
-2
lines changed

.pre-commit-hooks.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@
66
language_version: python3
77
require_serial: true
88
minimum_pre_commit_version: "0.15.4"
9+
- id: commitizen-prepare-commit-msg
10+
name: commitizen prepare commit msg
11+
description: "prepare commit message"
12+
entry: cz commit --commit-msg-file
13+
language: python
14+
language_version: python3
15+
require_serial: true
16+
minimum_pre_commit_version: "0.15.4"

commitizen/cli.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@
5454
"action": "store_true",
5555
"help": "Sign off the commit",
5656
},
57+
{
58+
"name": "--commit-msg-file",
59+
"help": (
60+
"ask for the name of the temporal file that contains "
61+
"the commit message. "
62+
"Using it in a git hook script: MSG_FILE=$1"
63+
),
64+
},
5765
],
5866
},
5967
{

commitizen/commands/commit.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import contextlib
22
import os
3+
import sys
34
import tempfile
45

56
import questionary
@@ -18,6 +19,21 @@
1819
)
1920

2021

22+
class WrapStdin:
23+
def __init__(self):
24+
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
25+
tty = open(fd, "wb+", buffering=0)
26+
self.tty = tty
27+
28+
def __getattr__(self, key):
29+
if key == "encoding":
30+
return "UTF-8"
31+
return getattr(self.tty, key)
32+
33+
def __del__(self):
34+
self.tty.close()
35+
36+
2137
class Commit:
2238
"""Show prompt for the user to create a guided commit."""
2339

@@ -63,6 +79,15 @@ def prompt_commit_questions(self) -> str:
6379
def __call__(self):
6480
dry_run: bool = self.arguments.get("dry_run")
6581

82+
commit_msg_file: str = self.arguments.get("commit_msg_file")
83+
if commit_msg_file:
84+
old_stdin = sys.stdin
85+
old_stdout = sys.stdout
86+
old_stderr = sys.stderr
87+
sys.stdin = WrapStdin()
88+
sys.stdout = open("/dev/tty", "w")
89+
sys.stderr = open("/dev/tty", "w")
90+
6691
if git.is_staging_clean() and not dry_run:
6792
raise NothingToCommitError("No files added to staging!")
6893

@@ -78,6 +103,22 @@ def __call__(self):
78103
if dry_run:
79104
raise DryRunExit()
80105

106+
if commit_msg_file:
107+
sys.stdin.close()
108+
sys.stdout.close()
109+
sys.stderr.close()
110+
sys.stdin = old_stdin
111+
sys.stdout = old_stdout
112+
sys.stderr = old_stderr
113+
defaultmesaage = ""
114+
with open(commit_msg_file) as f:
115+
defaultmesaage = f.read()
116+
with open(commit_msg_file, "w") as f:
117+
f.write(m)
118+
f.write(defaultmesaage)
119+
out.success("Commit message is successful!")
120+
return
121+
81122
signoff: bool = self.arguments.get("signoff")
82123

83124
if signoff:

docs/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ cz commit -s
101101
```
102102

103103
### Integrating with Pre-commit
104-
Commitizen can lint your commit message for you with `cz check`.
104+
Commitizen can lint your commit message for you with `cz check` and `cz commit`.
105105
You can integrate this in your [pre-commit](https://pre-commit.com/) config with:
106106

107107
```yaml
@@ -112,6 +112,8 @@ repos:
112112
hooks:
113113
- id: commitizen
114114
stages: [commit-msg]
115+
- id: commitizen-prepare-commit-msg
116+
stages: [prepare-commit-msg]
115117
```
116118
117119
After the configuration is added, you'll need to run

tests/commands/test_commit_command.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import os
2+
import sys
23

34
import pytest
45

5-
from commitizen import cmd, commands
6+
from commitizen import cli, cmd, commands
67
from commitizen.cz.exceptions import CzException
78
from commitizen.exceptions import (
89
CommitError,
@@ -178,3 +179,41 @@ def test_commit_in_non_git_project(tmpdir, config):
178179
with tmpdir.as_cwd():
179180
with pytest.raises(NotAGitProjectError):
180181
commands.Commit(config, {})
182+
183+
184+
def test_commit_from_pre_commit_msg_hook(config, mocker, capsys):
185+
testargs = ["cz", "commit", "--commit-msg-file", "some_file"]
186+
mocker.patch.object(sys, "argv", testargs)
187+
188+
prompt_mock = mocker.patch("questionary.prompt")
189+
prompt_mock.return_value = {
190+
"prefix": "feat",
191+
"subject": "user created",
192+
"scope": "",
193+
"is_breaking_change": False,
194+
"body": "",
195+
"footer": "",
196+
}
197+
198+
commit_mock = mocker.patch("commitizen.git.commit")
199+
mocker.patch("commitizen.commands.commit.WrapStdin")
200+
mocker.patch("os.open")
201+
reader_mock = mocker.mock_open(read_data="\n\n#test\n")
202+
mocker.patch("builtins.open", reader_mock, create=True)
203+
204+
cli.main()
205+
206+
out, _ = capsys.readouterr()
207+
assert "Commit message is successful!" in out
208+
commit_mock.assert_not_called()
209+
210+
211+
def test_WrapStdin(mocker):
212+
mocker.patch("os.open")
213+
reader_mock = mocker.mock_open(read_data="data")
214+
mocker.patch("builtins.open", reader_mock, create=True)
215+
216+
wrap_stdin = commands.commit.WrapStdin()
217+
218+
assert wrap_stdin.encoding == "UTF-8"
219+
assert wrap_stdin.read() == "data"

0 commit comments

Comments
 (0)