在持续集成与持续交付体系中,版本号、Git Tag、构建产物与 Release 并不是同一个概念。
很多项目在规模较小时,会直接采用“合并一次加一个版本”“提交一次增加 PATCH”“Push Tag 自动发布”等简单规则。这些方式能够快速工作,但随着项目开始被第三方依赖、出现并行开发、Hotfix、预发布版本以及多平台制品后,版本号很容易从“软件兼容性的描述”退化为“Git 历史计数器”。
本文定义一套适用于 Go 服务、CLI、框架及一般软件项目的自动化版本管理规范,其核心原则是:
Git 历史负责回答“这是什么代码”,SemVer 负责回答“这次变化意味着什么”,Git Tag 负责固定一个版本,Release 负责决定是否正式分发这个版本。
本规范采用 Semantic Versioning 2.0.0 作为正式版本语义基础。SemVer 将版本定义为 MAJOR.MINOR.PATCH:不兼容变更增加 MAJOR,向后兼容的新功能增加 MINOR,向后兼容的问题修复增加 PATCH;同时允许通过先行版本号和构建元数据描述开发版本。
1. 设计目标
本方案希望同时满足以下要求:
- 正式版本遵循 Semantic Versioning。
main是正式版本的唯一来源。dev用于集成开发,但不直接产生正式版本。- CI 自动判断版本升级等级。
- 正式 Git Tag 自动创建。
- Git Tag 创建后不自动执行正式 Release。
- Release 必须由人工明确触发。
- Release 必须从指定 Tag 对应的精确 Commit 构建。
- 开发快照能够自动生成版本身份,但默认不污染 Git Tag。
- 多个 PR 连续合并时不能产生版本号竞争。
- 正式版本必须能够通过仓库历史进行审计和验证。
- 已产生的正式 Tag 必须视为不可变版本锚点。
最终形成:
Commit ↓Version Decision ↓Git Tag ↓Manual Approval ↓Release Build ↓Release / Artifact ↓Deployment(可选)版本生成与软件发布由此完全解耦。
2. 核心概念
整个版本生命周期由以下五种对象组成:
| 对象 | 含义 |
|---|---|
| Commit | 某一具体源码状态 |
| Version | 对该源码变化语义的描述 |
| Git Tag | 将版本永久绑定到某一 Commit |
| Build | 从某一确定源码状态产生的制品 |
| Release | 将特定版本作为正式制品对外分发 |
它们之间存在关联,但不能互相替代。
例如:
Commit abc123 │ ├── Version: 1.6.0 │ └── Tag: v1.6.0 │ ├── 可以暂时没有 Release │ └── 若人工批准 ↓ Release v1.6.0因此:
存在 Git Tag,并不意味着该版本已经正式 Release。
这是本规范最重要的设计前提之一。
3. 分支模型
本规范使用两个长期分支:
查看图表源码
gitGraph commit id: "v1.4.0" branch dev checkout dev commit id: "feature A" commit id: "fix B" branch feature/mysql checkout feature/mysql commit id: "mysql adapter" checkout dev merge feature/mysql id: "merge feature/mysql" checkout main merge dev id: "v1.5.0" branch fix/ws-timeout checkout fix/ws-timeout commit id: "fix websocket" checkout main merge fix/ws-timeout id: "v1.5.1" checkout dev merge main id: "sync v1.5.1"各分支职责如下。
| 分支 | 定位 | 是否产生正式版本 |
|---|---|---|
main | 发布主线 | 是 |
dev | 集成开发分支 | 否 |
feature/* | 功能开发 | 否 |
fix/* | 修复分支 | 合入 main 后可能产生 PATCH |
release/* | 可选的大版本稳定分支 | 根据项目需要 |
必须遵守:
正式
vX.Y.ZTag 只能指向main发布线上的 Commit。
dev、feature/* 不允许直接创建正式版本 Tag。
4. 正式版本格式
正式版本采用:
vMAJOR.MINOR.PATCH例如:
v1.4.0v1.4.1v1.5.0v2.0.0其中:
MAJOR.MINOR.PATCH必须表达软件变化的语义,而不能表达 Git 提交数量。
SemVer 的正式定义要求:
PATCH用于向后兼容的问题修复;
MINOR用于向后兼容的新功能;
MAJOR用于不兼容的公共 API 变化。
5. 为什么不能使用提交次数作为 PATCH
以下版本算法不属于本规范:
PATCH = main 上的 Commit 数MINOR = dev → main 合并次数例如当前:
v1.4.0之后依次产生:
docs: update READMEci: update workflowtest: add test casefix: resolve websocket timeout如果采用 Commit 计数,可能得到:
v1.4.1v1.4.2v1.4.3v1.4.4然而前三次变化并没有代表三个用户可见的问题修复。
这会使:
PATCH失去版本语义,变成 Revision Counter。
因此正式版本必须由:
变化的语义
决定,而不是由:
变化发生了多少次
决定。
6. SemVer 判定规则
本规范定义四种 Release Level:
majorminorpatchnone优先级为:
major > minor > patch > none6.1 PATCH
以下变化通常属于 PATCH:
fix:例如:
fix: reconnect websocket after timeoutfix: prevent nil pointer in config loaderfix: correct sqlite migration当前版本:
v1.5.2则:
v1.5.2 ↓v1.5.36.2 MINOR
以下变化通常属于 MINOR:
feat:例如:
feat: add mysql providerfeat: expose plugin lifecycle APIfeat: support configuration hot reload版本变化:
v1.5.3 ↓v1.6.0升级 MINOR 时:
PATCH = 06.3 MAJOR
以下变化属于 MAJOR:
feat!:fix!:refactor!:或者 Commit 中声明:
BREAKING CHANGE:例如:
feat!: redesign plugin provider interface版本变化:
v1.8.4 ↓v2.0.0MAJOR 升级后:
MINOR = 0PATCH = 0由于 MAJOR 表示兼容性边界发生变化,本规范要求:
MAJOR 可以由 CI 自动检测,但必须经过 Maintainer 明确确认。
6.4 NONE
以下变化默认不产生新的正式版本:
docs:test:ci:style:chore:build:refactor: 和 perf: 默认也可以归为 none,但如果实际改变公共行为,可以通过 PR 元数据显式覆盖。
例如:
v1.6.0 | docs | ci | test | fix ↓v1.6.1此前未产生版本号的修改自然包含在 v1.6.1 中。
这是正常行为。
7. Conventional Commits 只负责自动推断
Commit Message 可以用于 CI 自动判断 Release Level,例如:
feat: ...fix: ...docs: ...feat!: ...但 Commit Message 不应成为无法覆盖的唯一事实来源。
本规范同时定义以下 PR Label:
semver:majorsemver:minorsemver:patchsemver:none最终判定优先级:
查看图表源码
flowchart TD A[PR 准备进入 main] --> B{是否存在 semver:* Label?} B -->|是| C[采用显式 Label] B -->|否| D[分析 Conventional Commits] D --> E{存在 Breaking Change?} E -->|是| F[major] E -->|否| G{存在 feat?} G -->|是| H[minor] G -->|否| I{存在 fix?} I -->|是| J[patch] I -->|否| K[none] C --> L[最终 Release Level] F --> L H --> L J --> L K --> L也就是说:
PR Label >Conventional Commit 自动分析 >默认 none8. 多种变化同时存在时如何计算
一个版本周期内可能同时存在:
fixfeatdocs必须采用最高 Release Level。
例如:
| Change Set | Version Level |
|---|---|
docs + test | none |
fix + docs | patch |
feat + fix | minor |
breaking + feat + fix | major |
例如:
当前正式版本:v1.7.4变化:fixfeatfixdocs最终版本:
v1.8.0而不是:
v1.7.5因为 minor > patch。
9. dev -> main 不再天然意味着 MINOR
这是本规范区别于传统“按合并次数计版本”的关键。
例如:
main = v1.4.2dev 中只有:
fix: correct websocket reconnectfix: prevent config race合入 main 后应该得到:
v1.4.3而不是:
v1.5.0如果 dev 包含:
fix: ...feat: add mysql providerdocs: ...则得到:
v1.5.0如果包含:
feat!: redesign module interface则候选版本为:
v2.0.010. 正式版本计算模型
假设当前正式版本:
vA.B.CCI 得到:
LEVEL则:
LEVEL = patch→ vA.B.(C+1)LEVEL = minor→ vA.(B+1).0LEVEL = major→ v(A+1).0.0LEVEL = none→ 不创建正式 Tag可以表示为:
查看图表源码
flowchart LR A["当前版本 vA.B.C"] --> B{Release Level} B -->|none| C["保持 vA.B.C<br/>不创建新 Tag"] B -->|patch| D["vA.B.(C+1)"] B -->|minor| E["vA.(B+1).0"] B -->|major| F["v(A+1).0.0"]11. 开发版本
dev 不创建正式版本,但每次 CI 构建应该拥有唯一版本身份。
推荐格式:
v<next-version>-dev.<revision>+sha.<commit>例如当前正式版本:
v1.5.2dev 只有一个尚未发布的 Fix:
v1.5.3-dev.1+sha.a183cd7之后出现一个 Feature:
v1.6.0-dev.2+sha.b711f20继续开发:
v1.6.0-dev.3+sha.c819ef2正式进入 main 后:
v1.6.0SemVer 规定先行版本的优先级低于对应正式版本,而 +... 后的 Build Metadata 不参与版本优先级比较,因此 -dev.N+sha.xxx 很适合表达开发构建。
12. 开发 Snapshot 默认不创建 Git Tag
虽然下面这种方式 technically 可行:
v1.6.0-dev.1v1.6.0-dev.2v1.6.0-dev.3...但如果每次 dev 更新都创建远程 Git Tag,很快就会产生大量永久 Tag。
因此本规范要求:
普通 Dev Snapshot 默认不得创建永久 Git Tag。
开发版本主要存在于:
CI ArtifactContainer ImageBinary Version MetadataCI Run Metadata例如:
goddd-v1.6.0-dev.17-linux-amd64.tar.gzDocker Image:
goddd:1.6.0-dev.17二进制:
Version:v1.6.0-dev.17+sha.ab319cd13. 哪些 Pre-release 可以创建 Git Tag
只有具有项目生命周期意义的版本才建议成为 Git Tag,例如:
v2.0.0-alpha.1v2.0.0-beta.1v2.0.0-rc.1v2.0.0-rc.2最终:
v2.0.0生命周期:
查看图表源码
flowchart LR D["dev snapshot<br/>2.0.0-dev.N"] A["v2.0.0-alpha.1"] B["v2.0.0-beta.1"] C["v2.0.0-rc.1"] R["v2.0.0"] D --> A A --> B B --> C C --> R项目不需要强制经过所有阶段。
对于普通版本:
dev→ stable已经足够。
对于大型 MAJOR 发布,则可以:
dev→ alpha→ beta→ rc→ stable14. Git Tag 的正式定义
正式 Tag 格式:
^v[0-9]+\.[0-9]+\.[0-9]+$例如:
v1.0.0v1.5.2v2.0.0推荐使用 Annotated Tag。
例如:
git tag -a v1.6.0 -m "Release version v1.6.0"如果项目具备代码签名基础设施,可以进一步采用 Signed Tag。
15. 正式 Tag 必须不可变
正式版本一旦创建:
v1.6.0 → commit abc123就不得变成:
v1.6.0 → commit def456即:
MUST NOT:force-update version tagMUST NOT:reuse released versionSHOULD NOT:delete stable version tagSemVer 同样规定:一个版本正式发布后,其内容不得再被修改;任何变化应该通过新的版本发布。
如果:
v1.6.0存在错误,应产生:
v1.6.1而不是重写:
v1.6.016. Tag 与 Release 必须解耦
本规范明确禁止:
Tag Push ↓自动正式 Release推荐模型是:
查看图表源码
flowchart TD A[PR merged into main] --> B[CI 验证] B --> C[分析 Release Level] C --> D{是否需要新版本?} D -->|否| E[结束] D -->|是| F[计算 Next Version] F --> G[创建 Git Tag] G --> H["停止<br/>不自动 Release"] H -.人工决定.-> I[Manual Release] I --> J[选择正式 Tag] J --> K[Checkout Exact Tag Commit] K --> L[Release Build] L --> M[Publish Release]于是完全允许:
v1.5.0 Tag onlyv1.5.1 Tag onlyv1.5.2 Tag + Release这不是异常,而是预期行为。
17. Tag Workflow
正式 Version Tag Workflow 只负责一件事:
版本计算与版本锚定。
触发条件:
Pull Request mergedANDbase == main其生命周期如下:
查看图表源码
sequenceDiagram participant Dev as Developer participant PR as Pull Request participant Main as main participant CI as Version CI participant Git as Git Repository Dev->>PR: 提交变更 PR->>PR: CI / Review PR->>Main: Merge Main->>CI: PR merged event CI->>CI: 读取 PR Metadata CI->>CI: 分析 Conventional Commits CI->>CI: 应用 semver:* Override CI->>Git: Fetch Tags Git-->>CI: Latest Stable Version CI->>CI: Calculate Next Version alt release:none CI-->>Git: 不创建 Tag else patch/minor/major CI->>Git: Create Annotated Tag CI->>Git: Push Tag end18. Release Workflow
Release 不监听 Tag Push。
Release 只能人工触发。
例如 GitHub Actions 中,可以使用:
workflow_dispatch作为人工入口。GitHub 官方文档将 workflow_dispatch 作为支持人工启动 Workflow 的标准触发方式之一。
用户必须明确指定:
release_tag = v1.6.0完整流程:
查看图表源码
flowchart TD A["人工触发 Release"] --> B["输入 release_tag"] B --> C{Tag 是否存在?} C -->|否| X[拒绝 Release] C -->|是| D{是否为正式版本格式?} D -->|否| X D -->|是| E{Tag Commit 是否属于 main?} E -->|否| X E -->|是| F["Checkout refs/tags/vX.Y.Z"] F --> G["验证 HEAD == Tag SHA"] G --> H["Test"] H --> I["Release Build"] I --> J["Cross Compile"] J --> K["Checksum"] K --> L["SBOM / Container"] L --> M["Generate Release Notes"] M --> N["Publish Release"]19. Release 必须从 Tag Commit 构建
假设:
main:A --- B --- C --- D ↑ ↑ v1.5.0 current HEAD现在决定发布:
v1.5.0构建输入必须是:
B而不是:
D因此“Release 在 main 上执行”更准确的定义应该是:
Release Workflow 的定义和正式版本来源属于 main,但 Release Build 必须 checkout 指定 Tag 所指向的精确 Commit。
绝不能:
checkout main+读取最近 Tag+构建当前 HEAD否则可能产生:
制品名称:v1.5.0实际源码:v1.6.0-dev这样的版本污染。
20. Release Version 不允许猜测
人工已经指定:
release_tag=v1.6.0因此:
VERSION=v1.6.0就是唯一版本来源。
Release Pipeline 不应该再执行:
git describe去猜版本。
也不应该根据:
main HEAD推断应该发布什么。
原则是:
Release consumes version; Release does not decide version.
即:
Version Workflow负责产生版本Release Workflow负责消费版本21. 构建元数据
正式二进制至少应该包含:
buildVersiongitCommitgitBranchbuildTimerelease例如正式版本:
buildVersion = v1.6.0gitCommit = abc1837gitBranch = mainbuildTime = 2026-10-03T13:00:00Zrelease = true开发版本:
buildVersion = v1.7.0-dev.18+sha.de71bc2gitCommit = de71bc2gitBranch = devbuildTime = 2026-10-03T14:00:00Zrelease = false22. CI Build 与 Release Build 必须区分
“Release 必须人工执行”并不意味着:
平时不构建程序。
普通 CI 应该自动执行:
lintformat checkunit testintegration teststatic analysisgo vetgo testgo build而以下行为才属于 Release:
cross compilerelease packagingchecksumsigningSBOMcontainer publishGitHub Releaseproduction publish生命周期:
查看图表源码
flowchart LR A[Code] --> B[CI Build] B --> C[Test] C --> D[Merge] D --> E[Version Tag] E -.人工触发.-> F[Release Build] F --> G[Publish] G -.可选.-> H[Deploy]23. Hotfix 流程
生产版本:
v1.6.0发现问题后:
fix/ws-timeout从 main 建立。
修复完成:
fix/ws-timeout ↓ main如果属于向后兼容 Bug Fix:
v1.6.1随后必须同步回:
dev完整流程:
查看图表源码
flowchart TD A["main<br/>v1.6.0"] --> B["创建 fix/ws-timeout"] B --> C["修复与测试"] C --> D["PR → main"] D --> E["自动 PATCH"] E --> F["Tag v1.6.1"] F --> G["main → dev 同步"] G --> H["dev 继续开发"]这样可以避免:
main 已修复dev 没有修复导致下一轮版本重新引入旧问题。
24. main -> dev 同步不能创建正式版本
Hotfix 回灌:
main → dev属于开发同步行为。
因此:
MUST NOT:创建新的正式版本MUST NOT:修改 main 当前版本MAY:重新计算 dev snapshot例如:
main = v1.6.1dev 原本:v1.7.0-dev.7同步 main 后:v1.7.0-dev.8仍然合理。
25. MAJOR 升级要求人工确认
CI 可以检测:
BREAKING CHANGE并计算:
Current:v1.9.4Proposed:v2.0.0但是 CI 不应自行决定公共兼容性契约发生重大升级。
建议流程:
查看图表源码
flowchart TD A[检测 Breaking Change] --> B["Proposed Version: v2.0.0"] B --> C{是否存在 semver:major?} C -->|否| D[阻止正式版本创建] C -->|是| E{Maintainer Approval} E -->|拒绝| D E -->|通过| F[Merge] F --> G[自动创建 v2.0.0]即:
MAJOR 自动计算,人工授权。
26. 并发版本竞争
考虑两个 PR 几乎同时进入 main:
当前:v1.6.0PR A:fixPR B:fix如果两个 Workflow 并行读取:
latest = v1.6.0就可能同时计算:
next = v1.6.1形成 Race Condition。
因此 Version Workflow 必须串行化。
GitHub Actions 提供 concurrency group 控制同组 Workflow/Job 的并发,并支持让同一资源的执行进行限制。
逻辑应为:
查看图表源码
sequenceDiagram participant A as PR-A Workflow participant B as PR-B Workflow participant L as Version Lock participant Git as Repository A->>L: Request Lock B->>L: Request Lock L-->>A: Granted A->>Git: fetch tags Git-->>A: v1.6.0 A->>Git: create v1.6.1 A->>L: Release Lock L-->>B: Granted B->>Git: fetch tags Git-->>B: v1.6.1 B->>Git: create v1.6.2 B->>L: Release Lock最终得到:
PR A → v1.6.1PR B → v1.6.227. 并发锁不能替代幂等校验
即使存在 Workflow Concurrency,仍应该执行:
fetch tags ↓calculate version ↓fetch tags again ↓verify candidate tag ↓create tag ↓push如果 Push 被拒绝:
MUST NOT:force push正确行为是:
重新 Fetch重新计算重新验证Tag 创建必须是:
Compare → Verify → Create
而不是:
Calculate once → Force write
28. 正式 Tag 权限
建议保护:
v*Tag Namespace。
普通开发者:
MUST NOT:create stable version tagMUST NOT:update stable version tagMUST NOT:delete stable version tag允许创建 Tag 的主体应该限定为:
Version CIGitHub App / Bot以及:Maintainer 的紧急 Break Glass 权限29. main 分支保护
main 至少应该满足:
禁止普通直接 Push禁止 Force Push禁止删除必须通过 PR必须通过 CI团队项目还应该:
要求 Review要求 Required Status Checks这样:
main才真正能够成为:
Release Source of Truth
30. Merge Strategy
由于新版版本算法使用:
PR Metadata+Commit Semantic+PR Label而不是依赖:
Merge Commit 第二父节点所以本方案不强制要求某一种 Merge Strategy。
可以采用:
Merge CommitSquash MergeRebase Merge但对于:
dev → main如果希望 main 成为非常干净的 Release History,可以考虑 Squash Merge。
例如:
dev:几十个工程提交 ↓ Squashmain:v1.4.0 |v1.4.1 |v1.5.0 |v1.5.1于是:
dev负责保存详细工程历史;
main负责表达版本演进历史。
31. 完整 CI/CD 架构
推荐将整个系统拆为四个 Workflow。
查看图表源码
flowchart TB SRC[Source Code] subgraph CI["Workflow 1 — Continuous Integration"] C1[Lint] C2[Test] C3[Build] end subgraph SNAP["Workflow 2 — Dev Snapshot"] S1[Calculate Candidate Version] S2[Generate dev.N + SHA] S3[Build Snapshot] S4[Upload Artifact] end subgraph VER["Workflow 3 — Version Tag"] V1[Analyze Release Level] V2[Calculate SemVer] V3[Concurrency Lock] V4[Create Annotated Tag] end subgraph REL["Workflow 4 — Release"] R1[Manual Trigger] R2[Validate Tag] R3[Checkout Exact Tag] R4[Release Build] R5[Publish] end SRC --> CI CI -->|dev| SNAP CI -->|merged main| VER VER -.manual selection.-> REL32. Workflow 1:Continuous Integration
触发:
Pull RequestPush devPush main负责:
LintUnit TestIntegration TestStatic AnalysisBuild Validation不得负责:
ReleaseDeployment正式 Version Tag 也建议由独立 Workflow 管理,以缩小权限范围。
33. Workflow 2:Dev Snapshot
触发:
push dev负责:
读取 Latest Stable ↓分析 Unreleased Changes ↓Calculate Candidate Version ↓Generate dev.N ↓Append Git SHA ↓Build Snapshot ↓Upload Artifact输出例如:
v1.7.0-dev.18+sha.a92bc71默认:
NO Git TagNO Release34. Workflow 3:Version Tag
触发:
PR merged into main负责:
读取 PR Metadata ↓Determine Release Level ↓Fetch Stable Tags ↓Calculate Next Version ↓Acquire Version Lock ↓Revalidate Remote Tags ↓Create Annotated Tag ↓Push Tag结束。
不得继续:
Publish ReleaseDeploy Production35. Workflow 4:Release
触发:
Manual / workflow_dispatch输入:
release_tag负责:
Validate ↓Checkout Exact Tag Commit ↓Test ↓Build ↓Cross Compile ↓Checksum ↓SBOM ↓Container ↓Release Notes ↓PublishGitHub Actions 的人工触发、Environment、保护规则和并发控制可以组合使用,为发布增加审批和部署保护。
36. 标准触发矩阵
| Event | CI | Snapshot | Stable Tag | Release |
|---|---|---|---|---|
| Feature Push | 可选 | 否 | 否 | 否 |
| Feature → Dev PR | 是 | 否 | 否 | 否 |
| Dev Push | 是 | 是 | 否 | 否 |
| Dev → Main | 是 | 否 | 根据语义 | 否 |
| Fix → Main | 是 | 否 | 通常 PATCH | 否 |
| Docs → Main | 是 | 否 | 通常 NONE | 否 |
| Main → Dev Sync | 是 | 是 | 否 | 否 |
| Stable Tag Push | 可验证 | 否 | 否 | 否 |
| Manual Release | 验证 | 否 | 否 | 是 |
37. 完整版本生命周期示例
当前:
v1.3.2开发开始。
第一次变化
fix: correct sqlite migration得到开发版本:
v1.3.3-dev.1+sha.a11bc22第二次变化
新增:
feat: add mysql adapter由于:
minor > patch开发候选版本变为:
v1.4.0-dev.2+sha.b31df91第三次变化
docs: update configuration guide候选等级仍然是:
minor开发版本:
v1.4.0-dev.3+sha.c51ab83合入 main
dev → mainCI 分析所有尚未发布变化。
最高等级:
minor于是:
v1.4.0Tag 自动创建。
但:
不自动 ReleaseHotfix
随后:
fix: websocket reconnect race进入:
main自动:
v1.4.1Docs
随后:
docs: add mysql example进入 main。
判定:
none不产生:
v1.4.2新 Feature
feat: expose plugin lifecycle API最终:
v1.5.0Breaking Change
feat!: redesign provider interfaceCI 得到:
Current:v1.5.0Detected:majorProposed:v2.0.0需要 Maintainer 确认:
semver:major批准合并后:
v2.0.038. 最终 Git 历史
最终正式版本可能是:
v1.3.0v1.3.1v1.3.2v1.4.0v1.4.1v1.5.0v2.0.0-rc.1v2.0.0与此同时,开发系统内部可能经历:
v1.3.3-dev.1v1.4.0-dev.2v1.4.0-dev.3v1.5.0-dev.1v1.5.0-dev.2v2.0.0-dev.1但这些普通 Dev Snapshot 不需要污染 Git Tag Namespace。
因此仓库中真正永久存在的 Tag 始终保持简洁。
39. 规范性要求
为了避免实现差异,项目应遵循以下强制约束。
MUST
项目必须:
- 使用明确的正式版本格式;
- 保证正式版本 Tag 唯一;
- 保证正式 Tag 指向确定 Commit;
- 从指定 Tag Commit 构建 Release;
- 保证正式版本 Tag 不被重写;
- 对并发 Version Workflow 进行串行化;
- 在创建 Tag 前重新同步远程 Tag;
- 区分 CI Build 与 Release Build;
- 区分 Dev Snapshot 与 Stable Version。
MUST NOT
项目不得:
- 使用 Git Commit 数量冒充 SemVer PATCH;
- 将
dev -> main次数直接定义为 MINOR; - Force Update 正式版本 Tag;
- 从当前
main HEAD冒充历史 Tag 构建; - 根据“最近 Tag”猜测用户指定的 Release;
- 因普通 Dev Commit 大规模创建永久 Tag;
- 在 Tag Push 时无条件自动发布正式版本。
SHOULD
项目应该:
- 使用 Annotated Tag;
- 对
main启用 Branch Protection; - 对
v*启用 Tag Protection; - 使用 Conventional Commits 辅助自动判定;
- 使用
semver:*Label 提供人工覆盖; - 对 MAJOR Upgrade 增加 Maintainer Approval;
- 在制品中记录 Git SHA;
- 为发布产物生成 Checksum;
- 为成熟项目生成 SBOM。
40. 设计边界
本方案解决的是:
Source↓Version↓Tag↓Build↓Release它并不强制规定:
Deployment Strategy因此:
Release之后可以根据项目选择:
Manual DeployContinuous DeliveryContinuous DeploymentCanaryBlue/GreenRolling Update版本制度无需因此发生变化。
41. 关于 0.x.y
SemVer 对:
0.y.z定义为初始开发阶段,其公共 API 尚不能视为稳定。
因此对于仍处于快速设计期的框架,可以采用:
v0.1.0v0.2.0v0.3.0直到项目明确形成稳定 Public API,再进入:
v1.0.0之后兼容性承诺正式生效。
这对于框架、SDK、Library 尤其重要。
42. 最终架构
整套规范最终可以概括为:
查看图表源码
flowchart TD DEV["Development<br/>feature / fix"] DEV --> INT["dev"] INT --> CI["Continuous Integration"] CI --> SNAP["Dev Snapshot<br/>next-version-dev.N + SHA"] INT --> PR["PR → main"] PR --> SEM["Semantic Analysis"] SEM --> LEVEL{"major / minor / patch / none"} LEVEL -->|none| END["No Version"] LEVEL -->|major/minor/patch| TAG["Immutable Version Tag"] TAG --> STOP["No Automatic Release"] STOP -.Maintainer Decision.-> MANUAL["Manual Release"] MANUAL --> CHECK["Validate Tag"] CHECK --> SOURCE["Checkout Exact Tag SHA"] SOURCE --> BUILD["Reproducible Release Build"] BUILD --> ART["Artifacts / Container / SBOM"] ART --> RELEASE["Release"] RELEASE -.optional.-> DEPLOY["Deployment"]43. 结语
一个成熟的版本系统不应该试图让一个数字同时承担:
Commit Counter+Build Number+Compatibility Contract+Release Identifier这些职责应该被拆开。
本规范分别使用:
SemVer表达:
软件兼容性与功能变化。
使用:
Git SHA表达:
精确源码身份。
使用:
-dev.N表达:
开发阶段顺序。
使用:
Git Tag表达:
不可变版本锚点。
使用:
Release表达:
某个版本已经被项目维护者选择并正式分发。
最终形成的版本生命周期不是:
提交代码→ 自动改数字→ 自动发布而是:
代码发生变化 ↓CI 判断变化语义 ↓SemVer 确定版本身份 ↓Git Tag 固定源码状态 ↓维护者决定是否发布 ↓从 Tag 精确复现构建 ↓产生正式 Release这也是本方案最重要的原则:
Version 是软件状态的语义声明;Tag 是这个声明在 Git 中的不可变锚点;Release 是一次明确的分发决策。
三者应该相关,但不应该混为一体。
< Back to blog list