From 7c7539ada905e8ff84e3a69eec762e56c3ffa728 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Fri, 31 Jul 2026 07:40:41 +0000 Subject: [PATCH] .tangled: add commit message lint (git hook & ci) Signed-off-by: Anirudh Oppiliappan --- .tangled/hooks/commit-msg | 14 ++++++++++++++ .tangled/hooks/commit-msg-lint.sh | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .tangled/hooks/install.sh | 29 +++++++++++++++++++++++++++++ .tangled/workflows/commit-lint.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 4 file(s) changed, 345 insertion(s)(+), 0 deletion(s)(-) diff --git a/.tangled/hooks/commit-msg b/.tangled/hooks/commit-msg new file mode 100644 --- /dev/null +++ b/.tangled/hooks/commit-msg @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# +# git commit-msg hook — lint the message against the Go commit conventions. +# +# Installed by pointing core.hooksPath at this directory: +# ./.tangled/hooks/install.sh +# +# Bypass for a single commit with `git commit --no-verify`. +# +# Note: jj (jujutsu) does not run git hooks. jj users are covered by the +# `commit-lint` CI workflow instead, and can lint locally on demand with: +# .tangled/hooks/commit-msg-lint.sh --rev @ + +exec "$(dirname "$0")/commit-msg-lint.sh" --file "$1" diff --git a/.tangled/hooks/commit-msg-lint.sh b/.tangled/hooks/commit-msg-lint.sh new file mode 100644 --- /dev/null +++ b/.tangled/hooks/commit-msg-lint.sh @@ -0,0 +1,262 @@ +#!/usr/bin/env bash +# +# commit-msg-lint.sh — enforce Go-style commit messages. +# +# Reference: https://go.dev/wiki/CommitMessage +# +# pkg/path: short summary in the imperative mood +# +# Optional body explaining what changed and why, wrapped at ~76 columns. +# The blank line between the summary and the body is required. +# +# Fixes #123 +# +# Usage: +# commit-msg-lint.sh --file # lint a single message file (git commit-msg hook) +# commit-msg-lint.sh --rev # lint the message of one commit +# commit-msg-lint.sh --range # lint every commit in a..b (exclusive of a) +# ... | commit-msg-lint.sh # lint a message on stdin +# +# Flags: +# --strict treat warnings as errors (exit non-zero on warnings too) +# +# Exit status: 0 = clean, 1 = at least one error (or warning under --strict). + +set -euo pipefail + +# Maximum length of the summary line. Go's guideline is "less than 76". +MAX_SUMMARY_LEN=76 + +# Bare prefixes that look like Conventional Commits rather than a Go package +# path. These are rejected because the summary should name the package/path +# affected, e.g. `ogre: ...` instead of `fix: ...`. +CONVENTIONAL_TYPES="feat fix chore refactor perf style ci revert" + +# Special prefixes that are not repo paths but are conventionally allowed, +# following the Go project (e.g. `all:` for tree-wide changes). +PATH_ALLOWLIST="all" + +# Space-separated list of real top-level entries in the repo (dirs and files), +# used to validate that a prefix names an actual path. Populated per run from +# the tree being linted; empty means "couldn't determine, skip path checks". +TOPLEVEL="" + +# is_toplevel — true if is a real top-level entry or allowlisted. +is_toplevel() { + case " $TOPLEVEL " in *" $1 "*) return 0 ;; esac + case " $PATH_ALLOWLIST " in *" $1 "*) return 0 ;; esac + return 1 +} + +# Non-imperative openers (past tense / gerund) that read as "described the +# change" rather than "make the change". Warning only. +NON_IMPERATIVE="added adds adding fixed fixes fixing updated updates updating \ +removed removes removing changed changes changing implemented implements \ +implementing created creates creating deleted refactored refactoring \ +improved improves improving bumped bumps" + +STRICT=0 +errors=0 +warnings=0 + +err() { printf ' \033[31merror\033[0m %s\n' "$1" >&2; errors=$((errors + 1)); } +warn() { printf ' \033[33mwarn\033[0m %s\n' "$1" >&2; warnings=$((warnings + 1)); } + +# lint_message