#!/usr/bin/env bash
# H13 (ersatztv#416 session) — refuse to push when a file in the pushed diff still has UNCOMMITTED
# changes in the working tree or index. That is the "I left part of my intended change behind"
# failure: a fix edited into the working file but never committed (e.g. after a `git reset --soft`
# that re-staged a stale index) gets pushed WITHOUT the fix — while local tests and a working-tree
# review both see the fix that never shipped. This bit the #416 session: a `--no-renames` review fix
# lived only in the working tree, so the pushed commit, CI, and the first re-review each saw a
# different tree, and a PR went out still carrying the bug the review had "confirmed" fixed.
#
# Scope is deliberately PRECISE to keep false positives near zero: it blocks only when a dirty
# tracked file is ALSO part of this branch's diff vs origin/main. Unrelated uncommitted scratch in a
# file the push doesn't touch is fine; untracked files are ignored.
#
# Fail-OPEN on anything we can't decide (a git pre-push hook has no "ask"): not a git repo, offline /
# no origin/main, HEAD unresolved -> allow. Deliberate escape: ETV_ALLOW_DIRTY_PUSH=1.
set -uo pipefail

[ "${ETV_ALLOW_DIRTY_PUSH:-}" = "1" ] && exit 0
git rev-parse --git-dir >/dev/null 2>&1 || exit 0

# Files with uncommitted changes vs HEAD — unstaged AND staged-but-uncommitted, tracked only.
dirty="$( { git diff --name-only; git diff --cached --name-only; } 2>/dev/null | sort -u )"
[ -z "$dirty" ] && exit 0   # clean tree -> nothing to guard

# The set of files this branch introduces vs origin/main (the "pushed diff"). Best-effort fetch;
# if origin/main is unavailable we cannot scope precisely -> fail open rather than over-block.
git fetch origin main --quiet 2>/dev/null || exit 0
git rev-parse --verify --quiet origin/main >/dev/null 2>&1 || exit 0
pushed="$( git diff --name-only "origin/main...HEAD" 2>/dev/null | sort -u )"
[ -z "$pushed" ] && exit 0

# Intersection: dirty files that are part of the pushed diff.
both="$( comm -12 <(printf '%s\n' "$dirty") <(printf '%s\n' "$pushed") )"
[ -z "$both" ] && exit 0

branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)
echo "husky - push blocked (H13): '$branch' has UNCOMMITTED changes to file(s) that are part of"
echo "  what you're pushing — the pushed commit does NOT match your working tree, so a local fix"
echo "  or review may be shipping without its change (the #416 index/worktree trap):"
printf '%s\n' "$both" | sed 's/^/    /'
echo "  Commit them (or 'git checkout --' to discard), then push. If the difference is intentional"
echo "  and unrelated, bypass with: ETV_ALLOW_DIRTY_PUSH=1 git push"
exit 1
