#!/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 " " 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."