#!/usr/bin/env bash # shadow-rs pre-push hook. # Install: ./hooks/install.sh # # Runs the workspace test suite once, on Debian, before a push that carries new # commits. # # It deliberately does *not* reproduce CI. CI runs fmt, clippy and the tests # with and without `pam`, the MSRV build, cargo-deny, the static musl archive, # the Debian/Alpine/Fedora matrix as root and the end-to-end deployment image, # in parallel and with retries. A hook that tried to match that took minutes on # every push, so every push used --no-verify -- and a hook that is always # skipped protects nothing while still costing time whenever someone forgets to # skip it. # # What is left is the one check worth having before the code leaves the # machine: the tests actually run. `make test` is the single definition of # what that means -- with and without `pam`, since the feature changes which # code paths exist. set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' ZERO='0000000000000000000000000000000000000000' # Does this push carry any commit that is not already on the remote? # # git feeds the hook one line per ref: # . Pushing a tag, deleting a branch, or re-pushing a branch the # remote already has moves no commits, and running a test suite for it is pure # waste -- pushing the 0.2.2 tag moved one object and blocked for two minutes. carries_commits() { local local_ref local_sha remote_ref remote_sha local found=1 while read -r local_ref local_sha remote_ref remote_sha; do [ -z "${local_ref:-}" ] && continue # A tag names commits that are already being pushed on their branch. case "$local_ref" in refs/tags/*) continue ;; esac # Deleting a ref pushes nothing. [ "$local_sha" = "$ZERO" ] && continue local range=() if [ "$remote_sha" = "$ZERO" ]; then # A new branch: everything on it that no other remote branch has. range=("$local_sha" --not --remotes=origin) else range=("$remote_sha..$local_sha") fi if [ -n "$(git rev-list -n 1 "${range[@]}" 2>/dev/null)" ]; then found=0 fi done return "$found" } # No stdin means the hook was invoked by hand; take the cautious path and run. if [ -t 0 ]; then : elif ! carries_commits; then echo "pre-push: no new commits in this push, skipping the test suite" exit 0 fi echo -e "${YELLOW}pre-push${NC}: make test (debian)" if ! docker compose run --rm -T debian make test 2>&1; then echo -e "${RED}FAILED${NC}: make test" echo "The full matrix -- fmt, clippy, ±pam, MSRV, musl, three distros," echo "end-to-end -- runs in CI on the pull request." exit 1 fi echo -e "${GREEN}pre-push: tests passed${NC}"