neon-sprawl/scripts/git-hooks/pre-push

112 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
echo "pre-push: checking for a clean working tree"
if [[ -n "$(git status --porcelain)" ]]; then
echo "pre-push: refuse to push — working tree is not clean. Commit, stash, or remove changes first:" >&2
git status --short >&2
exit 1
fi
zero_sha="0000000000000000000000000000000000000000"
gd_changed_files=()
collect_gd_changes_in_range() {
local range="$1"
while IFS= read -r -d '' path; do
gd_changed_files+=("$path")
done < <(
git diff --name-only -z --diff-filter=ACMR "$range" -- \
'client/scripts/*.gd' 'client/test/*.gd' 2>/dev/null || true
)
}
read_any_ref=false
while read -r local_ref local_sha remote_ref remote_sha; do
read_any_ref=true
if [[ "$local_sha" == "$zero_sha" ]]; then
continue
fi
range=""
if [[ "$remote_sha" == "$zero_sha" ]]; then
base=""
if git show-ref --verify --quiet refs/remotes/origin/main; then
base="$(git merge-base origin/main "$local_sha" 2>/dev/null || true)"
fi
if [[ -z "$base" ]] && git show-ref --verify --quiet refs/heads/main; then
base="$(git merge-base main "$local_sha" 2>/dev/null || true)"
fi
if [[ -n "$base" ]]; then
range="${base}..${local_sha}"
else
range="$local_sha"
fi
else
range="${remote_sha}..${local_sha}"
fi
collect_gd_changes_in_range "$range"
done
# Fallback when stdin is empty (non-standard invocations).
if [[ "$read_any_ref" == false ]]; then
head_sha="$(git rev-parse HEAD)"
base=""
if git show-ref --verify --quiet refs/remotes/origin/main; then
base="$(git merge-base origin/main "$head_sha" 2>/dev/null || true)"
fi
if [[ -z "$base" ]] && git show-ref --verify --quiet refs/heads/main; then
base="$(git merge-base main "$head_sha" 2>/dev/null || true)"
fi
if [[ -n "$base" ]]; then
collect_gd_changes_in_range "${base}..${head_sha}"
else
collect_gd_changes_in_range "$head_sha"
fi
fi
if [[ ${#gd_changed_files[@]} -eq 0 ]]; then
echo "pre-push: no client/scripts or client/test .gd changes in push — skipping gdlint/gdformat"
exit 0
fi
gdlint_bin=""
gdformat_bin=""
if [[ -x "$repo_root/.venv-gd/bin/gdlint" ]]; then
gdlint_bin="$repo_root/.venv-gd/bin/gdlint"
elif [[ -x "$repo_root/.venv-gd/Scripts/gdlint.exe" ]]; then
gdlint_bin="$repo_root/.venv-gd/Scripts/gdlint.exe"
elif command -v gdlint >/dev/null 2>&1; then
gdlint_bin="$(command -v gdlint)"
fi
if [[ -x "$repo_root/.venv-gd/bin/gdformat" ]]; then
gdformat_bin="$repo_root/.venv-gd/bin/gdformat"
elif [[ -x "$repo_root/.venv-gd/Scripts/gdformat.exe" ]]; then
gdformat_bin="$repo_root/.venv-gd/Scripts/gdformat.exe"
elif command -v gdformat >/dev/null 2>&1; then
gdformat_bin="$(command -v gdformat)"
fi
if [[ -z "$gdlint_bin" || -z "$gdformat_bin" ]]; then
echo "pre-push: missing gdtoolkit commands (gdlint/gdformat)." >&2
echo "Install locally with:" >&2
echo " python3 -m venv .venv-gd" >&2
echo " .venv-gd/bin/pip install \"gdtoolkit==4.5.0\"" >&2
exit 1
fi
echo "pre-push: client .gd changes detected — running gdlint and gdformat --check"
printf ' %s\n' "${gd_changed_files[@]}"
echo "pre-push: running gdlint on client/scripts and client/test"
"$gdlint_bin" client/scripts client/test
echo "pre-push: running gdformat --check on client/scripts and client/test"
"$gdformat_bin" --check client/scripts client/test