Files
grm/scripts/validate_commit_msg.py
T
Emil Simeonov 790b5c3769 GRM-20: refactor: rework all scripts to use click and i18n
- Replace argparse/print/sys.exit with click commands and ClickException
- Translate all user-facing messages via _()
- Add friendly Oops! / Nice! prompts
- Wrap HTTP errors in all scripts with user-friendly translated messages
- Update all unit tests to use CliRunner and expect ClickException
- Add 100% branch coverage for new HTTP error handling branches
- Add missing translation keys to i18n.py
- Fix pre-commit hook to use venv Python for validate_commit_msg.py
2026-06-19 20:11:20 +02:00

94 lines
2.7 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 click
from gitea_runner_manager.i18n import _
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 ""
@click.command()
@click.argument("commit_msg_file")
def main(commit_msg_file: str) -> None:
with open(commit_msg_file) as f:
msg = f.read().strip()
branch = get_branch()
subject = first_line(msg)
if branch == "master":
if not TASK_ID_RE.match(subject):
raise click.ClickException(
_(
"Oops! Master branch commits must start with a task ID.\n"
" Expected: GRM-N: <conventional commit message>\n"
" Got: {subject}",
subject=subject,
)
)
remainder = TASK_ID_RE.sub("", subject).strip()
if not CONVENTIONAL_RE.match(remainder):
raise click.ClickException(
_(
"Oops! Master branch commit must follow conventional format after task ID.\n"
" Expected: GRM-N: <type>: <description>\n"
" Got: {subject}",
subject=subject,
)
)
return
if TASK_ID_RE.match(subject):
raise click.ClickException(
_(
"Oops! Do not include task ID (GRM-N) in feature branch commits.\n"
" The task ID will be added automatically on merge via CI."
)
)
if not CONVENTIONAL_RE.match(subject):
raise click.ClickException(
_(
"Oops! Commit message must follow conventional commit format.\n"
" Expected: <type>: <description>\n"
" Got: {subject}\n"
" Allowed types: feat, fix, chore, docs, style, refactor,\n"
" perf, test, ci, build, revert, BREAKING CHANGE",
subject=subject,
)
)
if __name__ == "__main__": # pragma: no cover
main()