Skip to content

feat: add an argument to limit the length of commit message #1076

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

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,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": ["-l", "--message-length-limit"],
"type": int,
"default": 0,
"help": "length limit of the commit message; 0 for no limit",
},
],
},
{
Expand Down
4 changes: 3 additions & 1 deletion commitizen/commands/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def prompt_commit_questions(self) -> str:

if not answers:
raise NoAnswersError()
return cz.message(answers)

message_length_limit: int = self.arguments.get("message_length_limit", 0)
return cz.message(answers, message_length_limit=message_length_limit)

def __call__(self):
dry_run: bool = self.arguments.get("dry_run")
Expand Down
12 changes: 11 additions & 1 deletion commitizen/cz/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from commitizen import git
from commitizen.config.base_config import BaseConfig
from commitizen.defaults import Questions
from commitizen.exceptions import CommitMessageLengthExceededError


class MessageBuilderHook(Protocol):
Expand Down Expand Up @@ -71,7 +72,7 @@ def questions(self) -> Questions:
"""Questions regarding the commit message."""

@abstractmethod
def message(self, answers: dict) -> str:
def message(self, answers: dict, message_length_limit: int) -> str:
"""Format your git message."""

@property
Expand Down Expand Up @@ -105,3 +106,12 @@ def process_commit(self, commit: str) -> str:
If not overwritten, it returns the first line of commit.
"""
return commit.split("\n")[0]

def _check_message_length_limit(
self, message: str, message_length_limit: int
) -> None:
message_len = len(message)
if message_length_limit > 0 and message_len > message_length_limit:
raise CommitMessageLengthExceededError(
f"Length of commit message exceeds limit ({message_len}/{message_length_limit})"
)
8 changes: 4 additions & 4 deletions commitizen/cz/conventional_commits/conventional_commits.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def questions(self) -> Questions:
]
return questions

def message(self, answers: dict) -> str:
def message(self, answers: dict, message_length_limit: int = 0) -> str:
prefix = answers["prefix"]
scope = answers["scope"]
subject = answers["subject"]
Expand All @@ -167,9 +167,9 @@ def message(self, answers: dict) -> str:
if footer:
footer = f"\n\n{footer}"

message = f"{prefix}{scope}: {subject}{body}{footer}"

return message
message = f"{prefix}{scope}: {subject}"
self._check_message_length_limit(message, message_length_limit)
return f"{message}{body}{footer}"

def example(self) -> str:
return (
Expand Down
13 changes: 8 additions & 5 deletions commitizen/cz/customize/customize.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,15 @@ def __init__(self, config: BaseConfig):
def questions(self) -> Questions:
return self.custom_settings.get("questions", [{}])

def message(self, answers: dict) -> str:
def message(self, answers: dict, message_length_limit: int = 0) -> str:
message_template = Template(self.custom_settings.get("message_template", ""))
if getattr(Template, "substitute", None):
return message_template.substitute(**answers) # type: ignore
else:
return message_template.render(**answers)
message: str = (
message_template.substitute(**answers) # type: ignore
if getattr(Template, "substitute", None)
else message_template.render(**answers)
)
self._check_message_length_limit(message, message_length_limit)
return message

def example(self) -> str | None:
return self.custom_settings.get("example")
Expand Down
6 changes: 4 additions & 2 deletions commitizen/cz/jira/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def questions(self) -> Questions:
]
return questions

def message(self, answers) -> str:
return " ".join(
def message(self, answers: dict, message_length_limit: int = 0) -> str:
message = " ".join(
filter(
bool,
[
Expand All @@ -57,6 +57,8 @@ def message(self, answers) -> str:
],
)
)
self._check_message_length_limit(message, message_length_limit)
return message

def example(self) -> str:
return (
Expand Down
5 changes: 5 additions & 0 deletions commitizen/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ExitCode(enum.IntEnum):
CHANGELOG_FORMAT_UNKNOWN = 29
CONFIG_FILE_NOT_FOUND = 30
CONFIG_FILE_IS_EMPTY = 31
COMMIT_MESSAGE_LENGTH_LIMIT_EXCEEDED = 32


class CommitizenException(Exception):
Expand Down Expand Up @@ -201,3 +202,7 @@ class ConfigFileNotFound(CommitizenException):
class ConfigFileIsEmpty(CommitizenException):
exit_code = ExitCode.CONFIG_FILE_IS_EMPTY
message = "Config file is empty, please check your file path again."


class CommitMessageLengthExceededError(CommitizenException):
exit_code = ExitCode.COMMIT_MESSAGE_LENGTH_LIMIT_EXCEEDED
10 changes: 10 additions & 0 deletions docs/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ You can use `cz commit --retry` to reuse the last commit message when the previo
To automatically retry when running `cz commit`, you can set the `retry_after_failure`
configuration option to `true`. Running `cz commit --no-retry` makes commitizen ignore `retry_after_failure`, forcing
a new commit message to be prompted.

### Commit message length limit

The argument `-l` (or `--message-length-limit`) followed by a positive number can limit the length of commit messages.
An exception would be raised when the message length exceeds the limit.
For example, `cz commit -l 72` will limit the length of commit messages to 72 characters.
By default the limit is set to 0, which means no limit on the length.

Note that for `ConventionalCommitsCz`, the limit applies only from the prefix to the subject.
In other words, everything after the first line (the body and the footer) are not counted in the length.
6 changes: 4 additions & 2 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,11 @@ class JiraCz(BaseCommitizen):
]
return questions

def message(self, answers: dict) -> str:
def message(self, answers: dict, message_length_limit: int = 0) -> str:
"""Generate the message with the given answers."""
return "{0} (#{1})".format(answers["title"], answers["issue"])
message = "{0} (#{1})".format(answers["title"], answers["issue"])
self._check_message_length_limit(message, message_length_limit)
return message

def example(self) -> str:
"""Provide an example to help understand the style (OPTIONAL)
Expand Down
8 changes: 5 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,12 @@ def questions(self) -> list:
},
]

