Skip to content

Commit 08a259d

Browse files
josixLee-W
andcommitted
feat(commands/commit): add force-edit functionality after answering questions
refactor: remove redundant return None Co-authored-by: Wei Lee <[email protected]> Update test_commit_command.py Co-authored-by: Wei Lee <[email protected]>
1 parent 2f6b7cc commit 08a259d

File tree

8 files changed

+148
-56
lines changed

8 files changed

+148
-56
lines changed

Diff for: commitizen/cli.py

+6
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,

Diff for: commitizen/commands/commit.py

+24
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 manual_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+
manual_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 manual_edit:
126+
m = self.manual_edit(m)
127+
104128
out.info(f"\n{m}\n")
105129

106130
if write_message_to_file:

Diff for: commitizen/git.py

+7
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)

Diff for: docs/contributing.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ If you're a first-time contributor, you can check the issues with [good first is
2222
(We use [CodeCov](https://codecov.io/) to ensure our test coverage does not drop.)
2323
7. Use [commitizen](https://github.com/commitizen-tools/commitizen) to do git commit. We follow [conventional commits](https://www.conventionalcommits.org/).
2424
8. Run `./scripts/format` and `./scripts/test` to ensure you follow the coding style and the tests pass.
25-
9. Optionally, update the `./docs/README.md`.
25+
9. Optionally, update the `./docs/README.md` or `docs/images/cli_help` (through running `scripts/gen_cli_help_screenshots.py`).
2626
9. **Do not** update the `CHANGELOG.md`, it will be automatically created after merging to `master`.
2727
10. **Do not** update the versions in the project, they will be automatically updated.
2828
10. If your changes are about documentation. Run `poetry run mkdocs serve` to serve documentation locally and check whether there is any warning or error.

Diff for: docs/images/cli_help/cz_commit___help.svg

+58-54
Loading

Diff for: tests/commands/test_commit_command.py

+30
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,36 @@ 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+
@pytest.mark.parametrize("editor", ["vim", None])
415+
def test_manual_edit(editor, config, mocker: MockFixture, tmp_path):
416+
mocker.patch("commitizen.git.get_core_editor", return_value=editor)
417+
subprocess_mock = mocker.patch("subprocess.call")
418+
419+
mocker.patch("shutil.which", return_value=editor)
420+
421+
test_message = "Initial commit message"
422+
temp_file = tmp_path / "temp_commit_message"
423+
temp_file.write_text(test_message)
424+
425+
mock_temp_file = mocker.patch("tempfile.NamedTemporaryFile")
426+
mock_temp_file.return_value.__enter__.return_value.name = str(temp_file)
427+
428+
commit_cmd = commands.Commit(config, {"edit": True})
429+
430+
if editor is None:
431+
with pytest.raises(RuntimeError):
432+
commit_cmd.manual_edit(test_message)
433+
else:
434+
edited_message = commit_cmd.manual_edit(test_message)
435+
436+
subprocess_mock.assert_called_once_with(["vim", str(temp_file)])
437+
438+
assert edited_message == test_message.strip()
439+
440+
temp_file.unlink()
441+
442+
413443
@skip_below_py_3_13
414444
def test_commit_command_shows_description_when_use_help_option(
415445
mocker: MockFixture, capsys, file_regression

Diff for: tests/commands/test_commit_command/test_commit_command_shows_description_when_use_help_option.txt

+2-1
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+
-e, --edit edit the commit message before committing
1920
-l, --message-length-limit MESSAGE_LENGTH_LIMIT
2021
length limit of the commit message; 0 for no limit

Diff for: tests/test_git.py

+20
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,26 @@ def test_eoltypes_get_eol_for_open():
283283
assert git.EOLTypes.get_eol_for_open(git.EOLTypes.CRLF) == "\r\n"
284284

285285

286+
def test_get_core_editor(mocker):
287+
mocker.patch.dict(os.environ, {"GIT_EDITOR": "nano"})
288+
assert git.get_core_editor() == "nano"
289+
290+
mocker.patch.dict(os.environ, clear=True)
291+
mocker.patch(
292+
"commitizen.cmd.run",
293+
return_value=cmd.Command(
294+
out="vim", err="", stdout=b"", stderr=b"", return_code=0
295+
),
296+
)
297+
assert git.get_core_editor() == "vim"
298+
299+
mocker.patch(
300+
"commitizen.cmd.run",
301+
return_value=cmd.Command(out="", err="", stdout=b"", stderr=b"", return_code=1),
302+
)
303+
assert git.get_core_editor() is None
304+
305+
286306
def test_create_tag_with_message(tmp_commitizen_project):
287307
with tmp_commitizen_project.as_cwd():
288308
create_file_and_commit("feat(test): test")

0 commit comments

Comments
 (0)