Skip to content

Commit 033159d

Browse files
committed
feat(commands/commit): apply prepare-commit-msg hook
add --commit-msg-file argument
1 parent 750213c commit 033159d

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
@@ -48,6 +48,14 @@
4848
"action": "store_true",
4949
"help": "show output to stdout, no commit, no modified files",
5050
},
51+
{
52+
"name": "--commit-msg-file",
53+
"help": (
54+
"ask for the name of the temporal file that contains "
55+
"the commit message. "
56+
"Using it in a git hook script: MSG_FILE=$1"
57+
),
58+
},
5159
],
5260
},
5361
{

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

@@ -58,6 +74,15 @@ def prompt_commit_questions(self) -> str:
5874
def __call__(self):
5975
dry_run: bool = self.arguments.get("dry_run")
6076

77+
commit_msg_file: str = self.arguments.get("commit_msg_file")
78+
if commit_msg_file:
79+
old_stdin = sys.stdin
80+
old_stdout = sys.stdout
81+
old_stderr = sys.stderr
82+
sys.stdin = WrapStdin()
83+
sys.stdout = open("/dev/tty", "w")
84+
sys.stderr = open("/dev/tty", "w")
85+
6186
if git.is_staging_clean() and not dry_run:
6287
raise NothingToCommitError("No files added to staging!")
6388

@@ -73,6 +98,22 @@ def __call__(self):
7398
if dry_run:
7499
raise DryRunExit()
75100

101+
if commit_msg_file:
102+
sys.stdin.close()
103+
sys.stdout.close()
104+
sys.stderr.close()
105+
sys.stdin = old_stdin
106+
sys.stdout = old_stdout
107+
sys.stderr = old_stderr
108+
defaultmesaage = ""
109+
with open(commit_msg_file) as f:
110+
defaultmesaage = f.read()
111+
with open(commit_msg_file, "w") as f:
112+
f.write(m)
113+
f.write(defaultmesaage)
114+
out.success("Commit message is successful!")
115+
return
116+
76117
c = git.commit(m)
77118

78119
if c.return_code != 0:

docs/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ cz c
7979
```
8080

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

8585
```yaml
@@ -90,6 +90,8 @@ repos:
9090
hooks:
9191
- id: commitizen
9292
stages: [commit-msg]
93+
- id: commitizen-prepare-commit-msg
94+
stages: [prepare-commit-msg]
9395
```
9496
9597
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,
@@ -158,3 +159,41 @@ def test_commit_in_non_git_project(tmpdir, config):
158159
with tmpdir.as_cwd():
159160
with pytest.raises(NotAGitProjectError):
160161
commands.Commit(config, {})
162+
163+
164+
def test_commit_from_pre_commit_msg_hook(config, mocker, capsys):
165+
testargs = ["cz", "commit", "--commit-msg-file", "some_file"]
166+
mocker.patch.object(sys, "argv", testargs)
167+
168+
prompt_mock = mocker.patch("questionary.prompt")
169+
prompt_mock.return_value = {
170+
"prefix": "feat",
171+
"subject": "user created",
172+
"scope": "",
173+
"is_breaking_change": False,
174+
"body": "",
175+
"footer": "",
176+
}
177+
178+
commit_mock = mocker.patch("commitizen.git.commit")
179+
mocker.patch("commitizen.commands.commit.WrapStdin")
180+
mocker.patch("os.open")
181+
reader_mock = mocker.mock_open(read_data="\n\n#test\n")
182+
mocker.patch("builtins.open", reader_mock, create=True)
183+
184+
cli.main()
185+
186+
out, _ = capsys.readouterr()
187+
assert "Commit message is successful!" in out
188+
commit_mock.assert_not_called()
189+
190+
191+
def test_WrapStdin(mocker):
192+
mocker.patch("os.open")
193+
reader_mock = mocker.mock_open(read_data="data")
194+
mocker.patch("builtins.open", reader_mock, create=True)
195+
196+
wrap_stdin = commands.commit.WrapStdin()
197+
198+
assert wrap_stdin.encoding == "UTF-8"
199+
assert wrap_stdin.read() == "data"

0 commit comments

Comments
 (0)