diff --git a/lib/deploy-commit.sh b/lib/deploy-commit.sh index e6b40e2..b1922df 100644 --- a/lib/deploy-commit.sh +++ b/lib/deploy-commit.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # deploy-commit.sh — surgical commit for the .claude/deploy/ runbook family. # Allowlist scope = .claude/deploy/ ONLY (inverse of doc-commit's .claude exclusion). -set -u +set -uo pipefail -_in_git_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1; } +_in_git_repo() { git rev-parse --git-dir >/dev/null 2>&1; } _unsafe_state() { # 0 = unsafe local g; g=$(git rev-parse --git-dir 2>/dev/null) || return 0 @@ -34,6 +34,13 @@ _in_git_repo || { echo "deploy-commit: not a git repo" >&2; exit 2; } case "$cmd" in pending) [ "$#" -gt 0 ] || { echo "deploy-commit: pending needs file args" >&2; exit 2; } + mapfile -t violations < <(_scope_violations "$@") + if [ "${#violations[@]}" -gt 0 ]; then + { echo "deploy-commit: REFUSED — path(s) outside .claude/deploy/ allowlist:"; + printf ' - %s\n' "${violations[@]}"; + echo "deploy-commit: NOTHING committed. Caller must pass only .claude/deploy/ files."; } >&2 + exit 4 + fi [ -n "$(_changed_only "$@")" ] && exit 0 || exit 1 ;; commit) msg="${1:-}"; shift || true @@ -49,6 +56,9 @@ case "$cmd" in mapfile -t changed < <(_changed_only "$@") [ "${#changed[@]}" -gt 0 ] || exit 1 git add -- "${changed[@]}" + if git diff --cached --quiet -- "${changed[@]}"; then + echo "deploy-commit: nothing staged — no-op" >&2; exit 1 + fi git commit -q -m "$msg" -- "${changed[@]}" || { echo "deploy-commit: git commit failed" >&2; exit 1; } git rev-parse --short HEAD ;; *) echo "usage: deploy-commit.sh pending ... | commit \"\" ..." >&2; exit 2 ;; diff --git a/lib/tests/deploy-commit.test.sh b/lib/tests/deploy-commit.test.sh index 3806943..915df03 100644 --- a/lib/tests/deploy-commit.test.sh +++ b/lib/tests/deploy-commit.test.sh @@ -42,4 +42,7 @@ printf 'i\n' >"$d/.claude/deploy/INCIDENTS.md"; printf '{}\n' >"$d/.claude/deplo check T7-atomic-rc "$?" 0 check T7-three-files "$(git -C "$d" show --name-only --format= HEAD | grep -c deploy)" 3 +d=$(mkrepo); printf 'b\n' >"$d/src.txt" +( cd "$d" && bash "$H" pending src.txt ) >/dev/null 2>&1; check T8-pending-out-of-scope-rc "$?" 4 + printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]