diff --git a/.cursor/rules/gdscript-style.md b/.cursor/rules/gdscript-style.md index 9106fb0..69eff90 100644 --- a/.cursor/rules/gdscript-style.md +++ b/.cursor/rules/gdscript-style.md @@ -59,7 +59,7 @@ CI also enforces **`gdlint`** and **`gdformat`** (see `gdlintrc` and `.github/wo `gdformat --check client/scripts client/test` - If formatting fails, run: `gdformat client/scripts client/test` -- Install the repo’s local **pre-push** hook from the repo root to enforce those checks automatically: +- Install the repo’s local **pre-push** hook from the repo root to enforce those checks when **`client/scripts/`** or **`client/test/`** `.gd` files change in the push: `./scripts/install-git-hooks.sh` ## GdUnit test layout (AAA) diff --git a/client/README.md b/client/README.md index 4270d56..2d2f73e 100644 --- a/client/README.md +++ b/client/README.md @@ -353,9 +353,9 @@ pwsh -File scripts/install-git-hooks.ps1 This installs: - **pre-commit**: blocks commits when `client/scripts/*.gd` changes lack matching `client/test/*_test.gd` updates, or when server route/contract files change without corresponding `server/NeonSprawl.Server.Tests/` and `bruno/neon-sprawl-server/**/*.bru` updates. -- **pre-push**: runs **`gdlint client/scripts client/test`** and **`gdformat --check client/scripts client/test`**. +- **pre-push**: when **`client/scripts/*.gd`** or **`client/test/*.gd`** changed in the commits being pushed, runs **`gdlint client/scripts client/test`** and **`gdformat --check client/scripts client/test`** (same scope as CI). Otherwise skips lint/format. Still requires a **clean working tree** on every push. -The pre-push hook prefers **`.venv-gd/bin/`** (Unix) or **`.venv-gd/Scripts/`** (Windows venv) when present; without that venv or a global install, the hook **fails** and blocks the push — same as CI. +The pre-push hook prefers **`.venv-gd/bin/`** (Unix) or **`.venv-gd/Scripts/`** (Windows venv) when present; without that venv or a global install, the hook **fails** when GDScript changed — same as CI. Pushes with no client `.gd` changes do not require gdtoolkit installed. **If CI fails gdlint but your push succeeded:** the hook was not installed, **`gdlint`/`gdformat` were missing** when the hook ran, or push bypassed hooks (`--no-verify`). diff --git a/scripts/git-hooks/pre-push b/scripts/git-hooks/pre-push index 7ff7e00..281e035 100755 --- a/scripts/git-hooks/pre-push +++ b/scripts/git-hooks/pre-push @@ -11,6 +11,70 @@ if [[ -n "$(git status --porcelain)" ]]; then 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="" @@ -38,6 +102,9 @@ if [[ -z "$gdlint_bin" || -z "$gdformat_bin" ]]; then 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