| #!/bin/bash | |
| # Copyright 2026 Marc-Antoine Ruel. All rights reserved. | |
| # Use of this source code is governed under the Apache License, Version 2.0 | |
| # that can be found in the LICENSE file. | |
| # Validates the checked-out, clean commit before pushing. | |
| set -euo pipefail | |
| if (($# != 2)); then | |
| printf '%s\n' 'Usage: pre-push <remote-name> <remote-url>' >&2 | |
| exit 2 | |
| fi | |
| script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" | |
| readonly script_dir | |
| repo_root="$(CDPATH='' cd -- "$script_dir/../.." && pwd)" | |
| readonly repo_root | |
| cd -- "$repo_root" | |
| zero=$(git hash-object --stdin </dev/null | tr '0-9a-f' '0') | |
| readonly zero | |
| head_oid=$(git rev-parse --verify HEAD 2>/dev/null || true) | |
| readonly head_oid | |
| has_ref_update=false | |
| while read -r local_ref local_oid remote_ref remote_oid; do | |
| # Deleting a remote ref does not push local content and is always allowed. | |
| if [[ "$local_ref" == '(delete)' || "$local_oid" == "$zero" ]]; then | |
| continue | |
| fi | |
| # Worktree-based checks below only describe the checked-out commit, so | |
| # refuse anything else instead of validating the wrong tree. | |
| if [[ -z "$head_oid" || "$local_oid" != "$head_oid" ]]; then | |
| printf 'Push blocked: %s is not checked out; only HEAD can be pushed.\n' "$local_ref" >&2 | |
| exit 1 | |
| fi | |
| has_ref_update=true | |
| if [[ "$remote_oid" == "$zero" ]]; then | |
| range="$local_oid" | |
| else | |
| range="$remote_oid..$local_oid" | |
| fi | |
| wip_commit=$(git rev-list -n 1 --grep '^WIP' "$range") | |
| if [[ -n "$wip_commit" ]]; then | |
| printf 'Found WIP commit in %s; not pushing.\n' "$local_ref" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$remote_ref" == 'refs/heads/main' ]]; then | |
| commit_count=$(git rev-list --count "$range") | |
| if ((commit_count > 1)); then | |
| printf 'Push blocked: %d commits to main; push one commit at a time.\n' "$commit_count" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| if ! "$has_ref_update"; then | |
| exit 0 | |
| fi | |
| if [[ -n "$(git status --porcelain)" ]]; then | |
| printf '%s\n' 'Commit or remove all changes before pushing.' >&2 | |
| exit 1 | |
| fi | |
| # Repository-specific verification. | |
| make verify | |
| if [[ -n "$(git status --porcelain)" ]]; then | |
| printf '%s\n' 'Verification modified the worktree; commit or remove those changes before pushing.' >&2 | |
| exit 1 | |
| fi |