Skip to content

Commit

Permalink
feat: add an argument to limit the length of commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin1kevin1k authored and Lee-W committed Apr 30, 2024
1 parent 9b45956 commit 88ef59b
Show file tree
Hide file tree
Showing 12 changed files with 80 additions and 20 deletions.
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
2 changes: 2 additions & 0 deletions commitizen/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Settings(TypedDict, total=False):
always_signoff: bool
template: str | None
extras: dict[str, Any]
message_length_limit: int


name: str = "cz_conventional_commits"
Expand Down Expand Up @@ -102,6 +103,7 @@ class Settings(TypedDict, total=False):
"always_signoff": False,
"template": None, # default provided by plugin
"extras": {},
"message_length_limit": 0,
}

MAJOR = "MAJOR"
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)

0 comments on commit 88ef59b

Please sign in to comment.