ci(githooks): enforce format, lint, and conventional commits

Run SwiftFormat and SwiftLint on staged Swift files in the pre-commit
hook (formatting-and-linting.md), gracefully skipping when a tool is
absent since CI re-runs both. Enforce the Conventional Commits subject
format and the 72-character subject limit in the commit-msg hook
(commits.md). Correct the pre-push comment that named the removed ci.yml.
This commit is contained in:
Mihaela M 2026-06-26 06:14:24 +02:00
parent c4f7f9dccc
commit 7270359d14
3 changed files with 49 additions and 1 deletions

View File

@ -123,4 +123,26 @@ for ident in "$(git var GIT_AUTHOR_IDENT 2>/dev/null)" "$(git var GIT_COMMITTER_
fi
done
# commits.md Rule 1/2: subject must follow Conventional Commits (lowercase type
# from the allowed set, optional scope, optional ! for breaking) and be at most
# 72 characters. Merge/revert/fixup/squash subjects are tooling-generated and
# exempt.
SUBJECT=$(printf '%s\n' "$CONTENT" | sed '/^[[:space:]]*$/d' | head -1)
case "$SUBJECT" in
"Merge "*|"Revert "*|"fixup! "*|"squash! "*|"") ;;
*)
if [ "${#SUBJECT}" -gt 72 ]; then
printf 'commit-msg: subject is %s chars (max 72).\n' "${#SUBJECT}" >&2
printf ' Rule: commits.md Rule 1\n' >&2
FAIL=1
fi
if ! printf '%s' "$SUBJECT" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\([a-z0-9._-]+\))?!?: .+'; then
printf 'commit-msg: subject must be Conventional Commits: "type(scope): summary".\n' >&2
printf ' Allowed types: feat fix docs style refactor perf test build ci chore\n' >&2
printf ' Rule: commits.md Rule 1, Rule 2\n' >&2
FAIL=1
fi
;;
esac
exit $FAIL

View File

@ -224,6 +224,32 @@ while IFS= read -r file; do
done <<<"$EMOJI_TELLS"
done <<<"$SCAN_FILES"
# SwiftFormat + SwiftLint on staged Swift files (formatting-and-linting.md
# acceptance item 2). Run only when the tool is installed; CI re-runs both as a
# required gate (gates.yml), so a machine lacking the tools cannot land
# unformatted or lint-dirty code regardless.
STAGED_SWIFT=$(printf '%s\n' "$STAGED_FILES" | grep -E '\.swift$' || true)
if [ -n "$STAGED_SWIFT" ]; then
if command -v swiftformat >/dev/null 2>&1; then
if ! printf '%s\n' "$STAGED_SWIFT" | xargs swiftformat --lint --config .swiftformat >/dev/null 2>&1; then
printf 'pre-commit: SwiftFormat found unformatted staged Swift.\n' >&2
printf ' Fix: swiftformat . --config .swiftformat\n' >&2
FAIL=1
fi
else
printf 'pre-commit: swiftformat not installed; format gate skipped (CI enforces).\n' >&2
fi
if command -v swiftlint >/dev/null 2>&1; then
if ! printf '%s\n' "$STAGED_SWIFT" | xargs swiftlint lint --strict --quiet --config .swiftlint.yml >/dev/null 2>&1; then
printf 'pre-commit: SwiftLint violations in staged Swift.\n' >&2
printf ' Fix: swiftlint --config .swiftlint.yml --strict\n' >&2
FAIL=1
fi
else
printf 'pre-commit: swiftlint not installed; lint gate skipped (CI enforces).\n' >&2
fi
fi
if [ "$FAIL" -ne 0 ]; then
printf '\n' >&2
printf 'Hook scope: only ADDED lines in the staged diff are gated.\n' >&2

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# pre-push hook: run the mechanical gates locally before a push reaches GitHub.
# These mirror the CI workflow (.github/workflows/ci.yml) so red is caught here
# These mirror the CI workflows (.github/workflows/) so red is caught here
# first. PureYAML is a root Swift package, so Swift gates run at repo root.
#
# Install (per repo): git config core.hooksPath .githooks