Git range-diff:看清 rebase 前后哪些提交变了

112 次浏览5 条回复

一组提交改过一轮以后,只看普通 diff 不太容易判断新旧提交怎么对应。git range-diff 会按补丁相似度配对两段提交,并把每个提交内部的变化单独列出来。

环境:Git 2.39+;Linux 或 macOS;Bash;需要 mktemp。下面只操作临时仓库:

set -eu
root=$(mktemp -d)
trap 'rm -rf -- "$root"' EXIT
repo="$root/demo"

git init -q -b main "$repo"
git -C "$repo" config user.name "Range Diff Demo"
git -C "$repo" config user.email "[email protected]"
printf 'limit=10\n' >"$repo/app.conf"
printf '# Demo\n' >"$repo/README.md"
git -C "$repo" add app.conf README.md
git -C "$repo" commit -qm base

git -C "$repo" switch -q -c series-v1
printf 'limit=20\n' >"$repo/app.conf"
git -C "$repo" commit -qam 'raise default limit'
printf '\nRun with the default settings.\n' >>"$repo/README.md"
git -C "$repo" commit -qam 'document default usage'

git -C "$repo" switch -q -c series-v2 main
git -C "$repo" cherry-pick series-v1~1
printf 'limit=25\n' >"$repo/app.conf"
git -C "$repo" commit -qam 'raise default limit' --amend
git -C "$repo" cherry-pick series-v1

git -C "$repo" range-diff main..series-v1 main..series-v2

输出里 ! 表示这对提交能对应上,但补丁内容变了;= 表示补丁没变。这里第一条提交会显示 limit=20 到 limit=25 的差异,第二条文档提交则保持一致。它很适合在 force-push 前后复查一组提交,也可以直接比较两个远端范围,例如 origin/main..origin/topic-v1 和 origin/main..origin/topic-v2。

实际拿它检查强推前后时,有个小坑:先 fetch 会把远端跟踪分支移到新提交,旧范围就不好引用了。假设审查分支是 origin/topic,同样在 Git 2.39+、Bash 下,可以先记住旧 tip 和旧基线:

old_tip=$(git rev-parse origin/topic)
old_base=$(git merge-base origin/main "$old_tip")
git fetch origin
git range-diff "$old_base..$old_tip" origin/main..origin/topic

这样目标分支也随 fetch 前进时,左右两边仍各自使用对应的基线,结果会更干净。

补一个提交改动很大时的排查点:默认配对偶尔会把同一条提交显示成 < 删除和 > 新增,而不是 !。Git 2.39+ 可以试着提高 creation factor:

git range-diff --creation-factor=80 main..series-v1 main..series-v2

数值调高后会更倾向于把删除和新增配成一次修改。不过也不是越高越好,提交本来就确实新增或删除时可能误配,所以只在默认结果明显不合理时再调。

两组提交共用同一个基线时,还可以用三参数简写,少写一遍范围:

git range-diff main series-v1 series-v2

在 Git 2.39+ 中,它等价于 git range-diff main..series-v1 main..series-v2。只适合左右共用 main 的情况;如果 rebase 前后基线也变了,还是像楼上保存旧基线那样显式写两个范围,避免比较结果混进基线变化。

在呢中Lv1#3

两组提交共用同一个基线时,还可以用三参数简写,少写一遍范围:

git range-diff main series-v1 series-v2

在 Git 2.39+ 中,它等价于 git range-diff main..series-v1 main..series-v2。只适合左右共用 main 的情况;如果 rebase 前后基线也变了,还是像楼上保存旧基线那样显式写两个范围,避免比较结果混进基线变化。

如果只是想先定位提交序列有没有漏掉或多出,也可以用 --right-only / --left-only:

git range-diff --right-only old_base..old_tip new_base..new_tip
git range-diff --left-only old_base..old_tip new_base..new_tip

前者只看新序列独有的提交,后者只看旧序列未被新序列保留的提交。这个筛选会隐藏已配对提交的补丁变化,所以最后还是要看一次默认的完整输出。

再补一个 CI 里的边界:range-diff 主要是给人看的,不是稳定的结构化接口。两组提交出现 !、<、> 时,命令也不会因此自动变成失败状态;脚本用退出码或 grep 这些符号做门禁,后续容易误判。

Git 2.39+、Bash 下,更适合把无颜色输出留成构建产物:

git range-diff --no-color old_base..old_tip new_base..new_tip >range-diff.txt

真正的门禁还是交给测试、lint 或项目自己的语义检查,range-diff.txt 留给审阅者看。