#!/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"