Git变更日志生成器踩坑:合并提交掩盖了真相

git变更日志生成器因线性假设丢失合并引入的提交,揭示git历史是DAG而非列表。
作者构建了一个基于diff两个commit的git变更日志生成器,第一版直接遍历`git log A..B`,在标准PR工作流下只能看到合并提交,却看不到被合并的feature提交,导致发布说明丢失实质内容。修复方案是用`git log --first-parent`获取分支级摘要,再对每个合并提交用`git log ^parent1 parent2`展开真实提交并去重。文章进一步指出`A..B`的语义是“从B可达但不从A可达”,会包含已revert的提交,不等于实际发布内容。核心教训是git历史是DAG,任何线性假设的工具都会在无声中丢信息,图才是事实来源。
一个看似简单的需求,却暴露了git的本质
作者构建了一个git变更日志生成器,通过diff两个commit来输出结构化的发布说明。第一版实现直接遍历git log A..B,把每个提交当作直线上的一个点处理——干净、确定性、但错了。
问题出在采用标准PR工作流的仓库上:对main分支执行git log A..B时,能看到合并提交,却看不到这些合并所引入的各个feature提交。生成的变更日志只会写一句"Merge pull request #42",读者对实际发布了什么毫无感知。

这个bug的隐蔽性在于:工具本身运行正常,输出格式也正确,但信息在悄无声息中被吞掉了。直到有人读发布说明时问出"v2.3到底改了什么?",问题才会浮出水面。
修复的关键不是更大的diff
作者的修复思路很有启发性——问题不在于diff范围不够大,而在于遍历的对象选错了。具体做法分两层:
- 用
git log --first-parent A..B获取分支级别的摘要,把每个合并提交当作一个结构性事件 - 对每个合并提交,再用
git log ^parent1 parent2枚举该合并真正引入的提交
最终输出先呈现合并事件,再展开其下的真实提交,并与基线已有内容去重。这样读者既能看到结构,也能看到实质内容。
为什么A..B会骗人
原文指出了一个容易被忽视的语义陷阱:git log A..B的含义是"从B可达但不从A可达"。在图上,这个集合包含了那些被合并、后来又被revert的分支上的提交——它们"可达",但不代表"已发布"。
如果想要的是"实际交付了什么",需要的是:从B可达、减去从A可达、与first-parent边取交集,再加上每次合并拖进来的侧分支提交。作者直言这不是一行命令能解决的,而是一个小型图遍历。
--first-parent 是 git log 的一个遍历选项,它只沿着每个合并提交的第一个父提交向下走。在标准的 GitHub/GitLab PR 工作流中,第一个父提交通常对应目标分支(如 main),第二个父提交对应被合并的 feature 分支。因此 git log --first-parent 天然地跳过所有侧分支提交,只把合并提交本身当作里程碑,从而给出“分支级别”的线性摘要。这适合用来回答“主线发生了什么大事”,但它不是完整的发布清单——被合并进来的具体提交全都被折叠掉了。
与之相对,git log ^parent1 parent2 是 Rev 范围语法的一种写法:^parent1 排除从第一个父提交可达的所有提交,parent2 表示从第二个父提交可达的集合。两者相减,就得到“只在 feature 分支上、尚未进入主线的提交”。对每个合并提交做一次这样的枚举,就能把被合并拖进来的真实提交全部找出来。这正是原文修复方案的核心机制。
更深层的教训:历史是DAG
git历史是一个有向无环图,不是列表。任何假设线性结构的工具都在对用户撒谎,而且这个谎言在你读发布说明之前是不可见的。图才是事实来源,--first-parent只是一个视图,把两者都走一遍再对账,才是诚实的答案。
这个案例的普适价值在于:很多围绕git构建的工具(变更日志、影响分析、版本对比)都容易掉进线性假设的坑。表面上跑得通,实际上丢信息。作者的做法是坚持确定性——没有用LLM去"重写"历史,只是让图做图该做的事。
在 git 的底层模型中,每个提交都是一条记录,包含一个树对象(代表当时的目录快照)和零个或多个父提交。没有父提交的是根提交,有两个或以上父提交的是合并提交。所谓“可达”,是指从某个引用出发,沿着父提交链不断回溯所能到达的所有提交的集合。A..B 就是“从 B 可达”与“从 A 可达”两个集合的差集。
之所以会出现“被 revert 的提交仍被包含”的现象,是因为 revert 只是新增了一个反向补丁的提交,原提交依然存在于历史图中,依然从 B 可达。可达 ≠ 生效,也 ≠ 已发布。理解这一点,就能明白为什么仅靠 A..B 无法回答“实际交付了什么”:它描述的是图的拓扑关系,而不是发布语义。
对工具开发者的启示
如果你在构建任何依赖git历史的工具,有两个检查点值得提前确认:默认的A..B语义是否符合你的"发布"定义;合并提交在你的输出中是被当作噪声忽略、还是被当作结构信号处理。这两个决定会直接影响最终用户对"改了什么"的理解质量。
相关推荐

WorkBuddy全流程教程:从零搭建AI办公自动化智能体
WorkBuddy 全流程教程精讲:从下载安装、三种工作模式到多模态绘本视频实战,再到专家团搭建智能体与工作流配置,帮你用低成本 AI Agent 实现办公自动化。

WorkBuddy智能体全攻略:7天从入门到自动化实战
基于B站256集WorkBuddy全套精讲课程,系统拆解腾讯AI办公工作台的核心能力:Agent原理、Skill封装、连接器跨平台、定时自动化、知识库与零代码开发,附学习路径建议。

8个AI账号翻车7个:我用AI干活的真实复盘
B站8个AI运营账号仅1个跑通。本文拆解"演AI"与"用AI干活"的本质区别,分析5条视频217万播放背后的产能控制、评论区运营和交付节奏策略。