name: dotnet-release-management description: “管理.NET发布生命周期。NBGV版本控制、SemVer、变更日志、预发布、分支管理。” user-invocable: false
dotnet-release-management
.NET项目的发布生命周期管理:Nerdbank.GitVersioning(NBGV)设置与version.json配置、版本高度计算、公共发布与预发布模式;针对.NET库(何时提升主版本/次版本/修订版本,API兼容性考虑)和应用程序(构建元数据、部署版本控制)的SemVer 2.0策略;变更日志生成(保持变更日志格式,使用git-cliff和常规提交自动生成);预发布版本工作流(alpha、beta、rc、稳定版进展);以及发布分支模式(发布分支、热修复分支、基于主干的发布与标签)。
版本假设: .NET 8.0+ 基线。Nerdbank.GitVersioning 3.6+(当前稳定版)。SemVer 2.0 规范。
范围
- Nerdbank.GitVersioning(NBGV)设置与版本高度
- 针对库和应用程序的SemVer 2.0策略
- 变更日志生成(保持变更日志格式,git-cliff)
- 预发布版本工作流(alpha、beta、rc、稳定版)
- 发布分支模式(发布分支、基于主干)
超出范围
- CI/CD NuGet推送和部署工作流——参见[技能:dotnet-gha-publish]和[技能:dotnet-ado-publish]
- GitHub Release创建和资产附件——参见[技能:dotnet-github-releases]
- NuGet包元数据和签名——参见[技能:dotnet-nuget-authoring]
- 项目级配置(SourceLink、CPM)——参见[技能:dotnet-project-structure]
交叉引用:[技能:dotnet-gha-publish]用于CI发布工作流,[技能:dotnet-ado-publish]用于ADO发布工作流,[技能:dotnet-nuget-authoring]用于NuGet包版本控制属性。
NBGV(Nerdbank.GitVersioning)
NBGV从git历史计算确定性版本号。版本源自version.json文件和git提交高度(自版本在version.json中设置以来的提交数),无需手动提升版本即为每个提交生成唯一版本。
安装
# 安装NBGV CLI工具
dotnet tool install --global nbgv
# 在仓库中初始化NBGV
nbgv install
# 这会在仓库根目录创建version.json
version.json配置
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.0",
"publicReleaseRefSpec": [
"^refs/heads/main$",
"^refs/tags/v\\d+\\.\\d+(\\.\\d+)?(-.*)?$"
],
"cloudBuild": {
"buildNumber": {
"enabled": true
},
"setVersionVariables": true
}
}
version.json字段参考
| 字段 | 目的 | 示例 |
|---|---|---|
version |
基础版本(主版本.次版本,可选修订版本) | "1.0", "2.3.0" |
publicReleaseRefSpec |
生成公共版本的分支/标签正则模式 | ["^refs/heads/main$"] |
cloudBuild.buildNumber.enabled |
将CI构建号设置为计算出的版本 | true |
cloudBuild.setVersionVariables |
将版本导出为CI环境变量 | true |
nugetPackageVersion |
覆盖NuGet包版本格式 | {"semVer": 2} |
assemblyVersion.precision |
程序集版本组件计数 | "major", "minor", "build", "revision" |
inherit |
从父目录version.json继承 | true |
版本高度如何工作
NBGV计算自version.json中version字段上次更改以来的提交数。该计数成为修订版本:
version.json: "version": "1.2"
提交历史:
abc1234 功能:添加缓存 -> 1.2.3
def5678 修复:空检查 -> 1.2.2
ghi9012 杂务:更新依赖 -> 1.2.1
jkl3456 提升版本到1.2 -> 1.2.0 (version.json在此更改)
版本高度确保每个提交都有唯一版本,无需手动干预。
预发布与公共发布
{
"version": "1.2-beta",
"publicReleaseRefSpec": [
"^refs/heads/main$",
"^refs/tags/v\\d+\\.\\d+(\\.\\d+)?(-.*)?$"
]
}
| 分支/引用 | 计算版本 | 备注 |
|---|---|---|
main(公共) |
1.2.5-beta |
公共预发布,高度=5 |
feature/foo(非公共) |
1.2.5-beta.gcommithash |
包含git哈希后缀 |
标签 v1.2.5(公共) |
1.2.5 |
在标签前移除-beta |
要发布稳定版本,在发布提交前从version.json移除预发布后缀:
{
"version": "1.2"
}
NBGV CLI命令
# 显示当前计算版本
nbgv get-version
# 显示特定版本属性
nbgv get-version -v NuGetPackageVersion
nbgv get-version -v SemVer2
# 准备发布(创建发布分支,提升版本)
nbgv prepare-release
# 为CI设置版本变量
nbgv cloud
单仓库NBGV配置
对于独立版本项目的单仓库,在每个项目目录放置version.json并使用inherit:
仓库根目录/
version.json <- { "version": "1.0" }
src/
LibraryA/
version.json <- { "version": "2.3", "inherit": true }
LibraryB/
version.json <- { "version": "1.1-beta", "inherit": true }
inherit字段从父version.json拉取设置(如publicReleaseRefSpec和cloudBuild),同时覆盖版本号。
.NET库的SemVer策略
何时提升版本
SemVer 2.0指定版本格式主版本.次版本.修订版本:
| 变更类型 | 版本提升 | 示例 |
|---|---|---|
| 破坏性API变更 | 主版本 | 移除公共类型/成员,更改方法签名,重命名命名空间 |
| 新功能(向后兼容) | 次版本 | 添加公共类型/成员,新扩展方法,新重载 |
| 错误修复(向后兼容) | 修订版本 | 修复错误行为,性能改进,内部重构 |
.NET特定破坏性变更考虑
| 变更 | 破坏性? | 备注 |
|---|---|---|
| 移除公共类型 | 是(主版本) | 引用它的消费者将无法编译 |
| 移除公共方法 | 是(主版本) | 直接调用者将失败 |
| 向公共方法添加必需参数 | 是(主版本) | 现有调用者未提供它 |
| 向公共方法添加可选参数 | 否(次版本) | 二进制兼容,但对使用命名参数的调用者源破坏 |
| 更改返回类型 | 是(主版本) | 二进制和源破坏 |
| 添加新公共类型 | 否(次版本) | 无现有代码受影响 |
| 添加新重载 | 否(次版本) | 现有调用仍解析 |
| 更改内部实现 | 否(修订版本) | 无公共API变更 |
| 更改可选参数的默认值 | 否(修订版本) | 二进制兼容(值在重新编译时嵌入调用点) |
| 密封先前未密封的类 | 是(主版本) | 继承它的消费者将失败 |
| 使虚拟方法非虚拟 | 是(主版本) | 覆盖它的消费者将失败 |
API兼容性验证
使用EnablePackageValidation捕获意外破坏性变更。完整包验证设置,参见[技能:dotnet-nuget-authoring]。
<PropertyGroup>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
</PropertyGroup>
应用程序的SemVer策略
应用程序(Web应用、桌面应用、服务)与库的版本控制考虑不同,因为它们没有公共API消费者。
应用程序版本控制方法
| 方法 | 格式 | 最适合 |
|---|---|---|
| SemVer(功能驱动) | 1.2.3 |
安装的桌面/移动应用,用户可见版本控制 |
| CalVer(日历基础) | 2024.1.15 |
持续部署的SaaS应用 |
| 构建号 | 1.2.3+42 |
CI驱动的版本控制,构建元数据 |
| NBGV高度 | 1.2.42 |
从git提交自动版本控制 |
构建元数据
SemVer 2.0允许+后缀的构建元数据,不影响版本优先级:
1.2.3+build.42 构建号
1.2.3+abcdef Git提交哈希
1.2.3+2024.01.15 构建日期
1.2.3-beta.1+42 预发布带构建元数据
构建元数据有助于将部署的二进制文件追踪回其源提交。NBGV自动附加git元数据。
部署版本控制
对于持续部署的服务,版本标记有助于故障排除:
<PropertyGroup>
<!-- 在程序集中嵌入完整版本,供运行时内省 -->
<InformationalVersion>1.2.3+abcdef.2024-01-15</InformationalVersion>
</PropertyGroup>
在运行时读取:
var version = typeof(Program).Assembly
.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
// 返回 "1.2.3+abcdef.2024-01-15"
变更日志生成
保持变更日志格式
保持变更日志格式是广泛采用的标准:
# 变更日志
本项目所有显著变更将记录在此文件中。
格式基于[保持变更日志](https://keepachangelog.com/en/1.1.0/),
本项目遵循[语义化版本控制](https://semver.org/spec/v2.0.0.html)。
## [未发布]
### 添加
- 小部件缓存支持以提高吞吐量
## [1.2.0] - 2024-03-15
### 添加
- 小部件配置的流畅API
- 批处理支持
### 更改
- 改进无效小部件状态的错误消息
### 修复
- 高并发下小部件池中的内存泄漏
- 计划小部件操作中的时区处理
### 弃用
- `Widget.Create()`静态方法——改用`WidgetBuilder`
## [1.1.0] - 2024-01-10
### 添加
- 小部件序列化支持
[未发布]: https://github.com/mycompany/widgets/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/mycompany/widgets/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/mycompany/widgets/releases/tag/v1.1.0
部分类型
| 部分 | 目的 |
|---|---|
Added |
新功能 |
Changed |
现有功能的变更 |
Deprecated |
将在未来版本移除的功能 |
Removed |
此版本中移除的功能 |
Fixed |
错误修复 |
Security |
漏洞修复 |
使用git-cliff自动生成
git-cliff从常规提交生成变更日志:
# 安装git-cliff
cargo install git-cliff
# 为所有版本生成变更日志
git cliff --output CHANGELOG.md
# 仅生成未发布变更的变更日志
git cliff --unreleased --output CHANGELOG.md
# 为特定标签范围生成笔记
git cliff --tag v1.2.0 --unreleased
为.NET常规提交模式配置cliff.toml:
# cliff.toml
[changelog]
header = """
# 变更日志
本项目所有显著变更将记录在此文件中。
"""
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [未发布]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^perf", group = "Changed" },
{ message = "^refactor", group = "Changed" },
{ message = "^docs", group = "Documentation" },
{ message = "^chore\\(deps\\)", group = "Dependencies" },
{ message = "^chore", skip = true },
{ message = "^ci", skip = true },
{ message = "^test", skip = true },
]
常规提交格式
feat: 添加小部件缓存支持
fix: 修正调度器中的时区处理
feat!: 重命名Widget.Create()为WidgetBuilder.Build()
chore(deps): 更新System.Text.Json到8.0.5
docs: 更新缓存的API参考
主体中的破坏性变更:
feat: 重新设计小部件API
破坏性变更:Widget.Create()已被移除。改用WidgetBuilder。
| 前缀 | SemVer影响 | 变更日志部分 |
|---|---|---|
feat: |
次版本 | 添加 |
fix: |
修订版本 | 修复 |
feat!: 或 破坏性变更: |
主版本 | 破坏性变更 |
perf: |
修订版本 | 更改 |
refactor: |
修订版本 | 更改 |
docs: |
无 | 文档 |
chore: |
无 | (跳过) |
预发布版本工作流
标准预发布进展
alpha -> beta -> rc -> 稳定版
1.0.0-alpha.1 早期开发,API不稳定
1.0.0-alpha.2 持续alpha迭代
1.0.0-beta.1 功能完整,API稳定化
1.0.0-beta.2 Beta错误修复
1.0.0-rc.1 发布候选,最终验证
1.0.0-rc.2 RC错误修复(如果需要)
1.0.0 稳定版发布
NBGV预发布工作流
# 从version.json中的预发布后缀开始
# version.json: { "version": "1.0-alpha" }
# 生成:1.0.1-alpha, 1.0.2-alpha, ...
# 提升到beta
# 编辑version.json: { "version": "1.0-beta" }
# 生成:1.0.1-beta, 1.0.2-beta, ...
# 提升到rc
# 编辑version.json: { "version": "1.0-rc" }
# 生成:1.0.1-rc, 1.0.2-rc, ...
# 提升到稳定版
# 编辑version.json: { "version": "1.0" }
# 生成:1.0.1, 1.0.2, ...
手动预发布工作流
对于不使用NBGV的项目:
<!-- 在.csproj或Directory.Build.props中 -->
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>beta.1</VersionSuffix>
<!-- 生成:1.0.0-beta.1 -->
</PropertyGroup>
从CI覆盖:
# CI设置预发布后缀
dotnet pack /p:VersionSuffix="beta.$(BUILD_NUMBER)"
# 稳定版发布:省略VersionSuffix
dotnet pack
NuGet预发布排序
NuGet遵循SemVer 2.0预发布优先级:
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.2
1.0.0-alpha.2 < 1.0.0-beta
1.0.0-beta < 1.0.0-beta.1
1.0.0-rc.1 < 1.0.0
数字标识符作为整数比较;字母标识符按词典顺序比较。
发布分支模式
基于主干的标签
最简单的发布模型。所有开发在main上进行,发布用标签标记。
main: A -- B -- C -- D -- E -- F -- G
| |
v1.0.0 v1.1.0
# 为发布标记并推送
git tag -a v1.0.0 -m "发布 v1.0.0"
git push origin v1.0.0
最适合: 库、小团队、持续交付。
发布分支
创建发布分支进行稳定化,同时main继续开发。
main: A -- B -- C -- D -- E -- F -- G
\
release/1.0: C' -- D' -- E'
|
v1.0.0
# 创建发布分支
git checkout -b release/1.0 main
# 在发布分支上稳定化(仅错误修复)
git commit -m "修复:小部件池中的空检查"
# 标记并发布
git tag -a v1.0.0 -m "发布 v1.0.0"
git push origin release/1.0 v1.0.0
# 将修复合并回main
git checkout main
git merge release/1.0
最适合: 有支持合同的产品、LTS版本、需要并行开发和稳定化的团队。
NBGV prepare-release
NBGV自动化发布分支创建和版本提升:
# 创建release/v1.0分支,将main提升到1.1-alpha
nbgv prepare-release
# 这做什么:
# 1. 从当前提交创建分支"release/v1.0"
# 2. 在发布分支上:移除预发布后缀(version: "1.0")
# 3. 在main上:提升到"1.1-alpha"(下一个开发版本)
热修复分支
针对已发布版本的紧急修复:
main: A -- B -- C -- D -- E
\
release/1.0: C' -- v1.0.0
\
hotfix/1.0.1: F' -- v1.0.1
# 从发布标签分支
git checkout -b hotfix/1.0.1 v1.0.0
# 修复关键问题
git commit -m "修复:认证处理器中的关键安全漏洞"
# 标记并发布热修复
git tag -a v1.0.1 -m "热修复 v1.0.1"
git push origin hotfix/1.0.1 v1.0.1
# 将热修复合并回main
git checkout main
git merge hotfix/1.0.1
分支模式比较
| 模式 | 发布节奏 | 并行版本 | 复杂性 |
|---|---|---|---|
| 主干 + 标签 | 持续 | 否 | 低 |
| 发布分支 | 计划 | 是 | 中等 |
| GitFlow(完整) | 计划 | 是 | 高 |
对于大多数.NET开源库,基于主干的标签和NBGV已足够。为同时维护多个支持版本的产品保留发布分支。
代理注意事项
-
NBGV
version.json仅使用主版本.次版本(非主版本.次版本.修订版本)——修订版本从提交高度计算。设置"version": "1.2.3"将修订版本固定为3,违背自动版本控制的目的。 -
NBGV需要git历史来计算版本高度——浅克隆(
git clone --depth 1)产生错误版本。在CI中,使用fetch-depth: 0与actions/checkout获取完整历史。 -
publicReleaseRefSpec模式是正则表达式,非通配符——使用^refs/heads/main$而非main。缺少锚点将匹配意外引用。 -
SemVer预发布排序中,非数字段按词典顺序——
alpha<beta<rc因为字母比较。数字段作为整数比较,所以beta.2<beta.10(因为2 < 10)。不要假设数字标识符的词典顺序。 -
不要对NuGet库使用CalVer——NuGet解析依赖于SemVer排序。像
2024.1.0这样的CalVer版本机械工作但违反消费者对API稳定性信号的期望。 -
VersionPrefix+VersionSuffix组合形成Version——同时设置所有三个会导致冲突。单独使用Version或一起使用VersionPrefix/VersionSuffix,而非两者。 -
保持变更日志
[未发布]部分必须在发布前更新——将条目从[未发布]移动到新版本部分,更新比较链接,并添加新的空[未发布]部分。 -
nbgv prepare-release修改新分支和当前分支——它将当前分支上的版本提升到下一个次版本。从要继续开发的分支(通常main)运行它。