#!/usr/bin/env bash
#
# pre-push：**推送前**拦住"不该进版本库的文件"。
#
# ★ 为什么要有这个钩子，而不是只靠 `criteria-hygiene` 里那条判据：
#   那条判据读的是 **index**（`git ls-files`），它守的是"**不会被再加回来**"，
#   不是"**不会被推出去**"。而 blob 恰恰躺在**未推送的提交**里 ——
#   也就是说 `git rm --cached` 之后，东西已经从 index 里消失了，
#   可是**历史里还有**，下一次 `git push` 会连它一起发出去，而**没有任何东西会红**。
#   ⇒ "先 ignore + `rm --cached`、再 push"这条顺序**只活在提交信息里**，
#     而"只活在文字里的规则等于没有规则"正是我们加那条判据的理由。
#     判据是**泄露之后**响的闹钟；这个钩子是**不让它泄露**。两件事都要有。
#
# 这个钩子由 `deploy/install.sh --git-hooks` 装（`core.hooksPath` 指向 `.githooks`），
# 所以它**跟着仓库走**：换一台机器 clone 下来，装一次就都装上了。
#
# 退出码：0 放行，1 拦下（git 会中止 push）。**绝不返回 2** ——
# 钩子里非 0 一律中止推送，所以"钩子自己坏了"与"真的有违规"都会拦下来，
# 这是**故意的方向**：宁可推不出去，也不要静默推出去。
set -uo pipefail

# 不许进版本库的路径（**加新路径就加在这里**，别写进判据里到处复制）
FORBIDDEN=(
  "client/harmony/entry/src/main/resources/rawfile/agconnect-services.json"
)

# 生效范围：推送的所有 ref 区间。钩子从 stdin 收到：
#   <local ref> <local sha> <remote ref> <remote sha>
# 远端 sha 全 0 = 新分支 ⇒ 要查**整个可达历史**。
zero="0000000000000000000000000000000000000000"
declare -a RANGES=()
while read -r _lref lsha _rref rsha; do
  # ★ **删除**用**全零** sha 表示，不是空串（原来是 `[ -z "${lsha:-}" ] && continue`）。
  #   少了这一支的后果：删除时 lsha=全零、rsha=旧值 ⇒ 走 else ⇒ 拼出
  #   `旧sha..0000…` ⇒ `git log` 解析不了 ⇒ **exit 1 中止**。
  #   实测（真 bare 仓）：
  #     $ git push /tmp/bare-del.git --delete tmp
  #     pre-push: 无法解析范围 '6702cc2f…..0000000000000000000000000000000000000000' —— 中止推送
  #
  #   为什么这不是小事：**删除不发布任何东西**，所以这是**纯误红**；
  #   而它恰恰因为"宁可推不出去"这条**正确的**方向而不可见 —— 看起来就像设计好的安全行为。
  #   同族形状：**一道闸消费了不属于它管辖的东西**（"到期闸把汇总一起吃掉"也是这个）。
  #   附带效果：`install.sh --check` 会一直说"钩子已接"，而实际上删分支/删 tag 全被挡。
  #
  #   ★ 我第一步只验了"会泄露的范围 ⇒ 1、已推过的范围 ⇒ 0"——
  #     **两个方向都没覆盖"没有东西被发布"这一支**，而它正是坏的那一支。
  #     和"只判下界"是同一条：**把边界少判了一侧**。
  if [ -z "${lsha:-}" ] || [ "$lsha" = "$zero" ]; then
    continue        # 没有东西被发布（删除 / 空 ref）—— 放行
  fi
  if [ "$rsha" = "$zero" ]; then
    # 新分支/新 tag：从根开始查（`--not --remotes` 会漏掉已被别的远端包含的情况）
    RANGES+=("$lsha")
  else
    RANGES+=("${rsha}..${lsha}")
  fi
done

if [ "${#RANGES[@]}" -eq 0 ]; then
  exit 0   # 没有任何 ref 要推（例如只删远端分支）—— 放行
fi

problems=0
for path in "${FORBIDDEN[@]}"; do
  for range in "${RANGES[@]}"; do
    # `--all` 覆盖"这次推送带出去的所有提交里，这个路径是否被改动过"
    if ! hits="$(git log --format='  %h %s' "$range" -- "$path" 2>/dev/null)"; then
      echo "pre-push: 无法解析范围 '$range' —— 中止推送（宁可推不出去，也不要盲推）" >&2
      exit 1
    fi
    if [ -n "$hits" ]; then
      problems=1
      {
        echo ""
        echo "✗ 这次推送会把 **$path** 的历史一起发出去。"
        echo "  它是 AGC 客户端凭证，而本仓的镜像是**匿名可 clone 的公开项目**。"
        echo "  涉及这些提交："
        echo "$hits"
        echo ""
        echo "  怎么处置（按顺序）："
        echo "    1) 如果这些提交**已经**推上去过：按\"已泄露\"处理 —— 去 AGC 轮换，"
        echo "       并且**先**在本地把这些提交改掉（rebase/filter-repo），再推。"
        echo "    2) 如果还没推过：把涉及它的提交从待推范围里摘掉，或者先把历史里的 blob 清掉。"
        echo "    3) 确认它**不在**要推的范围里之后，再 push。"
        echo ""
        echo "  （这条钩子的存在理由：判据读的是 index，只证明\"不会再被加回来\"；"
        echo "    而 blob 还躺在未推送的提交里，只有推送前拦得住。）"
      } >&2
    fi
  done
done

exit "$problems"
