Git range-diff 对照两轮提交:变基以后,哪些补丁真的改了

10-01 3阅读

一个分支完成评审后,作者把它变基到新主线,又修改了一条提交。提交编号全部变了,普通的两端 diff 还会混入新主线的文件变化。评审者真正想知道的是:上一轮的每个补丁,在这一轮变成什么,哪些只是换了基础,哪些修改了内容。

git range-diff 接收两组提交范围,为旧、新补丁建立对应关系,再显示这些补丁之间的差异。它适合复查重新排列、变基或修订过的提交序列,不是把两个目录当前内容作一次简单比较。

保留旧范围,再建立新范围

下例在 Git 2.52.0 和 Bash 5.2.37 验证,仅操作自动清理的临时仓库。旧主题分支包含两个提交:增加 timeout 和增加 retry。主线另增 upstream.txt;主题分支变基后,把第二个提交中的 retry 从一改成二。

Git range-diff 对照两轮提交:变基以后,哪些补丁真的改了

AI概念示意图:用抽象图形说明本文主题,不代表真实界面或运行结果。

#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null
lab=$(mktemp -d)
trap 'rm -rf -- "$lab"' EXIT
git -c init.templateDir= init -q -b main "$lab/repo"
cd "$lab/repo"
git config user.name Demo
git config user.email demo@example.invalid
git config commit.gpgSign false
printf 'base\n' > readme.txt
git add .
git commit -qm base
base=$(git rev-parse HEAD)
git switch -qc topic
printf 'timeout=10\n' > timeout.txt
git add .
git commit -qm 'Add timeout'
old_first=$(git rev-parse HEAD)
printf 'enabled=yes\nretry=1\nbackoff=linear\njitter=no\n' > retry.txt
git add .
git commit -qm 'Add retry'
git branch review-old
git switch -q main
printf 'upstream\n' > upstream.txt
git add .
git commit -qm 'Advance main'
git switch -q topic
git rebase main
printf 'enabled=yes\nretry=2\nbackoff=linear\njitter=no\n' > retry.txt
git add retry.txt
git commit --amend -qm 'Add retry'
[[ $old_first != $(git rev-parse HEAD~1) ]]
[[ $(git show review-old:retry.txt) == $'enabled=yes\nretry=1\nbackoff=linear\njitter=no' ]]
[[ $(git show topic:retry.txt) == $'enabled=yes\nretry=2\nbackoff=linear\njitter=no' ]]
[[ $(git rev-list --count main..topic) == 2 ]]
[[ -f upstream.txt ]]
if git cat-file -e review-old:upstream.txt 2>/dev/null; then exit 1; fi
git --no-pager range-diff --no-color --creation-factor=90 "$base..review-old" main..topic
printf 'verified: two topic commits; retry 1 -> 2\n'

范围的左端同样重要

第一个范围从原始 base 到 review-old,包含旧版的两个提交;第二个从新 main 到 topic,包含新版的两个提交。两边明确写出各自基础,可以避免把主线新增的提交也当成主题补丁。旧分支是实验刻意保留的参照点,不依赖易被后续操作改变的最近一次位置。

运行后会看到第一对提交标为等号,第二对标为感叹号。第一对补丁仍然相同,但因为父提交变了,对象编号也跟着改变;第二对除了编号变化,补丁内容也不同,下面的嵌套差异展示 retry 从一改到二。脚本还直接断言新旧文件内容,以免只靠颜色猜结果。

把对应标记当作评审导航

等号表示匹配的提交没有检测到相关变化,感叹号表示匹配后发现差异。只有旧侧的一项以小于号表示,只有新侧的一项以大于号表示。阅读时先找未对应项和有变化项,再查看具体提交,通常比重新审完所有已看过的补丁更省力。

对应关系是根据补丁等信息计算出来的。本例补丁很短,显式把 creation-factor 设为九十,提高把修改视作匹配提交的倾向;默认值六十可能把第二项显示为删除与新增。这不是永久保存的身份映射。一次提交被拆成很多份,或者几个提交合为一份时,匹配结果需要人工理解;非常大的重写也可能让你看到删除加新增,而不是理想中的一一对应。

它不能替代最终状态验证

脚本故意确认新版含有 upstream.txt、旧版没有。这个差别来自主线前进,不是第二轮主题提交新写的工作;因此没有出现在两组主题补丁的核心对照里。这正是 range-diff 有用的地方,也提醒你它不是完整最终文件树审计。

通过补丁评审之后,仍要检查新版主题分支与当前主线的最终差异,并运行项目测试。补丁看似没改,在新的基础上仍可能与其他代码发生交互;相同的几行增加内容,不保证运行时依赖、配置或调用顺序完全相同。

官方文档明确把这份输出定位为供人阅读的格式,不承诺跨版本文本稳定,也不应把它交给 git apply。示例没有用正则解析提交摘要来做自动化通过判定;可靠断言直接读取提交内容、提交数量和对象关系,range-diff 则留下给人解释差异。

实际评审前,先记住上一轮的头提交和对应基础,再让作者重写自己的分支。保存这些参照能让复查可重复;对已经由多人共同依赖的历史进行重写,则需要另行协调,不能因为存在一个好用的比较命令就忽略协作影响。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。