-
-
Notifications
You must be signed in to change notification settings - Fork 301
feat: add custom validation #1236
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
base: v4-11-0
Are you sure you want to change the base?
Changes from 7 commits
f66afbc
7bd16c3
67b5eb4
8dfe7b6
2dc3e78
8eaf37c
3596a06
784e3b4
904f200
33a06e9
ff7eee5
866101a
be8d7a9
e5805b6
f32bec0
75e8833
5f6482b
50405b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| Customizing commitizen is not hard at all. | ||
| from commitizen import BaseCommitizenCustomizing commitizen is not hard at all. | ||
benediktziegler marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| We have two different ways to do so. | ||
|
|
||
| ## 1. Customize in configuration file | ||
|
|
@@ -308,6 +308,72 @@ cz -n cz_strange bump | |
|
|
||
| [convcomms]: https://github.com/commitizen-tools/commitizen/blob/master/commitizen/cz/conventional_commits/conventional_commits.py | ||
|
|
||
| ### Custom commit validation and error message | ||
|
|
||
| The commit message validation can be customized by overriding the `validate_commit_message` and `format_error_message` | ||
| methods from `BaseCommitizen`. This allows for a more detailed feedback to the user where the error originates from. | ||
|
|
||
| ```python | ||
| import re | ||
|
|
||
| from commitizen.cz.base import BaseCommitizen | ||
| from commitizen import git | ||
|
|
||
|
|
||
| class CustomValidationCz(BaseCommitizen): | ||
| def validate_commit_message( | ||
| self, | ||
| commit_msg: str, | ||
| pattern: str | None, | ||
| allow_abort: bool, | ||
| allowed_prefixes: list[str], | ||
| max_msg_length: int, | ||
| ) -> tuple[bool, list]: | ||
|
||
| """Validate commit message against the pattern.""" | ||
| if not commit_msg: | ||
| return allow_abort, [] if allow_abort else [f"commit message is empty"] | ||
|
|
||
| if pattern is None: | ||
| return True, [] | ||
|
|
||
| if any(map(commit_msg.startswith, allowed_prefixes)): | ||
| return True, [] | ||
| if max_msg_length: | ||
| msg_len = len(commit_msg.partition("\n")[0].strip()) | ||
| if msg_len > max_msg_length: | ||
| return False, [ | ||
| f"commit message is too long. Max length is {max_msg_length}" | ||
| ] | ||
| pattern_match = re.match(pattern, commit_msg) | ||
| if pattern_match: | ||
| return True, [] | ||
| else: | ||
| # Perform additional validation of the commit message format | ||
| # and add custom error messages as needed | ||
| return False, ["commit message does not match the pattern"] | ||
|
|
||
| def format_exception_message( | ||
| self, ill_formated_commits: list[tuple[git.GitCommit, list]] | ||
| ) -> str: | ||
| """Format commit errors.""" | ||
| displayed_msgs_content = "\n".join( | ||
| [ | ||
| ( | ||
| f'commit "{commit.rev}": "{commit.message}"' | ||
| f"errors:\n" | ||
| "\n".join((f"- {error}" for error in errors)) | ||
| ) | ||
| for commit, errors in ill_formated_commits | ||
| ] | ||
| ) | ||
| return ( | ||
| "commit validation: failed!\n" | ||
| "please enter a commit message in the commitizen format.\n" | ||
| f"{displayed_msgs_content}\n" | ||
| f"pattern: {self.schema_pattern()}" | ||
| ) | ||
| ``` | ||
|
|
||
| ### Custom changelog generator | ||
|
|
||
| The changelog generator should just work in a very basic manner without touching anything. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -452,3 +452,44 @@ def test_check_command_with_message_length_limit_exceeded(config, mocker: MockFi | |
| with pytest.raises(InvalidCommitMessageError): | ||
| check_cmd() | ||
| error_mock.assert_called_once() | ||
|
|
||
|
|
||
| @pytest.mark.usefixtures("use_cz_custom_validator") | ||
| def test_check_command_with_custom_validator_succeed(mocker: MockFixture, capsys): | ||
| testargs = [ | ||
| "cz", | ||
| "--name", | ||
| "cz_custom_validator", | ||
| "check", | ||
| "--commit-msg-file", | ||
| "some_file", | ||
| ] | ||
| mocker.patch.object(sys, "argv", testargs) | ||
| mocker.patch( | ||
| "commitizen.commands.check.open", | ||
| mocker.mock_open(read_data="ABC-123: add commitizen pre-commit hook"), | ||
| ) | ||
| cli.main() | ||
| out, _ = capsys.readouterr() | ||
| assert "Commit validation: successful!" in out | ||
|
|
||
|
|
||
| @pytest.mark.usefixtures("use_cz_custom_validator") | ||
| def test_check_command_with_custom_validator_failed(mocker: MockFixture): | ||
| testargs = [ | ||
| "cz", | ||
| "--name", | ||
| "cz_custom_validator", | ||
| "check", | ||
| "--commit-msg-file", | ||
| "some_file", | ||
| ] | ||
| mocker.patch.object(sys, "argv", testargs) | ||
| mocker.patch( | ||
| "commitizen.commands.check.open", | ||
| mocker.mock_open(read_data="ABC-123 add commitizen pre-commit hook"), | ||
|
||
| ) | ||
| with pytest.raises(InvalidCommitMessageError) as excinfo: | ||
| cli.main() | ||
| assert "commit validation: failed!" in str(excinfo.value) | ||
| assert "commit message does not match pattern" in str(excinfo.value) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| import pytest | ||
| from pytest_mock import MockerFixture | ||
|
|
||
| from commitizen import cmd, defaults | ||
| from commitizen import cmd, defaults, git | ||
| from commitizen.changelog_formats import ( | ||
| ChangelogFormat, | ||
| get_changelog_format, | ||
|
|
@@ -231,6 +231,78 @@ def mock_plugin(mocker: MockerFixture, config: BaseConfig) -> BaseCommitizen: | |
| return mock | ||
|
|
||
|
|
||
| class ValidationCz(BaseCommitizen): | ||
| def questions(self): | ||
| return [ | ||
| {"type": "input", "name": "commit", "message": "Initial commit:\n"}, | ||
| {"type": "input", "name": "issue_nb", "message": "ABC-123"}, | ||
| ] | ||
|
|
||
| def message(self, answers: dict): | ||
| return f"{answers['issue_nb']}: {answers['commit']}" | ||
|
|
||
| def schema(self): | ||
| return "<issue_nb>: <commit>" | ||
|
|
||
| def schema_pattern(self): | ||
| return r"^(?P<issue_nb>[A-Z]{3}-\d+): (?P<commit>.*)$" | ||
|
|
||
| def validate_commit_message( | ||
| self, | ||
| commit_msg: str, | ||
| pattern: str | None, | ||
| allow_abort: bool, | ||
| allowed_prefixes: list[str], | ||
| max_msg_length: int, | ||
| ) -> tuple[bool, list]: | ||
| """Validate commit message against the pattern.""" | ||
| if not commit_msg: | ||
| return allow_abort, [] if allow_abort else ["commit message is empty"] | ||
|
|
||
| if pattern is None: | ||
| return True, [] | ||
|
|
||
| if any(map(commit_msg.startswith, allowed_prefixes)): | ||
| return True, [] | ||
| if max_msg_length: | ||
| msg_len = len(commit_msg.partition("\n")[0].strip()) | ||
| if msg_len > max_msg_length: | ||
| return False, [ | ||
| f"commit message is too long. Max length is {max_msg_length}" | ||
| ] | ||
| pattern_match = bool(re.match(pattern, commit_msg)) | ||
| if not pattern_match: | ||
| return False, [f"commit message does not match pattern {pattern}"] | ||
| return True, [] | ||
|
|
||
| def format_exception_message( | ||
| self, ill_formated_commits: list[tuple[git.GitCommit, list]] | ||
| ) -> str: | ||
| """Format commit errors.""" | ||
| displayed_msgs_content = "\n".join( | ||
| [ | ||
| ( | ||
| f'commit "{commit.rev}": "{commit.message}"\n' | ||
| f"errors:\n" | ||
| "\n".join(f"- {error}" for error in errors) | ||
| ) | ||
| for (commit, errors) in ill_formated_commits | ||
| ] | ||
| ) | ||
| return ( | ||
| "commit validation: failed!\n" | ||
| "please enter a commit message in the commitizen format.\n" | ||
| f"{displayed_msgs_content}\n" | ||
| f"pattern: {self.schema_pattern}" | ||
| ) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def use_cz_custom_validator(mocker): | ||
|
||
| new_cz = {**registry, "cz_custom_validator": ValidationCz} | ||
| mocker.patch.dict("commitizen.cz.registry", new_cz) | ||
|
|
||
|
|
||
| SUPPORTED_FORMATS = ("markdown", "textile", "asciidoc", "restructuredtext") | ||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.