#!/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 local range="" if [[ "$remote_sha" == "$zero_sha" ]]; then local 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 local head_sha head_sha="$(git rev-parse HEAD)" local 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