78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate commit messages for GRM.
|
|
|
|
Rules:
|
|
- On feature branches: conventional commits ONLY, must NOT include GRM-N prefix.
|
|
- On master branch: must follow '<task-id>: <conventional commit>' pattern,
|
|
e.g. 'GRM-24: fix: resolve timeout'.
|
|
"""
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
CONVENTIONAL_RE = re.compile(
|
|
r"^(feat|fix|chore|docs|style|refactor|perf|test|ci|build|revert|BREAKING CHANGE)(\(.+\))?: .+"
|
|
)
|
|
TASK_ID_RE = re.compile(r"^GRM-\d+:")
|
|
|
|
|
|
def first_line(text: str) -> str:
|
|
return text.split("\n")[0]
|
|
|
|
|
|
def get_branch() -> str:
|
|
try:
|
|
result = subprocess.run(
|
|
["git", "symbolic-ref", "--short", "HEAD"],
|
|
capture_output=True, text=True, check=True,
|
|
)
|
|
return result.stdout.strip()
|
|
except subprocess.CalledProcessError:
|
|
return ""
|
|
|
|
|
|
def main(args=None) -> None:
|
|
argv = args if args is not None else sys.argv
|
|
if len(argv) < 2:
|
|
print("Usage: validate_commit_msg.py <commit-msg-file>")
|
|
sys.exit(1)
|
|
|
|
with open(argv[1]) as f:
|
|
msg = f.read().strip()
|
|
|
|
branch = get_branch()
|
|
|
|
subject = first_line(msg)
|
|
|
|
if branch == "master":
|
|
if not TASK_ID_RE.match(subject):
|
|
print("ERROR: Master branch commits must start with a task ID.")
|
|
print(" Expected: GRM-N: <conventional commit message>")
|
|
print(f" Got: {subject}")
|
|
sys.exit(1)
|
|
# Strip task-id prefix and validate the remainder as conventional
|
|
remainder = TASK_ID_RE.sub("", subject).strip()
|
|
if not CONVENTIONAL_RE.match(remainder):
|
|
print("ERROR: Master branch commit message must follow conventional format after task ID.")
|
|
print(" Expected: GRM-N: <type>: <description>")
|
|
print(f" Got: {subject}")
|
|
sys.exit(1)
|
|
return
|
|
|
|
if TASK_ID_RE.match(subject):
|
|
print("ERROR: Do not include task ID (GRM-N) in feature branch commits.")
|
|
print(" Task ID will be added automatically on merge via CI.")
|
|
sys.exit(1)
|
|
|
|
if not CONVENTIONAL_RE.match(subject):
|
|
print("ERROR: Commit message must follow conventional commit format.")
|
|
print(" Expected: <type>: <description>")
|
|
print(f" Got: {subject}")
|
|
print(" Allowed types: feat, fix, chore, docs, style, refactor,")
|
|
print(" perf, test, ci, build, revert, BREAKING CHANGE")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main()
|