diff --git a/CHANGELOG.md b/CHANGELOG.md index 519d30c791..efa9b61c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## Unreleased + +### Feature + +- new retry argument to execute previous commit again + ## v1.5.1 ### Fix diff --git a/commitizen/cli.py b/commitizen/cli.py index c4ae2d2b16..11e933000e 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -41,6 +41,13 @@ "name": ["commit", "c"], "help": "create new commit", "func": commands.Commit, + "arguments": [ + { + "name": ["--retry"], + "action": "store_true", + "help": "retry last commit", + }, + ] }, { "name": "example", diff --git a/commitizen/commands/commit.py b/commitizen/commands/commit.py index 182980e102..d05c6651cd 100644 --- a/commitizen/commands/commit.py +++ b/commitizen/commands/commit.py @@ -1,30 +1,56 @@ +import contextlib +import os import questionary +import tempfile + from commitizen import factory, out, git NO_ANSWERS = 5 COMMIT_ERROR = 6 +NO_COMMIT_BACKUP = 7 class Commit: """Show prompt for the user to create a guided commit.""" - def __init__(self, config: dict, *args): + def __init__(self, config: dict, arguments: dict): self.config: dict = config self.cz = factory.commiter_factory(self.config) + self.arguments = arguments + self.temp_file: str = os.path.join(tempfile.gettempdir(), "cz.commit.backup") def __call__(self): - cz = self.cz - questions = cz.questions() - answers = questionary.prompt(questions) - if not answers: - raise SystemExit(NO_ANSWERS) - m = cz.message(answers) + retry: bool = self.arguments.get("retry") + + if retry: + # Check the commit backup file exists + if not os.path.isfile(self.temp_file): + out.error("No commit backup found") + raise SystemExit(NO_COMMIT_BACKUP) + + # Read commit message from backup + with open(self.temp_file, "r") as f: + m = f.read().strip() + else: + # Prompt user for the commit message + cz = self.cz + questions = cz.questions() + answers = questionary.prompt(questions) + if not answers: + raise SystemExit(NO_ANSWERS) + m = cz.message(answers) + out.info(f"\n{m}\n") c = git.commit(m) if c.err: out.error(c.err) + + # Create commit backup + with open(self.temp_file, "w") as f: + f.write(m) + raise SystemExit(COMMIT_ERROR) if "nothing added" in c.out or "no changes added to commit" in c.out: @@ -32,5 +58,7 @@ def __call__(self): elif c.err: out.error(c.err) else: + with contextlib.suppress(FileNotFoundError): + os.remove(self.temp_file) out.write(c.out) out.success("Commit successful!") diff --git a/tests/test_cli.py b/tests/test_cli.py index 9cfd3653c8..03e7c4e51b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -10,7 +10,7 @@ def test_sysexit_no_argv(): def test_ls(mocker, capsys): testargs = ["cz", "-n", "cz_jira", "ls"] - mocker.patch.object(sys, 'argv', testargs) + mocker.patch.object(sys, "argv", testargs) cli.main() out, err = capsys.readouterr() @@ -21,5 +21,5 @@ def test_ls(mocker, capsys): def test_version(mocker): testargs = ["cz", "--version"] with pytest.raises(SystemExit): - mocker.patch.object(sys, 'argv', testargs) - cli.main() \ No newline at end of file + mocker.patch.object(sys, "argv", testargs) + cli.main() diff --git a/tests/test_commands.py b/tests/test_commands.py index 9ef0fe1421..bc3c1041c3 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,5 +1,7 @@ -# import pytest +import os +import pytest from unittest import mock + from commitizen import defaults, commands, cmd config = {"name": defaults.name} @@ -20,8 +22,53 @@ def test_commit(mocker): commit_mock.return_value = cmd.Command("success", "", "", "") success_mock = mocker.patch("commitizen.out.success") - commands.Commit(config)() + commands.Commit(config, {})() + success_mock.assert_called_once() + + +def test_commit_retry_fails_no_backup(mocker): + commit_mock = mocker.patch("commitizen.git.commit") + commit_mock.return_value = cmd.Command("success", "", "", "") + + with pytest.raises(SystemExit): + commands.Commit(config, {"retry": True})() + + +def test_commit_retry_works(mocker): + prompt_mock = mocker.patch("questionary.prompt") + prompt_mock.return_value = { + "prefix": "feat", + "subject": "user created", + "scope": "", + "is_breaking_change": False, + "body": "closes #21", + "footer": "", + } + + commit_mock = mocker.patch("commitizen.git.commit") + commit_mock.return_value = cmd.Command("", "error", "", "") + error_mock = mocker.patch("commitizen.out.error") + + with pytest.raises(SystemExit): + commit_cmd = commands.Commit(config, {}) + temp_file = commit_cmd.temp_file + commit_cmd() + + prompt_mock.assert_called_once() + error_mock.assert_called_once() + assert os.path.isfile(temp_file) + + # Previous commit failed, so retry should pick up the backup commit + # commit_mock = mocker.patch("commitizen.git.commit") + commit_mock.return_value = cmd.Command("success", "", "", "") + success_mock = mocker.patch("commitizen.out.success") + + commands.Commit(config, {"retry": True})() + + commit_mock.assert_called_with("feat: user created\n\ncloses #21") + prompt_mock.assert_called_once() success_mock.assert_called_once() + assert not os.path.isfile(temp_file) def test_example():