工程实践

Git 标签、发布记录和你的体验日志该怎样对应

阅读约 1 分钟

Git 标签、一次发布,还有一条体验日志记录,是同一个事件的三份不同记录,而把它们混为一谈,会让体验日志悄悄偏离实际发布出去的东西。标签标记的是一个提交。发布把这个标签和一堆构建产物、一段描述打包在一起。体验日志记录则用一种即使是仓库之外的读者也能理解的语言,说明到底发生了什么变化。它们通常在时间上离得很近,正因为这样,把它们当成一个步骤而不是三个步骤来处理才会显得那么自然,也正因为这样,这道裂缝往往要等到几个月之后,有人问起”v2.4 到底发布了什么”、而诚实的答案需要一番真正的挖掘时,才会真正显露出来。

这三者之间到底有什么真正的区别

记录存在于写给谁看
Git 标签仓库里,作为一个引用任何会去检出那个具体提交的人
发布代码托管平台(GitHub、GitLab)任何会去下载某个构建版本的人
体验日志记录产品自己的体验日志任何在使用这个产品的人,而不只是这个仓库

标签是三者里最偏机械化的那一个:执行一句 git tag v2.4.0 就完事了,完全不要求有任何东西去解释它里面到底装了什么。发布会加上一段描述,通常还会带上可供下载的构建产物,而它面向的对象,依然是那些知道发布页面是什么的开发者。体验日志记录是三者里唯一一个专门写给那种可能永远都不会打开这个仓库的读者看的东西,正因如此,它才是最需要编辑投入心思的那一份,也是在截止日期的压力下最容易被跳过的那一份。

是不是每一个 git 标签都需要配一条体验日志记录

不需要,把两者当成一一对应的关系,是一个很常见的错误。一个标签可能标记的是一个内部里程碑、一个候选发布版本,或者一个根本不会触达大多数用户的紧急修复;这些情况都不一定需要一条公开的记录。判断标准和判断某样东西到底该不该被收进体验日志里的标准完全一样:一个用户或者调用方,会不会注意到这个变化,或者在不在乎这个变化。大多数标签都能通过这个测试。但有些标签,比如那种纯粹只是为了触发一次 CI 流水线而创建的标签,永远都不会通过。

是不是每一条体验日志记录都需要拥有自己专属的标签

不总是这样,而这正是持续部署的团队和发布带版本号软件包的团队开始分道扬镳的地方。一个每天要部署好几次的 SaaS 产品,完全可以把多次部署归到同一条带日期的体验日志记录下面,而不需要每次部署都配一个一一对应的标签;而一个发布到软件包注册中心的库,通常需要为每一个已发布的版本配一个标签。Go 模块和 Swift Package Manager 直接从标签本身解析版本;在 npm 或 PyPI 上,注册中心保存着已发布的版本,而标签是任何人把那个版本对应回源代码的方式。一个拥有多个各自独立管理版本号的包的仓库,必须按包来做这个决定,而不是给整个仓库一次性拍板;Monorepo 的体验日志 讲解了标签前缀和体验日志的范围,为什么应该跟着包的边界走,而不是跟着文件夹的边界走。语义化版本控制,应该怎样配合你的体验日志 完整讲解了版本号本身应该怎样对应到体验日志的分类上;而标签,正是让一个版本号能够对照真实代码被验证的那套机制。

一份发布说明,到底应该和体验日志记录之间是什么关系

它们可以是同一段文字,但前提是两者面对的读者真的完全一致,而这种情况其实比看上去要少见得多。代码托管平台上的发布页面,几乎清一色只有开发者会去读;如果一个产品同时还有非技术用户在读体验日志,那么把发布说明原封不动地复制过去,只会把内部术语和面向代码的措辞,送到一个其实需要通俗语言版本的读者面前。最干净的做法是:把体验日志记录本身,当作面向读者的主要成果来写,然后让发布说明要么直接链接回这条记录,要么给那些本来就已经习惯了那种风格的读者,留一份更短、更偏技术性的摘要。

# Release v2.4.0(GitHub,面向开发者)
把报表流水线升级到了新的聚合引擎。面向客户的摘要请见体验日志:
https://example.com/changelog#v2.4.0

## 2026-09-07(体验日志,面向客户)
### Added
- 报表现在即使对于超过一百万行数据的账户,也能在一秒之内加载
  出来。

同一次发布,两份文档,各自用各自的措辞,写给各自真正的读者。

体验日志记录到底是从哪里来的

来自两个不同的起点,而大多数真实存在的流水线,其实都是这两者的混合。它可以在打标签的那一刻,从提交信息里自动生成出来,这种方式很快,而且绝不会漏掉任何一个已经合并的拉取请求;从 Conventional Commits 到体验日志 完整讲解了这整条流水线。或者,它也可以完全独立于标签之外,靠人工手写出来,时间点对齐的是一个功能被认定为完成的那一刻,而不是代码被合并的那一刻。自动生成出来的记录足够一致,但会原封不动地继承每一条含糊不清的提交信息;而手写的记录读起来更清楚,但需要有人真正花时间去把它写出来。大多数采用自动化的团队,最终还是会在生成出来的文本变成公开记录之前,保留一道轻量的编辑环节,而不是直接把未经处理的原始输出展示出来,这和 Keep a Changelog,实践版 所推荐的自律其实是同一回事,无论那段原始文字最初到底是从哪里来的。

当这三者不再同步的时候,到底会出什么问题

会失去的,是读者最先查看的那个东西所带来的信任。一个存在、却没有对应体验日志记录的标签,从阅读体验日志那一方的视角来看,就像是那一周什么事都没发生过一样。而一条没有对应标签或者发布的体验日志记录,会让一个正在排查生产环境问题的人,根本没办法检出到那条记录发布时、正在线上真正运行着的那份确切代码。真正的解决办法,不是追求完美的自动化,而是为这种对应关系建立一个单一的事实来源:哪怕只是发布流程自己的一份检查清单,明确规定一次可发布的变更,必须在引入它的那同一个提交或者拉取请求里,同时拿到这三样东西。

FAQ

体验日志记录是不是应该从 git 标签里自动生成出来? 可以把它当作一个起点,但仅凭一个标签本身,并不携带任何面向读者的描述,它携带的只是一个提交范围。自动化生成必须去读取这个范围内的提交信息本身,而不能只依赖标签的存在,才能真正产出一些有用的东西。

如果我们并不给每一次发布都打标签,会怎么样? 那么体验日志记录就会变成主要的记录来源,它依然应该带上日期,如果这个产品有版本号的话,也应该带上版本号,这样即使没有对应的标签,这条记录也依然能成为读者以后可以回头引用的东西。

预发布标签(比如 v2.4.0-rc.1 这种)是不是也应该配上体验日志记录? 一般来说不应该。候选发布版本是用来做内部测试或者公测的,为它配一条体验日志记录,只会训练读者去期待那些可能根本不会按描述的样子真正发布出去的版本也有自己的记录。把记录留给那些真正达到了正式可用状态的标签就够了。

一条体验日志记录是不是可以同时覆盖多个 git 标签? 可以,而且对于那些经常打标签的团队来说,往往也应该这样做。把相关的标签归到同一条描述净变化的、带日期的记录下面,而不是按标签逐个发布单薄的记录,把一个功能拆得七零八落,散落在好几次阅读里。


本文的技术内容未经独立审核。如有错误,请告诉我们,我们会更正。

changeloop 相关页面: changelog 生成器, 开发者文档

changeloop
打造闭环 changelog 的团队。用户提出需求,你的团队交付,提出需求的人得知结果。