Files
knowledge-governance/.gitea/scripts/ci_checkout.sh
T
t 6de5d52363
build / build (push) Successful in 51s
feat(version): 开发号随提交自动递增 + 首个正式版本 0.1.0
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 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。
2026-10-13 16:45:56 +08:00

147 lines
7.9 KiB
Bash
Executable File
Raw 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
# ③ 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}"