From f7a7b0db263832dcef9b647b39e0897d5efe3c28 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Fri, 31 Jul 2026 10:40:41 +0300 Subject: [PATCH] .tangled: add Go-style commit-message lint (hook + CI) Add a shared linter (.tangled/hooks/commit-msg-lint.sh) enforcing the Go / Tailscale commit convention: a real pkg/path prefix, imperative lowercase summary, no trailing period, blank line before the body. It rejects Conventional Commit prefixes (fix:, feat:) and invented roots like workflows/ (the real path is .tangled/workflows). Wire it up two ways: a commit-lint CI workflow that lints the PR commit range (or the pushed tip), and a git commit-msg hook installable via .tangled/hooks/install.sh. jj users rely on CI or run the linter by hand. Document the convention and setup in CONTRIBUTING.md. --- .tangled/hooks/commit-msg | 14 ++ .tangled/hooks/commit-msg-lint.sh | 262 +++++++++++++++++++++++++++++ .tangled/hooks/install.sh | 29 ++++ .tangled/workflows/commit-lint.yml | 40 +++++ CONTRIBUTING.md | 72 ++++++++ 5 files changed, 417 insertions(+) create mode 100755 .tangled/hooks/commit-msg create mode 100755 .tangled/hooks/commit-msg-lint.sh create mode 100755 .tangled/hooks/install.sh create mode 100644 .tangled/workflows/commit-lint.yml create mode 100644 CONTRIBUTING.md diff --git a/.tangled/hooks/commit-msg b/.tangled/hooks/commit-msg new file mode 100755 index 00000000..f7968fec --- /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 100755 index 00000000..9a573823 --- /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