Why: PR #682 merged a stale branch that reset five component pins to pre-batch values, silently dropping the batch-1 composition on main. Evidence: materialized byte-exact from the reviewed transport (gatemat-run.json); regression/forward/same/skip/missing-object cases (verify-pingate.json); make test-governance and test-pins re-run below. Impact: new check script wired into check-pins and task-pr-check; PIN_REGRESSION_SKIP=1 documents deliberate downgrades. Refs: feature/audit-followups-pin-gate-m5
101 lines
3.6 KiB
Bash
Executable File
101 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 组合指针不倒退:HEAD 中每个 gitlink(deps/*、modules/*)相对 origin/main
|
||
# 只能前进——新 pin 必须以 origin/main 的旧 pin 为祖先。事故原型:PR #682
|
||
# 合入了过期分支,其树把五个组件 pin 重置回批次前的值,静默丢掉了 main 上
|
||
# batch-1 的组合。
|
||
#
|
||
# ┌─ 逃生口(须 owner Review)──────────────────────────────────────────────┐
|
||
# │ PIN_REGRESSION_SKIP=1 声明这是一次有意的降级:失败改为醒目的 warn 并 │
|
||
# │ 退出 0。只用于经组件 owner 审阅的刻意回退,PR │
|
||
# │ 描述须写明原因;不得用来绕过过期分支。 │
|
||
# └─────────────────────────────────────────────────────────────────────────┘
|
||
#
|
||
# 其他开关:
|
||
# PIN_REGRESSION_NO_FETCH=1 旧 pin 在子仓本地缺失时不尝试 fetch(测试/离线用)。
|
||
#
|
||
# 在当前工作目录所属仓库的顶层运行。origin/main 本地不可解析(浅检出)时只告警、退出 0。
|
||
set -euo pipefail
|
||
root=$(git rev-parse --show-toplevel)
|
||
cd "$root"
|
||
|
||
base=origin/main
|
||
skip=${PIN_REGRESSION_SKIP:-0}
|
||
|
||
if ! git rev-parse --verify -q "$base^{commit}" >/dev/null; then
|
||
echo "warn: $base is not resolvable locally; pin regression check skipped" >&2
|
||
exit 0
|
||
fi
|
||
|
||
fail=0
|
||
|
||
declare -A main_pins=()
|
||
while read -r mode _typ sha path; do
|
||
[ "$mode" = "160000" ] || continue
|
||
main_pins["$path"]=$sha
|
||
done < <(git ls-tree -r "$base")
|
||
|
||
# 子仓须是独立检出的仓库;未初始化时 git -C 会落到 Dev 自身。
|
||
own_repo() {
|
||
local path="$1" top
|
||
top=$(git -C "$path" rev-parse --show-toplevel 2>/dev/null) || return 1
|
||
[ "$top" = "$root/$path" ]
|
||
}
|
||
|
||
has_commit() {
|
||
git -C "$1" cat-file -e "$2^{commit}" 2>/dev/null
|
||
}
|
||
|
||
ensure_commit() {
|
||
local path="$1" sha="$2"
|
||
has_commit "$path" "$sha" && return 0
|
||
[ "${PIN_REGRESSION_NO_FETCH:-0}" = "1" ] && return 1
|
||
git -C "$path" fetch -q origin "$sha" >/dev/null 2>&1 || true
|
||
has_commit "$path" "$sha"
|
||
}
|
||
|
||
regress() {
|
||
if [ "$skip" = "1" ]; then
|
||
echo "warn: PIN_REGRESSION_SKIP=1 — $* (deliberate downgrade; requires owner review)" >&2
|
||
else
|
||
echo "pin: $*" >&2
|
||
fail=1
|
||
fi
|
||
}
|
||
|
||
while read -r mode _typ new path; do
|
||
[ "$mode" = "160000" ] || continue
|
||
old=${main_pins["$path"]:-}
|
||
if [ -z "$old" ]; then
|
||
echo "ok $path (new, not on $base)"
|
||
continue
|
||
fi
|
||
if [ "$old" = "$new" ]; then
|
||
echo "ok $path"
|
||
continue
|
||
fi
|
||
if ! own_repo "$path"; then
|
||
regress "$path is not checked out; cannot verify ${new:0:12} descends from $base ${old:0:12}"
|
||
continue
|
||
fi
|
||
if ! ensure_commit "$path" "$old"; then
|
||
regress "$path $base pin $old is missing locally and could not be fetched; cannot verify no regression"
|
||
continue
|
||
fi
|
||
if ! ensure_commit "$path" "$new"; then
|
||
regress "$path HEAD pin $new is missing locally and could not be fetched; cannot verify no regression"
|
||
continue
|
||
fi
|
||
if git -C "$path" merge-base --is-ancestor "$old" "$new"; then
|
||
echo "ok $path (forward ${old:0:12}..${new:0:12})"
|
||
continue
|
||
fi
|
||
regress "$path moves BACKWARD: $base is ${old:0:12}, HEAD is ${new:0:12} (not a descendant)." \
|
||
"This is the PR #682 pattern (a stale branch resetting pins over main): merge $base and re-pin," \
|
||
"or set PIN_REGRESSION_SKIP=1 for an owner-reviewed deliberate downgrade"
|
||
done < <(git ls-tree -r HEAD)
|
||
|
||
if [ "$fail" -ne 0 ]; then
|
||
exit 1
|
||
fi
|
||
echo "pin regression ok"
|