Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(commit): add --write-message-to-file option #731

Merged
merged 11 commits into from
May 1, 2023
Merged
7 changes: 6 additions & 1 deletion commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,15 @@
"action": "store_true",
"help": "show output to stdout, no commit, no modified files",
},
{
"name": "--write-message-to-file",
crai0 marked this conversation as resolved.
Show resolved Hide resolved
"metavar": "FILE_PATH",
"help": "write message to file before commiting (can be combined with --dry-run)",
},
{
"name": ["-s", "--signoff"],
"action": "store_true",
"help": "Sign off the commit",
"help": "sign off the commit",
woile marked this conversation as resolved.
Show resolved Hide resolved
},
],
},
Expand Down
13 changes: 13 additions & 0 deletions commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
NoAnswersError,
NoCommitBackupError,
NotAGitProjectError,
NotAllowed,
NothingToCommitError,
)
from commitizen.git import smart_open
Expand Down Expand Up @@ -63,10 +64,18 @@ def prompt_commit_questions(self) -> str:

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

if git.is_staging_clean() and not dry_run:
raise NothingToCommitError("No files added to staging!")

if write_message_to_file is not None:
crai0 marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(write_message_to_file, str):
raise NotAllowed(
"Commit message file name is broken.\n"
"Check the flag `--write-message-to-file` in the terminal"
)
woile marked this conversation as resolved.
Show resolved Hide resolved

retry: bool = self.arguments.get("retry")

if retry:
Expand All @@ -76,6 +85,10 @@ def __call__(self):

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

if write_message_to_file:
with smart_open(write_message_to_file, "w") as file:
file.write(m)

if dry_run:
raise DryRunExit()

Expand Down
4 changes: 4 additions & 0 deletions docs/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ In your terminal run `cz commit` or the shortcut `cz c` to generate a guided git

A commit can be signed off using `cz commit --signoff` or the shortcut `cz commit -s`.

You can run `cz commit --write-message-to-file COMMIT_MSG_FILE` to additionally save the
generated message to a file. This can be combined with the `--dry-run` flag to only
write the message to a file and not modify files and create a commit.

crai0 marked this conversation as resolved.
Show resolved Hide resolved
!!! note
To maintain platform compatibility, the `commit` command disable ANSI escaping in its output.
In particular pre-commit hooks coloring will be deactivated as discussed in [commitizen-tools/commitizen#417](https://github.com/commitizen-tools/commitizen/issues/417).
27 changes: 27 additions & 0 deletions docs/tutorials/auto_prepare_commit_message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Automatically prepare message before commit

## About

To automatically prepare a commit message prior to committing, you can use a [Git hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks).
crai0 marked this conversation as resolved.
Show resolved Hide resolved

## How to

- Step 1: Create a new [`prepare-commit-msg`][prepare-commit-msg-docs] Git hook by running the following commands from the root of the Git repository:

```sh
cd .git/hooks
touch prepare-commit-msg
chmod +x prepare-commit-msg
```

- Step 2: Edit the newly created file and add the following content:

```sh
#!/bin/sh
COMMIT_MSG_FILE=$1
exec < /dev/tty && cz commit --dry-run --write-message-to-file $COMMIT_MSG_FILE || true
```

See the Git hooks documentation on [`prepare-commit-msg` hooks][prepare-commit-msg-docs] for details on how this works.

[prepare-commit-msg-docs]: https://git-scm.com/docs/githooks#_prepare_commit_msg
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ nav:
- Tutorials:
- Writing commits: "tutorials/writing_commits.md"
- Auto check commits: "tutorials/auto_check.md"
- Auto prepare commit message: "tutorials/auto_prepare_commit_message.md"
- GitLab CI: "tutorials/gitlab_ci.md"
- Github Actions: "tutorials/github_actions.md"
- Jenkins pipeline: "tutorials/jenkins_pipeline.md"
Expand Down
48 changes: 48 additions & 0 deletions tests/commands/test_commit_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
NoAnswersError,
NoCommitBackupError,
NotAGitProjectError,
NotAllowed,
NothingToCommitError,
)

Expand Down Expand Up @@ -109,6 +110,53 @@ def test_commit_command_with_dry_run_option(config, mocker: MockFixture):
commit_cmd()


@pytest.mark.usefixtures("staging_is_clean")
def test_commit_command_with_write_message_to_file_option(
crai0 marked this conversation as resolved.
Show resolved Hide resolved
config, tmp_path, mocker: MockFixture
):
tmp_file = tmp_path / "message"

prompt_mock = mocker.patch("questionary.prompt")
prompt_mock.return_value = {
"prefix": "feat",
"subject": "user created",
"scope": "",
"is_breaking_change": False,
"body": "",
"footer": "",
}

commit_mock = mocker.patch("commitizen.git.commit")
commit_mock.return_value = cmd.Command("success", "", b"", b"", 0)
success_mock = mocker.patch("commitizen.out.success")

commands.Commit(config, {"write_message_to_file": str(tmp_file)})()
success_mock.assert_called_once()
assert tmp_file.exists()
assert tmp_file.read_text() == "feat: user created"


@pytest.mark.usefixtures("staging_is_clean")
@pytest.mark.parametrize("message_file", [True, False, 0, 1])
def test_commit_command_with_invalid_write_message_to_file_option(
config, message_file, mocker: MockFixture
):
prompt_mock = mocker.patch("questionary.prompt")
prompt_mock.return_value = {
"prefix": "feat",
"subject": "user created",
"scope": "",
"is_breaking_change": False,
"body": "",
"footer": "",
}

with pytest.raises(NotAllowed):
print(isinstance(message_file, str))
commit_cmd = commands.Commit(config, {"write_message_to_file": message_file})
commit_cmd()


@pytest.mark.usefixtures("staging_is_clean")
def test_commit_command_with_signoff_option(config, mocker: MockFixture):
prompt_mock = mocker.patch("questionary.prompt")
Expand Down