11from __future__ import annotations
22
33from abc import ABCMeta , abstractmethod
4- from typing import TYPE_CHECKING , Any , Callable , Protocol
4+ from collections .abc import Iterable , Mapping
5+ from typing import TYPE_CHECKING , Any , Callable , NamedTuple , Protocol
56
67from jinja2 import BaseLoader , PackageLoader
78from prompt_toolkit .styles import Style
89
10+ from commitizen .exceptions import CommitMessageLengthExceededError
11+
912if TYPE_CHECKING :
13+ import re
1014 from collections .abc import Iterable , Mapping
1115
1216 from commitizen import git
@@ -26,6 +30,11 @@ def __call__(
2630 ) -> dict [str , Any ]: ...
2731
2832
33+ class ValidationResult (NamedTuple ):
34+ is_valid : bool
35+ errors : list
36+
37+
2938class BaseCommitizen (metaclass = ABCMeta ):
3039 bump_pattern : str | None = None
3140 bump_map : dict [str , str ] | None = None
@@ -43,7 +52,7 @@ class BaseCommitizen(metaclass=ABCMeta):
4352 ("disabled" , "fg:#858585 italic" ),
4453 ]
4554
46- # The whole subject will be parsed as message by default
55+ # The whole subject will be parsed as a message by default
4756 # This allows supporting changelog for any rule system.
4857 # It can be modified per rule
4958 commit_parser : str | None = r"(?P<message>.*)"
@@ -101,3 +110,55 @@ def schema_pattern(self) -> str:
101110 @abstractmethod
102111 def info (self ) -> str :
103112 """Information about the standardized commit message."""
113+
114+ def validate_commit_message (
115+ self ,
116+ * ,
117+ commit_msg : str ,
118+ pattern : re .Pattern [str ],
119+ allow_abort : bool ,
120+ allowed_prefixes : list [str ],
121+ max_msg_length : int | None ,
122+ commit_hash : str ,
123+ ) -> ValidationResult :
124+ """Validate commit message against the pattern."""
125+ if not commit_msg :
126+ return ValidationResult (
127+ allow_abort , [] if allow_abort else ["commit message is empty" ]
128+ )
129+
130+ if any (map (commit_msg .startswith , allowed_prefixes )):
131+ return ValidationResult (True , [])
132+
133+ if max_msg_length is not None :
134+ msg_len = len (commit_msg .partition ("\n " )[0 ].strip ())
135+ if msg_len > max_msg_length :
136+ # TODO: capitalize the first letter of the error message for consistency in v5
137+ raise CommitMessageLengthExceededError (
138+ f"commit validation: failed!\n "
139+ f"commit message length exceeds the limit.\n "
140+ f'commit "{ commit_hash } ": "{ commit_msg } "\n '
141+ f"message length limit: { max_msg_length } (actual: { msg_len } )"
142+ )
143+
144+ return ValidationResult (
145+ bool (pattern .match (commit_msg )),
146+ [f"pattern: { pattern .pattern } " ],
147+ )
148+
149+ def format_exception_message (
150+ self , invalid_commits : list [tuple [git .GitCommit , list ]]
151+ ) -> str :
152+ """Format commit errors."""
153+ displayed_msgs_content = "\n " .join (
154+ [
155+ f'commit "{ commit .rev } ": "{ commit .message } \n "' + "\n " .join (errors )
156+ for commit , errors in invalid_commits
157+ ]
158+ )
159+ # TODO: capitalize the first letter of the error message for consistency in v5
160+ return (
161+ "commit validation: failed!\n "
162+ "please enter a commit message in the commitizen format.\n "
163+ f"{ displayed_msgs_content } "
164+ )
0 commit comments