Skip to content

Commit

Permalink
feat(commands/commit): add force-edit functionality after answering q…
Browse files Browse the repository at this point in the history
…uestions
  • Loading branch information
josix committed Oct 15, 2024
1 parent 00d2d96 commit 30409a4
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 1 deletion.
6 changes: 6 additions & 0 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ def __call__(
"action": "store_true",
"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.",
},
{
"name": ["-e", "--edit"],
"action": "store_true",
"default": False,
"help": "edit the commit message before committing",
},
{
"name": ["-l", "--message-length-limit"],
"type": int,
Expand Down
24 changes: 24 additions & 0 deletions commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import contextlib
import os
import shutil
import subprocess
import tempfile

import questionary

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

return message

def force_edit(self, message: str) -> str:
editor = git.get_core_editor()
if editor is None:
raise RuntimeError("No 'editor' value given and no default available.")

Check warning on line 81 in commitizen/commands/commit.py

View check run for this annotation

Codecov / codecov/patch

commitizen/commands/commit.py#L81

Added line #L81 was not covered by tests
exec_path = shutil.which(editor)
if exec_path is None:
raise RuntimeError(f"Editor '{editor}' not found.")

Check warning on line 84 in commitizen/commands/commit.py

View check run for this annotation

Codecov / codecov/patch

commitizen/commands/commit.py#L84

Added line #L84 was not covered by tests
with tempfile.NamedTemporaryFile(mode="w", delete=False) as file:
file.write(message)
file_path = file.name
argv = [exec_path, file_path]
subprocess.call(argv)
with open(file_path) as temp_file:
message = temp_file.read().strip()
file.unlink()
return message

def __call__(self):
dry_run: bool = self.arguments.get("dry_run")
write_message_to_file: bool = self.arguments.get("write_message_to_file")
force_edit: bool = self.arguments.get("edit")

is_all: bool = self.arguments.get("all")
if is_all:
Expand All @@ -101,6 +122,9 @@ def __call__(self):
else:
m = self.prompt_commit_questions()

if force_edit:
m = self.force_edit(m)

Check warning on line 126 in commitizen/commands/commit.py

View check run for this annotation

Codecov / codecov/patch

commitizen/commands/commit.py#L126

Added line #L126 was not covered by tests

out.info(f"\n{m}\n")

if write_message_to_file:
Expand Down
7 changes: 7 additions & 0 deletions commitizen/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ def get_eol_style() -> EOLTypes:
return map["native"]


def get_core_editor() -> str | None:
c = cmd.run("git var GIT_EDITOR")
if c.out:
return c.out.strip()
return None


def smart_open(*args, **kargs):
"""Open a file with the EOL style determined from Git."""
return open(*args, newline=get_eol_style().get_eol_for_open(), **kargs)
Expand Down
25 changes: 25 additions & 0 deletions tests/commands/test_commit_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,31 @@ def test_commit_command_with_message_length_limit(config, mocker: MockFixture):
commands.Commit(config, {"message_length_limit": message_length - 1})()


@pytest.mark.usefixtures("staging_is_clean")
def test_force_edit(config, mocker: MockFixture, tmp_path):
mocker.patch("commitizen.git.get_core_editor", return_value="vim")
subprocess_mock = mocker.patch("subprocess.call")

mocker.patch("shutil.which", return_value="vim")

test_message = "Initial commit message"
temp_file = tmp_path / "temp_commit_message"
temp_file.write_text(test_message)

mock_temp_file = mocker.patch("tempfile.NamedTemporaryFile")
mock_temp_file.return_value.__enter__.return_value.name = str(temp_file)

commit_instance = commands.Commit(config, {"edit": True})

edited_message = commit_instance.force_edit(test_message)

subprocess_mock.assert_called_once_with(["vim", str(temp_file)])

assert edited_message == test_message.strip()

temp_file.unlink()


@skip_below_py_3_13
def test_commit_command_shows_description_when_use_help_option(
mocker: MockFixture, capsys, file_regression
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
usage: cz commit [-h] [--retry] [--no-retry] [--dry-run]
[--write-message-to-file FILE_PATH] [-s] [-a]
[--write-message-to-file FILE_PATH] [-s] [-a] [-e]
[-l MESSAGE_LENGTH_LIMIT]

create new commit
Expand All @@ -16,5 +16,6 @@ options:
-a, --all 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.
-e, --edit edit the commit message before committing
-l, --message-length-limit MESSAGE_LENGTH_LIMIT
length limit of the commit message; 0 for no limit
20 changes: 20 additions & 0 deletions tests/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,26 @@ def test_eoltypes_get_eol_for_open():
assert git.EOLTypes.get_eol_for_open(git.EOLTypes.CRLF) == "\r\n"


def test_get_core_editor(mocker):
mocker.patch.dict(os.environ, {"GIT_EDITOR": "nano"})
assert git.get_core_editor() == "nano"

mocker.patch.dict(os.environ, clear=True)
mocker.patch(
"commitizen.cmd.run",
return_value=cmd.Command(
out="vim", err="", stdout=b"", stderr=b"", return_code=0
),
)
assert git.get_core_editor() == "vim"

mocker.patch(
"commitizen.cmd.run",
return_value=cmd.Command(out="", err="", stdout=b"", stderr=b"", return_code=1),
)
assert git.get_core_editor() is None


def test_create_tag_with_message(tmp_commitizen_project):
with tmp_commitizen_project.as_cwd():
create_file_and_commit("feat(test): test")
Expand Down

0 comments on commit 30409a4

Please sign in to comment.