#!/usr/bin/env python3 """Enforce Conventional Commits on the first line of the commit message. https://www.conventionalcommits.org/en/v1.0.0/ Activate per clone with: git config core.hooksPath .githooks """ from __future__ import annotations import re import sys from pathlib import Path ALLOWED_PREFIXES = ("Merge ", "Revert ", "fixup! ", "squash! ", "amend! ") PATTERN = re.compile( r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)" r"(\([a-z0-9._-]+\))?!?: .+" ) def main(argv: list[str]) -> int: if len(argv) < 1: print("commit-msg: missing message file path", file=sys.stderr) return 1 msg_file = Path(argv[0]) text = msg_file.read_text(encoding="utf-8", errors="replace") first_line = text.splitlines()[0] if text else "" if any(first_line.startswith(p) for p in ALLOWED_PREFIXES): return 0 if not PATTERN.match(first_line): sys.stderr.write("commit-msg: aborting — message does not follow Conventional Commits.\n") sys.stderr.write(" expected: [()][!]: \n") sys.stderr.write(" types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert\n") sys.stderr.write(f" got: {first_line}\n") return 1 return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))