-
-
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 13 commits
f66afbc
7bd16c3
67b5eb4
8dfe7b6
2dc3e78
8eaf37c
3596a06
784e3b4
904f200
33a06e9
ff7eee5
866101a
be8d7a9
e5805b6
f32bec0
75e8833
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 |
|---|---|---|
|
|
@@ -309,6 +309,73 @@ 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]: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also use |
||
| """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"), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make it obvious wrong. I read a few time to notice the missing |
||
| ) | ||
| 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,14 +9,14 @@ | |
| 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, | ||
| ) | ||
| from commitizen.config import BaseConfig | ||
| from commitizen.cz import registry | ||
| from commitizen.cz.base import BaseCommitizen | ||
| from commitizen.cz.base import BaseCommitizen, ValidationResult | ||
| from tests.utils import create_file_and_commit | ||
|
|
||
| SIGNER = "GitHub Action" | ||
|
|
@@ -238,6 +238,84 @@ 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, | ||
| ) -> ValidationResult: | ||
| """Validate commit message against the pattern.""" | ||
| if not commit_msg: | ||
| return ValidationResult( | ||
| allow_abort, [] if allow_abort else ["commit message is empty"] | ||
| ) | ||
|
|
||
| if pattern is None: | ||
| return ValidationResult(True, []) | ||
|
|
||
| if any(map(commit_msg.startswith, allowed_prefixes)): | ||
| return ValidationResult(True, []) | ||
| if max_msg_length: | ||
| msg_len = len(commit_msg.partition("\n")[0].strip()) | ||
| if msg_len > max_msg_length: | ||
| return ValidationResult( | ||
| 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 ValidationResult( | ||
| False, [f"commit message does not match pattern {pattern}"] | ||
| ) | ||
| return ValidationResult(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): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: If it's only used in |
||
| 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.