Public Access
102 lines
3.2 KiB
Python
102 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate commit messages for devx.
|
|
|
|
Rules:
|
|
- On feature branches: conventional commits ONLY, must NOT include <PREFIX>-N prefix.
|
|
- On master branch: must follow '<task-id>: <conventional commit>' pattern,
|
|
e.g. 'DEVX-24: fix: resolve timeout'.
|
|
|
|
The task ID prefix is configurable via the ``DEVX_TASK_PREFIX`` environment
|
|
variable (default: ``DEVX``). Projects consuming devx (e.g., GRM) set
|
|
their own prefix (e.g., ``GRM``) so the validator enforces the correct
|
|
task ID format for each project.
|
|
"""
|
|
|
|
import re
|
|
import subprocess # nosec B404
|
|
|
|
import click
|
|
|
|
from devx.config import CONVENTIONAL_RE, TASK_PREFIX
|
|
from devx.i18n import _
|
|
|
|
MASTER_TASK_ID_RE = re.compile(rf"^{TASK_PREFIX}-\d+:")
|
|
|
|
|
|
def first_line(text: str) -> str:
|
|
return text.split("\n")[0]
|
|
|
|
|
|
def get_branch() -> str:
|
|
try:
|
|
result = subprocess.run( # nosec
|
|
["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")
|
|
@click.option("--branch", default=None, help="Override branch detection (for CI use).")
|
|
def main(commit_msg_file: str, branch: str | None) -> None:
|
|
with open(commit_msg_file) as f:
|
|
msg = f.read().strip()
|
|
|
|
if branch is None:
|
|
branch = get_branch()
|
|
subject = first_line(msg)
|
|
|
|
if branch == "master":
|
|
if not MASTER_TASK_ID_RE.match(subject):
|
|
raise click.ClickException(
|
|
_(
|
|
"Oops! Master branch commits must start with a task ID.\n"
|
|
" Expected: {prefix}-N: <conventional commit message>\n"
|
|
" Got: {subject}",
|
|
prefix=TASK_PREFIX,
|
|
subject=subject,
|
|
)
|
|
)
|
|
remainder = MASTER_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: {prefix}-N: <type>: <description>\n"
|
|
" Got: {subject}",
|
|
prefix=TASK_PREFIX,
|
|
subject=subject,
|
|
)
|
|
)
|
|
return
|
|
|
|
if MASTER_TASK_ID_RE.match(subject):
|
|
raise click.ClickException(
|
|
_(
|
|
"Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n"
|
|
" The task ID will be added automatically on merge via CI.",
|
|
prefix=TASK_PREFIX,
|
|
)
|
|
)
|
|
|
|
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()
|