#!/usr/bin/env python3 """Validate commit messages for GRM. Rules: - On master branch: allow anything (merge commits already have task ID). - On feature branches: must use conventional commit format, must NOT include GRM-N prefix. """ 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 ") sys.exit(1) with open(argv[1]) as f: msg = f.read().strip() branch = get_branch() if branch == "master": return subject = first_line(msg) 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: : ") 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()