- Add scripts/validate_commit_msg.py with conventional commit enforcement - Add scripts/configure_repo.py for Gitea branch protection and labels - Add scripts/__init__.py for Python package importability - Create Gitea Actions workflows: ci, auto-merge, post-merge, publish - Update .pre-commit-config.yaml with commit-msg hook - Update pyproject.toml pythonpath and coverage for scripts - Add comprehensive unit tests for both scripts with 100% coverage
65 lines
1.8 KiB
Python
65 lines
1.8 KiB
Python
#!/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 <commit-msg-file>")
|
|
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: <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()
|