对着试点仓落地时暴露的问题做的口径与模板修正(本轮不推、不打 tag、不部署): - 条目形状:四个自定义必填节 → **固定四类** Added/Changed/Fixed/Security (分类可省略、不许空栏目);过程记录进 docs/archive/,未合并事项进 docs/BACKLOG.md。 - 破坏性变化/迁移:固定写在 Changed 里、以 `**破坏性**` 开头并**必须链接发布文档**, 仓内 `docs/….md` 链接**对账**(不存在即红)。 - 构建身份:release_version / build_kind / commit_sha / display_version / built_at; 开发构建显示 X.Y.Z-dev+g<短SHA>(无基线 dev+g<短SHA>),**不许只显示上一正式版本号、 不许自称正式版**;release 只认显式发布上下文。 - **还没有正式版本一律放行**:不判红;首个正式版本号**由人指定**(预检把它列成阻塞项)。 - **没核对远端就不许说校验成功**:CI/预检用 `--require-remote`(本地模式明确写「未核对远端」)。 - 修正"CI 取不到 tag 所以不用 tag"的过时口径:正式版本**就是**用注释 tag, 只是**构建时不要用 git describe 取版本号**;**测试 tag 只在临时测试仓库里创建**。 - 模板同步:templates/check-changelog.py、templates/release-preflight.py、 新增 templates/version.py;ci-gate.py 的 version 项在 CI 下强制核对远端; new-project.sh 落 scripts/version.py 并按四类重写 CHANGELOG 骨架; README/07/模板 README 同步;04 增加 §I(V-1~V-4 四条,含负例在哪)。 验证:templates 两个检查器 --self-test 全过;new-project.sh bash -n 通过; 生成的新工程里 check_changelog --tags 绿、--identity 给出 dev+g<短SHA>、 CI=true 下 version 项因"未核对远端"判红(预期);试点仓反向夹具 80/0。
7.3 KiB
07 · 发版操作说明(注释 tag 与 Gitea Release)
正本条款见
01-规范-Git与CI-CD(Gitea版).md§6。本文件只讲怎么操作。 适用:单主干main的所有仓库(本机 Gitea)。 ⚠️ 一句话前提:CI 不会替你发版。CI 只做检查;打 tag、建 Release 是人在明确批准后的动作。
0. 三个状态,先说清楚(别混成一个"绿")
| 状态 | 什么时候算成立 | 证据 |
|---|---|---|
| CI 成功 | 提交推上去,必需检查全过 | 提交上的 status context + run 链接 + 完整 SHA |
| 正式发版 | 批准后打了注释 tag、建了 Release | 远端 tag SHA == 验收过的提交;Release 的 tag_name 读回一致 |
| 部署 | 某个环境真的在跑这个版本 | /version 的 commit_sha + 部署读回证据 |
"CI 绿"不代表"发过版";"发过版"不代表"部署了"。汇报时必须说是哪一个。
1. 版本号的来源与构建身份
构建身份(python3 scripts/version.py --identity):
| 情形 | build_kind |
release_version |
display_version |
|---|---|---|---|
| 开发构建(有正式基线) | development |
顶部已发布版本 | X.Y.Z-dev+g<短SHA> |
| 开发构建(还没有正式版本) | development |
空 | dev+g<短SHA> |
| 发布构建(显式发布上下文) | release |
顶部已发布版本 | X.Y.Z(另带完整 SHA) |
⚠️ 开发构建不许只显示上一正式版本号,也不许自称正式版;build_kind=release
只能来自显式上下文(BUILD_KIND=release),普通 main 构建永远是 development。
- 本环境默认:仓库根
CHANGELOG.md顶部那条已发布版本就是版本号的唯一来源。- 读它:
python3 scripts/version.py(解析不到 ⇒ 非 0 退出,构建据此失败)。 - 构建注入:
-ldflags "-X <mod>/internal/version.Number=<该值>";CI 断言"注入 == 来源",且注入的断言 被 SKIP 也算不通过(contracts检查项)。
- 读它:
- ⚠️ 不要再放
VERSION文件、不要把版本写进源码常量、不要在 CI 里用git describe(检出是--depth 50,取不到 tag ⇒ 静默给错值)。 - 某个项目要用别的权威来源,必须在它自己的
AGENTS.md里写明,并让构建与 CI 都读那一个来源。
2. 日常改动(平时就做,不用发版)
- 改完代码,把使用者可见的变化写进
CHANGELOG.md顶部## [Unreleased]的四类小节 (Added/Changed/Fixed/Security;分类可省略,但别留空栏目); - 提交前跑本仓门禁:
make verify(唯一权威入口,本地与 CI 同一条命令); - 推送。不需要每次提交都升版本、也不需要打 tag。
过程记录不进变更记录:审查轮次、命令输出、逐条修正明细放 docs/archive/;
未合并事项放 docs/BACKLOG.md。日常改动不要求每个提交都写,有使用者可见的变化才记。
3. 准备发版
3.1 先把记录补齐
-
把
## [Unreleased]改成## [x.y.z] - YYYY-MM-DD(三段十进制、不许前导零、日期合法;版本号只增不减)。 ⚠️ 本仓还没有正式发布过时:没有可 bump 的基线,版本号由你指定 (预检会把它列成阻塞项并给一个建议值,不替你编号); -
该版本块里按四类小节写(分类可省略,但不要空栏目):
### Added/### Changed/### Fixed/### Security; -
破坏性变化 / 数据迁移写在
### Changed里、以**破坏性**开头,并链接发布文档:- **破坏性**:接口 `/api/old` 下线,升级步骤见 [迁移步骤](docs/RELEASE_RUNBOOK.md)检查器会对账仓内链接(
docs/….md不存在 ⇒ 红); -
清干净工作区(预检会因为"有未提交改动"直接拦住);
-
跑一遍检查器与构建身份:
python3 scripts/check_changelog.py --tags --require-remote # 结构 + 正式 tag 与提交内版本一致(真查远端)
python3 scripts/version.py --identity # 构建身份(开发/发布、显示版本、完整 SHA)
3.2 跑发布预检(只读,不产生任何副作用)
python3 scripts/release_preflight.py # 默认建议 patch
python3 scripts/release_preflight.py --bump minor # 或 --version 0.2.0
它会打印:待发布版本 / 完整提交 SHA / 该提交上的 CI 成功证据 / [Unreleased] 原文 /
同名远端 tag 与 Release 是否已存在 / 遗留风险与未验证项,并在最后给出
"可以发"或"不能发 + 阻塞项"。
预检的只读性是可验证的:python3 scripts/release_preflight.py --self-test
(断言写类 git 命令与 POST/PUT/DELETE/PATCH 在预检路径上被拒)。
3.3 请主人批准
批准要包含三样:版本号、准确提交的完整 SHA、那次 CI 的成功证据(run 链接)。 不要拿"本地跑过了"当 CI 证据,也不要用短 sha 当"准确提交"。
4. 批准后执行(只有这一步会动远端)
# 三个参数缺一不可;--approve-version / --approve-sha 必须**逐字符**等于预检结果
python3 scripts/release_preflight.py --execute \
--version 0.2.0 --approve-version 0.2.0 --approve-sha <完整40位SHA>
内部顺序固定:打注释 tag (git tag -a v0.2.0) → 推送该 tag → 建 Gitea Release → 读回。
读回判据(缺一即不算完成):
- 远端 tag 的 SHA == 本地 tag 指向的提交(完整比对,不是前缀);
- Release 的
tag_name读回一致; - Release 说明来自 CHANGELOG,不另写正文。
⚠️ tag 推送成功、建 Release 失败 ⇒ 不要重复推 tag(tag 已经存在会被拒),先把 Release 补上。
5. 发版之后
- 部署是另一件事(§5 / 各项目 CD runbook):正式发版 ≠ 已部署。
- 已发布的 tag 不移动、不覆盖。要"重来一次"就发一个新版本号。
- 回滚走部署层(恢复旧产物),不改版本历史。
- 汇报时给:tag、完整 SHA、Release 链接、CI 证据、(若已部署)
/version读回。
6. 常见错误(都踩过)
| 症状 | 原因 | 处理 |
|---|---|---|
| 日常 CI 红了,说我"没有 tag" | 把 tag 一致性检查写成了"必须有 tag" | 没有 tag 不算失败:开发版本不打 tag 是常态,检查只能在存在正式 tag 时判 |
| tag 打上了,但变更记录里没有这个版本 | tag 指向的提交里 CHANGELOG.md 顶部还是旧的版本号 |
检查会红:tag 版本 != 该提交里 CHANGELOG 顶部的版本;先补 CHANGELOG 再打 tag |
| 打了轻量 tag | git tag v1.2.3(没有 -a) |
正式 tag 必须是注释 tag;已发布的轻量 tag 不要强推覆盖,发新版本号 |
| 预检通过了却发不出去 | 预检是只读的,发版要显式 --execute 加两个批准参数 |
按 §4 执行,别指望预检替你发 |
CI 里 git describe 拿不到版本 |
检出是 --depth 50,没有 tag |
不要用 git describe 取版本;读 CHANGELOG 顶部 |
| Release 说明和 CHANGELOG 不一致 | 又写了一份正文 | Release 说明来自 CHANGELOG,只此一份 |