def message(self, answers: dict) -> str:
def message(self, answers: dict, message_length_limit: int = 0) -> str:
prefix = answers["prefix"]
subject = answers.get("subject", "default message").trim()
return f"{prefix}: {subject}"
message = f"{prefix}: {subject}"
self._check_message_length_limit(message, message_length_limit)
return message


@pytest.fixture()
Expand All @@ -220,7 +222,7 @@ class MockPlugin(BaseCommitizen):
def questions(self) -> defaults.Questions:
return []

def message(self, answers: dict) -> str:
def message(self, answers: dict, message_length_limit: int = 0) -> str:
return ""


Expand Down
20 changes: 18 additions & 2 deletions tests/test_cz_base.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import pytest

from commitizen.cz.base import BaseCommitizen
from commitizen.exceptions import CommitMessageLengthExceededError


class DummyCz(BaseCommitizen):
def questions(self):
return [{"type": "input", "name": "commit", "message": "Initial commit:\n"}]

def message(self, answers):
return answers["commit"]
def message(self, answers: dict, message_length_limit: int = 0):
message = answers["commit"]
self._check_message_length_limit(message, message_length_limit)
return message


def test_base_raises_error(config):
Expand Down Expand Up @@ -48,3 +51,16 @@ def test_process_commit(config):
cz = DummyCz(config)
message = cz.process_commit("test(test_scope): this is test msg")
assert message == "test(test_scope): this is test msg"


def test_message_length_limit(config):
cz = DummyCz(config)
commit_message = "123456789"
message_length = len(commit_message)
assert cz.message({"commit": commit_message}) == commit_message
assert (
cz.message({"commit": commit_message}, message_length_limit=message_length)
== commit_message
)
with pytest.raises(CommitMessageLengthExceededError):
cz.message({"commit": commit_message}, message_length_limit=message_length - 1)