template/.githooks/pre-push

58 lines
1.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# pre-push hook: quality gate for the *pushed* commits — a poor man's CI.
# Install with: make setup (sets core.hooksPath = .githooks)
#
# Why a throwaway worktree: a gate must judge what you publish, not the state of
# your desk. Reading the live working tree lets uncommitted WIP either break the
# gate or sneak past it. So we check out the exact commit being pushed into a
# clean, isolated worktree and run the checks there.
set -euo pipefail
REPO_ROOT=$(git rev-parse --show-toplevel)
# shellcheck source=../tools.versions
source "${REPO_ROOT}/tools.versions"
run_checks() { # cwd is a clean checkout of the pushed commit
echo " → gofmt"
local unformatted
unformatted=$(gofmt -l $(git ls-files '*.go'))
if [ -n "$unformatted" ]; then
echo " FAIL: the following files are not gofmt-formatted:"
echo "$unformatted" | sed 's/^/ /'
echo " Fix with: make lint-fix"
return 1
fi
echo " → go vet"
go vet ./...
echo " → golangci-lint"
go run github.com/golangci/golangci-lint/cmd/golangci-lint@"${GOLANGCI_LINT_VERSION}" run ./...
echo " → gosec"
go run github.com/securego/gosec/v2/cmd/gosec@"${GOSEC_VERSION}" -quiet ./...
# govulncheck is intentionally omitted (network + slow).
# Run it manually with: make security
}
# pre-push feeds one "<local_ref> <local_sha> <remote_ref> <remote_sha>" line
# per pushed ref on stdin.
while read -r _local_ref local_sha _remote_ref _remote_sha; do
# Branch deletion (all-zero sha): nothing to check.
[[ "$local_sha" =~ ^0+$ ]] && continue
echo "pre-push: checking ${local_sha:0:12} in a clean worktree..."
tmp=$(mktemp -d)
git worktree add --detach --quiet "$tmp" "$local_sha"
if ( cd "$tmp" && run_checks ); then
git worktree remove --force "$tmp"
else
git worktree remove --force "$tmp"
echo "pre-push: checks FAILED for ${local_sha:0:12}"
exit 1
fi
done
echo "pre-push: all checks passed."