Skip to content

Instantly share code, notes, and snippets.

@bindreams
Created January 29, 2025 11:02
Show Gist options
  • Save bindreams/b977a6d6ea005457525ea1b1a329f189 to your computer and use it in GitHub Desktop.
Save bindreams/b977a6d6ea005457525ea1b1a329f189 to your computer and use it in GitHub Desktop.
Git hook: ensure that every commit message contains a ticket reference
#!/usr/bin/env python3
"""Ensure that every commit starts with a ticket ref.
A ticket ref regex is defined in `RE_TICKET_REF`, and by default matches tickets like "ABC-123456". The ticket must be
the first text in the commit message, not counting punctuation like brackets or parenthesis. When a ticket ref is
missing, this hook tries to guess one from the branch name, or fails the commit.
You can bypass this commit hook using `git commit --no-verify`.
"""
import sys
import re
import subprocess as sp
RE_TICKET_REF = r"[A-Z]+-\d+" # Such as QD-123456
RE_TAGGED_MESSAGE = rf"^\W*({RE_TICKET_REF})" # Non-letters (square bracket, etc.) followed by the ticket
def normalize_message(message: str):
"""Convert the contents of a commit message file, with comments and empty lines, into a commit message."""
result_lines = []
first_nonempty = None
last_nonempty = None
for line in message.splitlines():
if line.startswith("#"):
continue # Skip comments
if not (line == "" or line.isspace()):
if first_nonempty is None:
first_nonempty = len(result_lines)
last_nonempty = len(result_lines)
result_lines.append(line)
# Strip empty lines bookending the commit message, if the commit is not empty
if first_nonempty is not None:
assert last_nonempty is not None
result_lines = result_lines[first_nonempty : last_nonempty + 1]
return "\n".join(result_lines)
def guess_ticket_from_branch():
branch_name = sp.run(["git", "branch", "--show-current"], check=True, text=True, stdout=sp.PIPE).stdout.strip()
m = re.search(RE_TICKET_REF, branch_name)
if m:
return m[0]
return None
def main():
with open(sys.argv[1], "r", encoding="utf-8") as fd:
text = fd.read()
message = normalize_message(text)
if re.search(RE_TAGGED_MESSAGE, message):
return 0 # Ticket present in message already
ticket = guess_ticket_from_branch()
if ticket is None:
print("Error: missing ticket reference at the start of the message.", file=sys.stderr)
return 1
message = f"{ticket}: {message}"
assert re.search(RE_TAGGED_MESSAGE, message)
with open(sys.argv[1], "w", encoding="utf-8", newline="\n") as fd:
fd.write(f"{message}\n")
print(f'Warning: added ticket ref "{ticket}" to the commit message.', file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment