6de5d52363
build / build (push) Successful in 51s
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 tag 仍由人批准): - **开发号自动递增**:display_version(development) = `<正式版本的主.次>.<自 v<正式版本> 以来的提交数>` (正式 0.1.0 之后第 1 个提交 0.1.1、第 12 个 0.1.12;恰在发布提交上 = 正式号)。 它**不写进 CHANGELOG**(发布记录只在发版时动),由 git 算出来 ⇒ 每个提交都有自己的号、数字只增。 - **浅克隆不猜号**:`--depth` 会把条数算少 ⇒ 浅仓库里拒绝算号,退回 `<版本>-dev+g<短SHA>`; 检出步骤在仓库有 tag 时 `--unshallow` 补全历史。 - 接线三处齐全:`version.py --identity` → 门禁 `-X …CommitsSinceRelease=<n>`;Makefile 的 `COMMITS_SINCE_RELEASE`;Dockerfile 同名 build-arg;Go 侧新增 `CommitsSinceRelease` 与 DisplayVersion() 新规则;契约测试**交叉核对**"注入的提交数 == git 算出的"(防注入错号)。 - **首个正式版本 0.1.0**:CHANGELOG 的 `[Unreleased]` 提成 `## [0.1.0] - 2026-10-13`; 头部补"版本怎么走"两行;RELEASE_RUNBOOK 补递增规则与"下一次发布把次版本加一"。 - 发版预检新增:打印**当前开发号**,并挡住"候选正式版本 <= 当前开发号"(会把开发号当正式号;建议次版本加一)。 验证:version.py --self-test(含 tag 后第 2 个提交 = 0.2.2、恰在 tag 上 = 0.2.0、无 tag 不猜号); release_preflight --self-test(21 分支,含开发号两条);go test 本地三态 + 临时仓库 tag+2 提交实测 (Python 与 Go 都得出 0.2.2);反向夹具 85/0;make verify 15/15 success。
147 lines
7.9 KiB
Bash
Executable File
147 lines
7.9 KiB
Bash
Executable File
#!/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
|
||
|
||
# ③ tag:正式发布用**注释 tag**(`v<版本号>`)。清单落到工作区,供门禁核对
|
||
# "tag 版本 == 它指到的那个提交里的版本记录"。取不到清单/对象时,核对会**明确失败**,
|
||
# 不会静默跳过("看不到就当没有"是假绿)。
|
||
if gitauth ls-remote --tags origin > "${WS}/.ci-remote-tags" 2>/dev/null; then
|
||
echo "✅ 已记录远端 tag 清单($(grep -c . "${WS}/.ci-remote-tags" 2>/dev/null || echo 0) 行)"
|
||
else
|
||
echo "⚠️ 取不到远端 tag 清单 ⇒ 门禁的 tag 一致性核对会明说『没核对远端』,不会假绿"
|
||
fi
|
||
if [ -s "${WS}/.ci-remote-tags" ]; then
|
||
gitauth fetch -q --tags --depth 1 origin 2>/dev/null \
|
||
&& echo "✅ 已取 tag 对象(判『注释 tag』需要它)" \
|
||
|| echo "⚠️ 取 tag 对象失败:门禁的 tag 核对会明确失败,不会跳过"
|
||
# ⚠️ 开发版本号 =「自 v<正式版本> 以来的**提交数**」,浅克隆会把条数算少(会报错号)。
|
||
# 有 tag 时就把历史补全;补不上也不影响正确性 —— version.py 在浅仓库里**拒绝算号**,
|
||
# 退回 dev+g<短SHA>(宁可没号,也不报错号)。
|
||
if [ "$(git rev-parse --is-shallow-repository 2>/dev/null)" = "true" ]; then
|
||
if gitauth fetch -q --unshallow origin 2>/dev/null; then
|
||
echo "✅ 已补全历史(开发版本号要按 tag 以来的提交数算准)"
|
||
else
|
||
echo "⚠️ 补全历史失败 ⇒ 开发版本号退回 dev+g<短SHA>(不会算错号)"
|
||
fi
|
||
fi
|
||
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}" |