From c4b71aac0d55cffcf9a74bacb4bcb2b1fbb58d10 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sat, 25 Apr 2026 22:38:24 -0400 Subject: [PATCH] NEO-29: enforce test coverage checks in git hooks Add a pre-commit guard that blocks commits missing required client/server test updates and Bruno requests for route/contract changes, and wire hook installers to install both pre-commit and pre-push hooks. --- client/README.md | 9 +++++-- scripts/git-hooks/pre-commit | 45 +++++++++++++++++++++++++++++++++++ scripts/install-git-hooks.ps1 | 17 ++++++++++--- scripts/install-git-hooks.sh | 11 +++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100755 scripts/git-hooks/pre-commit diff --git a/client/README.md b/client/README.md index 25a4298..a67be42 100644 --- a/client/README.md +++ b/client/README.md @@ -192,7 +192,7 @@ On **Linux** (including GitHub Actions), the path must be **`gdUnit4`** with a c **Dev (NEO-18):** **`dev_toggle_occluder_obstacle`** (default **Ctrl+Shift+K**) toggles the prototype `Obstacle` visibility/collision in **`main.gd`**. **F9** / **F10** are used by the embedded debugger; use this action instead when the game is running in the editor. -**Git hook (recommended):** install the repo’s local **pre-push** GDScript lint hook from the repo root (once per clone): +**Git hooks (recommended):** install the repo’s local hooks from the repo root (once per clone): ```bash ./scripts/install-git-hooks.sh @@ -204,7 +204,12 @@ On **Windows** (PowerShell), same hook: pwsh -File scripts/install-git-hooks.ps1 ``` -It runs **`gdlint client/scripts client/test`** and **`gdformat --check client/scripts client/test`** before each push. It 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. +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`**. + +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. **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-commit b/scripts/git-hooks/pre-commit new file mode 100755 index 0000000..ff710bd --- /dev/null +++ b/scripts/git-hooks/pre-commit @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +cd "$repo_root" + +mapfile -t staged_files < <(git diff --cached --name-only --diff-filter=ACMR) +if [[ ${#staged_files[@]} -eq 0 ]]; then + exit 0 +fi + +has_staged_match() { + local pattern="$1" + for f in "${staged_files[@]}"; do + if [[ "$f" =~ $pattern ]]; then + return 0 + fi + done + return 1 +} + +server_route_like_changed=false +if has_staged_match '^server/NeonSprawl\.Server/.*(Api\.cs|Dtos\.cs|Program\.cs)$'; then + server_route_like_changed=true +fi + +if [[ "$server_route_like_changed" == true ]]; then + if ! has_staged_match '^server/NeonSprawl\.Server\.Tests/'; then + echo "pre-commit: server route/contract files changed but no server tests staged." >&2 + echo "Add or update tests under server/NeonSprawl.Server.Tests/ before commit." >&2 + exit 1 + fi + if ! has_staged_match '^bruno/neon-sprawl-server/.*\.bru$'; then + echo "pre-commit: server route/contract files changed but no Bruno request staged." >&2 + echo "Add/update .bru requests under bruno/neon-sprawl-server/ before commit." >&2 + exit 1 + fi +fi + +if has_staged_match '^client/scripts/.*\.gd$'; then + if ! has_staged_match '^client/test/.*_test\.gd$'; then + echo "pre-commit: client/scripts .gd changes require test updates in client/test/*_test.gd." >&2 + exit 1 + fi +fi diff --git a/scripts/install-git-hooks.ps1 b/scripts/install-git-hooks.ps1 index d1d86e6..ff28665 100644 --- a/scripts/install-git-hooks.ps1 +++ b/scripts/install-git-hooks.ps1 @@ -1,19 +1,30 @@ -# Installs repo pre-push hook (gdlint + gdformat). Same behavior as install-git-hooks.sh. +# Installs repo pre-commit and pre-push hooks. Same behavior as install-git-hooks.sh. # Run from repo root: pwsh -File scripts/install-git-hooks.ps1 $ErrorActionPreference = "Stop" $repoRoot = (git rev-parse --show-toplevel).Trim() $hookDir = Join-Path $repoRoot ".git/hooks" +$preCommitHookPath = Join-Path $hookDir "pre-commit" $hookPath = Join-Path $hookDir "pre-push" New-Item -ItemType Directory -Force -Path $hookDir | Out-Null $unixRoot = $repoRoot -replace "\\", "/" -$lines = @( +$preCommitLines = @( + "#!/bin/sh", + "set -e", + "repo_root=`"$unixRoot`"", + 'cd "$repo_root" || exit 1', + 'exec sh "$repo_root/scripts/git-hooks/pre-commit" "$@"' +) +$preCommitContent = ($preCommitLines -join "`n") + "`n" +$pushLines = @( "#!/bin/sh", "set -e", "repo_root=`"$unixRoot`"", 'cd "$repo_root" || exit 1', 'exec sh "$repo_root/scripts/git-hooks/pre-push" "$@"' ) -$content = ($lines -join "`n") + "`n" +$content = ($pushLines -join "`n") + "`n" $utf8 = New-Object System.Text.UTF8Encoding $false +[System.IO.File]::WriteAllText($preCommitHookPath, $preCommitContent, $utf8) [System.IO.File]::WriteAllText($hookPath, $content, $utf8) +Write-Host "Installed pre-commit hook at $preCommitHookPath" Write-Host "Installed pre-push hook at $hookPath" diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh index 6752368..10e4a65 100755 --- a/scripts/install-git-hooks.sh +++ b/scripts/install-git-hooks.sh @@ -3,10 +3,19 @@ set -euo pipefail repo_root="$(git rev-parse --show-toplevel)" hook_dir="$repo_root/.git/hooks" +pre_commit_hook_path="$hook_dir/pre-commit" hook_path="$hook_dir/pre-push" mkdir -p "$hook_dir" +cat >"$pre_commit_hook_path" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +exec "$repo_root/scripts/git-hooks/pre-commit" "$@" +EOF + cat >"$hook_path" <<'EOF' #!/usr/bin/env bash set -euo pipefail @@ -15,5 +24,7 @@ repo_root="$(git rev-parse --show-toplevel)" exec "$repo_root/scripts/git-hooks/pre-push" "$@" EOF +chmod +x "$pre_commit_hook_path" chmod +x "$hook_path" +echo "Installed pre-commit hook at $pre_commit_hook_path" echo "Installed pre-push hook at $hook_path"