diff --git a/scripts/lint_commits.py b/scripts/lint_commits.py index 10cd7029..4b664dfd 100755 --- a/scripts/lint_commits.py +++ b/scripts/lint_commits.py @@ -17,6 +17,24 @@ # below 50 characters, with occasional outliers. COMMIT_MSG_MAX_SUMMARY_LEN = 100 +# Conventional commit types (https://www.conventionalcommits.org/) +CONVENTIONAL_COMMIT_TYPES = { + "feat", + "fix", + "docs", + "style", + "refactor", + "perf", + "test", + "chore", + "ci", + "build", + "revert", +} + +# Matches: [()][!]: +CONVENTIONAL_COMMIT_RE = re.compile(r"^(?P[a-z]+)(\([^)]+\))?!?: .+") + def error(msg: str, commit: str | None = None) -> None: """Log error.""" @@ -84,6 +102,25 @@ def lint_commit_message(commit: str) -> bool: ) success = False + # Check conventional commit format. + cc_match = CONVENTIONAL_COMMIT_RE.match(lines[0]) + if cc_match is None: + error( + f"The summary line {lines[0]!r} does not follow the conventional " + "commit format. Expected: [()][!]: . " + f"Valid types are: {', '.join(sorted(CONVENTIONAL_COMMIT_TYPES))}. " + "See https://www.conventionalcommits.org/ for details.", + commit, + ) + success = False + elif cc_match.group("type") not in CONVENTIONAL_COMMIT_TYPES: + error( + f"The commit type {cc_match.group('type')!r} is not recognised. " + f"Valid types are: {', '.join(sorted(CONVENTIONAL_COMMIT_TYPES))}.", + commit, + ) + success = False + # Check for an empty line separating the summary line from the long # description. if len(lines) > 1 and lines[1] != "":