diff options
Diffstat (limited to 'buildscripts/validate_commit_message.py')
| -rwxr-xr-x | buildscripts/validate_commit_message.py | 270 |
1 files changed, 23 insertions, 247 deletions
diff --git a/buildscripts/validate_commit_message.py b/buildscripts/validate_commit_message.py index 114a799dd0c..da01fa75a62 100755 --- a/buildscripts/validate_commit_message.py +++ b/buildscripts/validate_commit_message.py @@ -28,270 +28,46 @@ # """Validate that the commit message is ok.""" import argparse -import logging -import os import re import sys -from typing import List, Optional - -from evergreen import EvergreenApi, RetryingEvergreenApi - -from buildscripts.client.jiraclient import JiraAuth, JiraClient, SecurityLevel - -JIRA_SERVER = "https://jira.mongodb.org" -EVG_CONFIG_FILE = "~/.evergreen.yml" -SERVER_TICKET_PREFIX = "SERVER-" -PUBLIC_PROJECT_PREFIX = "mongodb-mongo-" +import logging LOGGER = logging.getLogger(__name__) -ERROR_MSG = """ -################################################################################ -Encountered an invalid commit message. Please correct to the commit message to -continue. - -Commit message should start with a Public Jira ticket, an "Import" for wiredtiger -or tools, or a "Revert" message. - -{error_msg} on '{branch}': -'{commit_message}' -################################################################################ -""" - -COMMON_PUBLIC_PATTERN = r""" - ((?P<revert>Revert)\s+[\"\']?)? # Revert (optional) - ((?P<ticket>(?:EVG|SERVER|WT)-[0-9]+)[\"\']?\s*) # ticket identifier - (?P<body>(?:(?!\(cherry\spicked\sfrom).)*)? # To also capture the body - (?P<backport>\(cherry\spicked\sfrom.*)? # back port (optional) - """ -"""Common Public pattern format.""" - -COMMON_10GENREPO_COMMIT_QUEUE_PATTERN = r' ^\'(?P<repo>10gen/mongo)\'\s.*commit\squeue\smerge.*SERVER-[0-9]+' -"""Common commit queue format.""" - -COMMON_LINT_PATTERN = r"(?P<lint>Fix\slint)" -"""Common Lint pattern format.""" - -COMMON_IMPORT_PATTERN = r"(?P<imported>Import\s(wiredtiger|tools):\s.*)" -"""Common Import pattern format.""" - -COMMON_REVERT_IMPORT_PATTERN = (r"Revert\s+[\"\']?(?P<imported>Import\s(wiredtiger|tools):\s.*)") -"""Common revert Import pattern format.""" - -COMMON_PRIVATE_PATTERN = r""" - ((?P<revert>Revert)\s+[\"\']?)? # Revert (optional) - ((?P<ticket>[A-Z]+-[0-9]+)[\"\']?\s*) # ticket identifier - (?P<body>(?:(?!('\s(into\s'(([^/]+))/(([^:]+)):(([^']+))'))).)*)? # To also capture the body -""" -"""Common Private pattern format.""" - STATUS_OK = 0 STATUS_ERROR = 1 -def new_patch_description(pattern: str) -> str: - """ - Wrap the pattern to conform to the new commit queue patch description format. - - Add the commit queue prefix and suffix to the pattern. The format looks like: - - Commit Queue Merge: '<commit message>' into '<owner>/<repo>:<branch>' - - :param pattern: The pattern to wrap. - :return: A pattern to match the new format for the patch description. - """ - return (r"""^((?P<commitqueue>Commit\sQueue\sMerge:)\s')""" - f"{pattern}" - # r"""('\s(?P<into>into\s'((?P<owner>[^/]+))/((?P<repo>[^:]+)):((?P<branch>[^']+))'))""" - ) - - -def old_patch_description(pattern: str) -> str: - """ - Wrap the pattern to conform to the new commit queue patch description format. - - Just add a start anchor. The format looks like: - - <commit message> - - :param pattern: The pattern to wrap. - :return: A pattern to match the old format for the patch description. - """ - return r"^" f"{pattern}" - - -# NOTE: re.VERBOSE is for visibility / debugging. As such significant white space must be -# escaped (e.g ' ' to \s). -COMMON_PUBLIC_PATTERNS = [ - re.compile( - new_patch_description(COMMON_PUBLIC_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - old_patch_description(COMMON_PUBLIC_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), -] -"""common public patterns.""" - -VALID_PATTERNS = [ - re.compile( - new_patch_description(COMMON_LINT_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - old_patch_description(COMMON_LINT_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - new_patch_description(COMMON_IMPORT_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - old_patch_description(COMMON_IMPORT_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - new_patch_description(COMMON_REVERT_IMPORT_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - old_patch_description(COMMON_REVERT_IMPORT_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), -] -"""valid public patterns.""" - -PRIVATE_PATTERNS = [ - re.compile( - new_patch_description(COMMON_PRIVATE_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - old_patch_description(COMMON_PRIVATE_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE, - ), - re.compile( - new_patch_description(COMMON_10GENREPO_COMMIT_QUEUE_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE), - re.compile( - old_patch_description(COMMON_10GENREPO_COMMIT_QUEUE_PATTERN), - re.MULTILINE | re.DOTALL | re.VERBOSE), -] -"""private patterns.""" - - -class CommitMessageValidationOrchestrator: - """An orchestrator to validate that commit messages are valid.""" - - def __init__(self, evg_api: EvergreenApi, jira_client: JiraClient) -> None: - """ - Initialize the orchestrator. - - :param evg_api: Evergreen API client. - :param jira_client: Client to Jira API. - """ - self.evg_api = evg_api - self.jira_client = jira_client - - def validate_ticket(self, ticket: str, project: str) -> bool: - """ - Check that the given Jira ticket has a proper security level. - - Commits targeting a public project should not have a defined security level (these are - public by default). - - :param ticket: Ticket to check. - :param project: Project commit is targeting. - :return: True if ticket is valid. - """ - if ticket.startswith(SERVER_TICKET_PREFIX) and project.startswith(PUBLIC_PROJECT_PREFIX): - security_level = self.jira_client.get_ticket_security_level(ticket) - return security_level == SecurityLevel.NONE - return True - - def validate_msg(self, message: str, project: str) -> bool: - """ - Check that the given message is valid. - - :param message: Commit message to validate. - :param project: Project commit is targeting. - :return: True if the message is valid. - """ - for pattern in COMMON_PUBLIC_PATTERNS: - match = pattern.match(message) - if not match: - continue - if not self.validate_ticket(match.group("ticket"), project): - print( - ERROR_MSG.format( - error_msg="Reference to a internal Jira Ticket", - branch=project, - commit_message=message, - )) - return False - return True - - valid_matches = [valid_pattern.match(message) for valid_pattern in VALID_PATTERNS] - if any(valid_matches): - return True - elif any(private_pattern.match(message) for private_pattern in PRIVATE_PATTERNS): - print( - ERROR_MSG.format( - error_msg="Reference to a private project", - branch=project, - commit_message=message, - )) - return False - else: - print( - ERROR_MSG.format( - error_msg="Commit without a ticket", - branch=project, - commit_message=message, - )) - return False - - def validate_commit_messages(self, version_id: str) -> int: - """ - Validate the commit messages for the given build. - - :param version_id: ID of version to validate. - :param evg_api: Evergreen API client. - :return: True if all commit messages were valid. - """ - found_error = False - code_changes = self.evg_api.patch_by_id(version_id).module_code_changes - for change in code_changes: - for message in change.commit_messages: - is_valid = self.validate_msg(message, change.branch_name) - found_error = found_error or not is_valid - - return STATUS_ERROR if found_error else STATUS_OK - - -def main(argv: Optional[List[str]] = None) -> int: +def main(argv=None): """Execute Main function to validate commit messages.""" parser = argparse.ArgumentParser( usage="Validate the commit message. " "It validates the latest message when no arguments are provided.") parser.add_argument( - "version_id", - metavar="version id", - help="The id of the version to validate", - ) - parser.add_argument( - "--evg-config-file", - default=EVG_CONFIG_FILE, - help="Path to evergreen configuration file containing auth information.", + "message", + metavar="commit message", + nargs="*", + help="The commit message to validate", ) args = parser.parse_args(argv) - evg_api = RetryingEvergreenApi.get_api(config_file=os.path.expanduser(args.evg_config_file)) - jira_auth = JiraAuth() - jira_client = JiraClient(JIRA_SERVER, jira_auth) - orchestrator = CommitMessageValidationOrchestrator(evg_api, jira_client) - return orchestrator.validate_commit_messages(args.version_id) + if not args.message: + LOGGER.error("Must specify non-empty value for --message") + return STATUS_ERROR + message = " ".join(args.message) + + # Valid values look like: + # 1. SERVER-\d+ + # 2. Revert "SERVER-\d+ + # 3. Import wiredtiger + # 4. Revert "Import wiredtiger + valid_pattern = re.compile(r'(Revert ")?(SERVER-[0-9]+|Import wiredtiger)') + + if valid_pattern.match(message): + return STATUS_OK + else: + LOGGER.error(f"Found a commit without a ticket\n{message}") # pylint: disable=logging-fstring-interpolation + return STATUS_ERROR if __name__ == "__main__": |
