Files
姚章浩 bda26730f5 fix(template): 同批次同步 bootstrap 启动、新分支基准与 Python 锁定隔离
模板 workflow 第一步改成最小 bootstrap(空工作区里仓内脚本还不存在);ci_checkout.sh 拆成 --post-checkout(检出后校验与补取:默认分支引用、事件 before)+ --print-remote;ci_gate.py 新分支范围以远端默认分支为基准;ci-gate.py 的 Python 依赖必须按 uv.lock 安装(uv sync --frozen)、所有检查走 uv run --frozen(项目内 .venv),并断言 sys.prefix != sys.base_prefix,缺锁/只有 requirements.txt 明确失败(不再 uv pip install --system)。文档补第三轮修正记录、trap 与自检命令。
2026-10-12 16:45:10 +08:00

123 lines
6.4 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 检出后的**仓内校验与补取**(`--post-checkout`);以及只打印 remote 的 `--print-remote`。
#
# ⚠️ 为什么本脚本**不能**是 workflow 的第一步:
# Runner 的工作区初始是空的(trap-6),此时 `.gitea/scripts/ci_checkout.sh` **根本不存在** ——
# 直接 `bash .gitea/scripts/ci_checkout.sh` 会返回 127(实测,第三轮审查)。所以顺序必须是:
# ① workflow 里一段**最小 bootstrap**(只依赖 runner 给的环境变量,不依赖仓内任何文件):
# 校验协议/凭据 → git init + remote add → 取**事件完整 SHA** → checkout → 断言 HEAD == 事件 SHA;
# ② 检出完成、脚本就位之后,才调用本脚本 `--post-checkout` 做剩下的校验与补取。
# 本脚本仍然保留协议/凭据/身份的断言 —— 它们要能被本地负例打到(`--print-remote` 等)。
#
# 用法:
# .gitea/scripts/ci_checkout.sh --post-checkout # workflow 检出后调用(读下面的环境变量)
# .gitea/scripts/ci_checkout.sh --print-remote # 只打印将要使用的 remote(本地测协议,不联网)
#
# 环境变量(CI 提供):
# GITHUB_SERVER_URL / GITHUB_REPOSITORY / GITHUB_REF_NAME / GITHUB_SHA
# GITHUB_TOKEN(步骤级注入)/ GITHUB_WORKSPACE / GITHUB_EVENT_PATH(取 before 与默认分支)
set -uo pipefail
SRV="${GITHUB_SERVER_URL:-http://192.168.50.223:3000}"
WS="${GITHUB_WORKSPACE:-$(pwd)}"
BR="${GITHUB_REF_NAME:-${GITHUB_REF:-}}"
BR="${BR#refs/heads/}"
EVENT_SHA="${GITHUB_SHA:-}"
ZERO="0000000000000000000000000000000000000000"
MODE="${1:-}"
# ── 协议:保留 runner 给的服务端协议,并显式禁止 https → http 的降级 ──────────
case "$SRV" in
https://*|http://*) ;;
*) echo "❌ GITHUB_SERVER_URL 的协议不认识(只接受 http/https):${SRV:0:40}"; exit 1 ;;
esac
case "$SRV" in *[[:space:]]*) echo "❌ GITHUB_SERVER_URL 里有空白字符"; exit 1 ;; esac
case "$SRV" in https://*) WANT_SCHEME="https" ;; *) WANT_SCHEME="http" ;; esac
REMOTE="${SRV%/}/${GITHUB_REPOSITORY}.git"
case "$REMOTE" in https://*) GOT_SCHEME="https" ;; http://*) GOT_SCHEME="http" ;; *) GOT_SCHEME="?" ;; esac
if [ "$GOT_SCHEME" != "$WANT_SCHEME" ]; then
echo "❌ remote 的协议被降级了:拼出来是 $GOT_SCHEME,服务端给的是 $WANT_SCHEME"
exit 1
fi
# 凭据一律走 credential helper(URL 内嵌凭据会进 ps / 配置 / 日志)
case "$REMOTE" in *@*) echo "❌ remote URL 里带上了凭据"; exit 1 ;; esac
if [ "$MODE" = "--print-remote" ]; then
echo "$REMOTE"
exit 0
fi
if [ "$MODE" != "--post-checkout" ]; then
echo "用法:ci_checkout.sh --post-checkout(检出后校验+补取)|--print-remote(只打印 remote)"
echo "⚠️ 本脚本**不是**检出本身:空工作区里它还不存在,第一步必须是最小 bootstrap。"
exit 2
fi
cd "$WS" || exit 1
[ -n "$EVENT_SHA" ] || { echo "❌ 没有 GITHUB_SHA:检出必须固定到事件的完整 SHA"; exit 1; }
[ -n "${GITHUB_TOKEN:-}" ] || { echo "❌ 没有拿到读仓库用的令牌(GITHUB_TOKEN 为空)"; exit 1; }
gitauth() { git -c credential.helper='!f() { printf "username=x-access-token\npassword=%s\n" "$GITHUB_TOKEN"; }; f' "$@"; }
# ① 检出必须就是事件那个完整 SHA(bootstrap 已经断言过,这里再断言一次 —— 这一层能被本地负例打到)
GOT="$(git rev-parse HEAD 2>/dev/null || echo "")"
if [ "$GOT" != "$EVENT_SHA" ]; then
echo "❌ 检出 SHA 与事件 SHA 不一致:实际 ${GOT:-空} / 事件 $EVENT_SHA"
exit 1
fi
# remote 里不许有凭据(防有人改回内嵌凭据的写法)
if git remote get-url origin 2>/dev/null | grep -q '@'; then
echo "❌ remote URL 里带上了凭据:$(git remote get-url origin | sed 's#//.*@#//***@#')"
exit 1
fi
# ② 补取:远端追踪引用(分支 + 默认分支)与事件 before
# —— 没有这些引用,范围校验要么算不出、要么退化成"整部历史",两种都不是"检查过本次改动"。
if [ -n "$BR" ]; then
gitauth fetch -q --depth 50 origin "refs/heads/${BR}:refs/remotes/origin/${BR}" 2>/dev/null \
&& echo "✅ 已取远端分支引用 refs/remotes/origin/${BR}"
fi
DEFAULT=""
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "${GITHUB_EVENT_PATH}" ]; then
DEFAULT="$(python3 -c 'import json,os
try:
d = json.load(open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8"))
print((d.get("repository") or {}).get("default_branch") or "")
except Exception:
print("")' 2>/dev/null || echo "")"
fi
[ -n "$DEFAULT" ] || DEFAULT="$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || true)"
[ -n "$DEFAULT" ] || DEFAULT="$BR" # 推 main 时,被推的分支就是默认分支
if [ -n "$DEFAULT" ]; then
gitauth fetch -q --depth 50 origin "refs/heads/${DEFAULT}:refs/remotes/origin/${DEFAULT}" 2>/dev/null \
&& echo "✅ 已取默认分支引用 refs/remotes/origin/${DEFAULT}(新分支推送的范围基准)" \
|| echo "⚠️ 取不到默认分支 ${DEFAULT}:新分支推送会因此明确失败,不会静默算成空范围"
else
echo "⚠️ 事件里没有默认分支名,也推断不出:新分支推送会因此明确失败"
fi
BEFORE=""
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "${GITHUB_EVENT_PATH}" ]; then
BEFORE="$(python3 -c 'import json,os
try:
d = json.load(open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8"))
print(d.get("before") or "")
except Exception:
print("")' 2>/dev/null || echo "")"
fi
if [ -n "$BEFORE" ] && [ "$BEFORE" != "$ZERO" ]; then
if git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
echo "✅ 事件 before ${BEFORE:0:12} 已在克隆里(本次推送范围可直接算)"
else
gitauth fetch -q --depth 50 origin "$BEFORE" 2>/dev/null \
&& echo "✅ 已补取事件 before ${BEFORE:0:12}" \
|| echo "⚠️ 取不到事件 before ${BEFORE:0:12}:范围校验会明确失败,不会静默收缩"
fi
else
echo "(事件里没有 before / before 为全 0:新分支推送,范围按默认分支为基准判)"
fi
git rev-list --count HEAD > "${WS}/.ci-commits-count" 2>/dev/null || echo 1 > "${WS}/.ci-commits-count"
: > "${WS}/.ci-preview-url"
echo "✅ 检出就绪:${GITHUB_REPOSITORY} 分支 ${BR:-(游离)} @ ${GOT:0:12}(== 事件完整 SHA,协议 $GOT_SCHEME)"
git log -1 --format=' %h %an %s'
echo "CI_COMMITS_COUNT_FILE=${WS}/.ci-commits-count" >> "${GITHUB_ENV:-/dev/null}"