Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Commitlint

on:
pull_request:
types: [opened, synchronize, reopened]

# Workflow only reads the event payload (SHAs) + local git log. No
# pull-requests API calls, so no pull-requests scope needed.
permissions:
contents: read

jobs:
commitlint:
name: Validate conventional commits
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Comment thread
willgriffin marked this conversation as resolved.
# SHA-pinned per GitHub's supply-chain recommendation. To update,
# find the new SHA: `gh api repos/actions/checkout/git/refs/tags/v5`
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0

- name: Validate commit messages
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# SHAs are hex so safe to interpolate, but use env vars per
# GitHub Actions injection-defense guidance.
COMMITS=$(git log --format=%s "$BASE_SHA".."$HEAD_SHA")

# Escape user-controlled content before printing inside a
# ::error:: workflow command. Without this, a commit subject
# containing `%`, `\r`, or `\n` could spoof log output or
# inject further workflow commands.
escape_workflow_msg() {
local s="$1"
s="${s//\%/%25}"
s="${s//$'\r'/%0D}"
s="${s//$'\n'/%0A}"
printf '%s' "$s"
}
Comment thread
willgriffin marked this conversation as resolved.

FAILED=0
while IFS= read -r msg; do
[[ -z "$msg" ]] && continue
# Allow merge commits. Single space, not escaped — `Merge `
# is the literal prefix of `Merge branch …` and
# `Merge pull request …`.
if [[ "$msg" == Merge\ * ]]; then
continue
fi
# Use printf instead of echo to avoid `-n`/`-e` flag parsing
# on subjects that happen to start with a dash.
if ! printf '%s\n' "$msg" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\([a-z0-9-]+\))?!?: .+'; then
printf '::error::Invalid commit message: %s\n' "$(escape_workflow_msg "$msg")"
printf ' Expected format: type(scope?): subject\n'
printf ' Valid types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert\n'
FAILED=1
fi
done <<< "$COMMITS"

if [[ "$FAILED" -eq 1 ]]; then
printf '\nSee https://www.conventionalcommits.org/ for the full spec.\n'
exit 1
fi

printf '✓ All commits follow Conventional Commits format.\n'