Skip to content

Commit 60a0f09

Browse files
committed
feat(commands/commit): add force-edit functionality after answering questions
1 parent 00d2d96 commit 60a0f09

File tree

5 files changed

+65
-2
lines changed

5 files changed

+65
-2
lines changed

commitizen/cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ def __call__(
156156
"action": "store_true",
157157
"help": "Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.",
158158
},
159+
{
160+
"name": ["-e", "--edit"],
161+
"action": "store_true",
162+
"default": False,
163+
"help": "edit the commit message before committing",
164+
},
159165
{
160166
"name": ["-l", "--message-length-limit"],
161167
"type": int,

commitizen/commands/commit.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import contextlib
44
import os
5+
import shutil
6+
import subprocess
7+
import tempfile
58

69
import questionary
710

@@ -72,9 +75,27 @@ def prompt_commit_questions(self) -> str:
7275

7376
return message
7477

78+
def force_edit(self, message: str) -> str:
79+
editor = git.get_core_editor()
80+
if editor is None:
81+
raise RuntimeError("No 'editor' value given and no default available.")
82+
exec_path = shutil.which(editor)
83+
if exec_path is None:
84+
raise RuntimeError(f"Editor '{editor}' not found.")
85+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as file:
86+
file.write(message)
87+
file_path = file.name
88+
argv = [exec_path, file_path]
89+
subprocess.call(argv)
90+
with open(file_path) as temp_file:
91+
message = temp_file.read().strip()
92+
file.unlink()
93+
return message
94+
7595
def __call__(self):
7696
dry_run: bool = self.arguments.get("dry_run")
7797
write_message_to_file: bool = self.arguments.get("write_message_to_file")
98+
force_edit: bool = self.arguments.get("edit")
7899

79100
is_all: bool = self.arguments.get("all")
80101
if is_all:
@@ -101,6 +122,9 @@ def __call__(self):
101122
else:
102123
m = self.prompt_commit_questions()
103124

125+
if force_edit:
126+
m = self.force_edit(m)
127+
104128
out.info(f"\n{m}\n")
105129

106130
if write_message_to_file:

commitizen/git.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ def get_eol_style() -> EOLTypes:
271271
return map["native"]
272272

273273

274+
def get_core_editor() -> str | None:
275+
c = cmd.run("git var GIT_EDITOR")
276+
if c.out:
277+
return c.out.strip()
278+
return None
279+
280+
274281
def smart_open(*args, **kargs):
275282
"""Open a file with the EOL style determined from Git."""
276283
return open(*args, newline=get_eol_style().get_eol_for_open(), **kargs)

tests/commands/test_commit_command.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,31 @@ def test_commit_command_with_message_length_limit(config, mocker: MockFixture):
410410
commands.Commit(config, {"message_length_limit": message_length - 1})()
411411

412412

413+
@pytest.mark.usefixtures("staging_is_clean")
414+
def test_force_edit(config, mocker: MockFixture, tmp_path):
415+
mocker.patch("commitizen.git.get_core_editor", return_value="vim")
416+
subprocess_mock = mocker.patch("subprocess.call")
417+
418+
mocker.patch("shutil.which", return_value="vim")
419+
420+
test_message = "Initial commit message"
421+
temp_file = tmp_path / "temp_commit_message"
422+
temp_file.write_text(test_message)
423+
424+
mock_temp_file = mocker.patch("tempfile.NamedTemporaryFile")
425+
mock_temp_file.return_value.__enter__.return_value.name = str(temp_file)
426+
427+
commit_instance = commands.Commit(config, {"edit": True})
428+
429+
edited_message = commit_instance.force_edit(test_message)
430+
431+
subprocess_mock.assert_called_once_with(["vim", str(temp_file)])
432+
433+
assert edited_message == test_message.strip()
434+
435+
temp_file.unlink()
436+
437+
413438
@skip_below_py_3_13
414439
def test_commit_command_shows_description_when_use_help_option(
415440
mocker: MockFixture, capsys, file_regression

tests/commands/test_commit_command/test_commit_command_shows_description_when_use_help_option.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
usage: cz commit [-h] [--retry] [--no-retry] [--dry-run]
2-
[--write-message-to-file FILE_PATH] [-s] [-a]
2+
[--write-message-to-file FILE_PATH] [-s] [-a] [-e]
33
[-l MESSAGE_LENGTH_LIMIT]
44

55
create new commit
@@ -16,5 +16,6 @@ options:
1616
-a, --all Tell the command to automatically stage files that
1717
have been modified and deleted, but new files you have
1818
not told Git about are not affected.
19-
-l, --message-length-limit MESSAGE_LENGTH_LIMIT
19+
-e, --edit edit the commit message before committing
20+
-l MESSAGE_LENGTH_LIMIT, --message-length-limit MESSAGE_LENGTH_LIMIT
2021
length limit of the commit message; 0 for no limit

0 commit comments

Comments
 (0)