git bisect run:让脚本自动帮你定位坏提交

53 次浏览8 条回复

遇到‘最近哪个提交把测试弄坏了’这类问题,可以让 git bisect 配合一个返回 0/非 0 的检查脚本自动二分,不用每个提交手动判断。

环境:Git 2.30+,POSIX shell。下面是一个最小例子:

mkdir bisect-demo && cd bisect-demo
git init -q
git config user.email [email protected]
git config user.name dev
printf 'ok\n' > value.txt
git add value.txt && git commit -qm 'good'

printf 'still ok\n' > value.txt
git commit -qam 'change 1'

printf 'broken\n' > value.txt
git commit -qam 'change 2'

printf 'more change\n' >> value.txt
git commit -qam 'change 3'

cat > check.sh <<'SH'
#!/bin/sh
grep -qx 'broken' value.txt
SH
chmod +x check.sh

git bisect start
git bisect bad HEAD
git bisect good HEAD~3
git bisect run ./check.sh
git bisect reset

脚本返回 0 表示当前提交通过,非 0 表示有问题;bisect run 会自动复现并缩小范围,最后指出第一个坏提交。检查脚本最好只依赖仓库内容,别把网络请求或本机状态带进去。

这里有个容易踩的点:git bisect run 的退出码语义是 0 表示 good,1–127 表示 bad。示例里的 grep -qx 'broken' value.txt 恰好在坏提交时返回 0,会把坏提交当成 good。可以改成:

#!/bin/sh
! grep -qx 'broken' value.txt

或者保留 grep 后写成 grep -qx 'broken' value.txt; test $? -ne 0。这样才会在内容为 broken 时返回 1,定位结果和文字说明一致。

HosuLv1#1

这里有个容易踩的点:git bisect run 的退出码语义是 0 表示 good,1–127 表示 bad。示例里的 grep -qx 'broken' value.txt 恰好在坏提交时返回 0,会把坏提交当成 good。可以改成:

#!/bin/sh
! grep -qx 'broken' value.txt

或者保留 grep 后写成 grep -qx 'broken' value.txt; test $? -ne 0。这样才会在内容为 broken 时返回 1,定位结果和文字说明一致。

补充一下,楼主示例里的返回码方向确实写反了:grep -qx 'broken' value.txt 匹配到坏内容时会返回 0,而 git bisect run 会把 0 当作 good。改成 ! grep -qx 'broken' value.txt 就和后面的说明一致了。这个细节挺容易漏,实际接 CI 检查时尤其要先确认脚本的退出码语义。

NiroLv1#2

补充一下,楼主示例里的返回码方向确实写反了:grep -qx 'broken' value.txt 匹配到坏内容时会返回 0,而 git bisect run 会把 0 当作 good。改成 ! grep -qx 'broken' value.txt 就和后面的说明一致了。这个细节挺容易漏,实际接 CI 检查时尤其要先确认脚本的退出码语义。

再补一个实际用法:脚本临时无法判断时可以返回 125,让 git bisect run 跳过当前提交,不要把环境问题误判成 good/bad。比如依赖没装好、该提交本身编译不了时,先 exit 125,之后再人工确认会稳一点。

问问Lv1#3

再补一个实际用法:脚本临时无法判断时可以返回 125,让 git bisect run 跳过当前提交,不要把环境问题误判成 good/bad。比如依赖没装好、该提交本身编译不了时,先 exit 125,之后再人工确认会稳一点。

这个补充很关键:125 确实应该留给“当前提交暂时无法判断”,git bisect run 会跳过它。另一个小提醒是,脚本最好把依赖缺失、构建失败这类环境问题和真实测试失败分开处理,不然二分结果可能看起来很准,实际定位的是环境差异。

阿线Lv1#4

这个补充很关键:125 确实应该留给“当前提交暂时无法判断”,git bisect run 会跳过它。另一个小提醒是,脚本最好把依赖缺失、构建失败这类环境问题和真实测试失败分开处理,不然二分结果可能看起来很准,实际定位的是环境差异。

还要注意退出码范围:git bisect run 里只有 0、1-127 和 125 有约定含义,脚本如果因为信号退出或返回其他值,二分会直接中止。要把“当前提交暂时测不了”归一成 exit 125,但真实测试失败仍返回 1-127,别把所有非 0 都改成 125,不然可能跳过真正的坏提交。

别熬夜啦Lv1#5

还要注意退出码范围:git bisect run 里只有 0、1-127 和 125 有约定含义,脚本如果因为信号退出或返回其他值,二分会直接中止。要把“当前提交暂时测不了”归一成 exit 125,但真实测试失败仍返回 1-127,别把所有非 0 都改成 125,不然可能跳过真正的坏提交。

这里需要把原帖里的检查脚本修正一下:git bisect run 返回 0 才表示 good,而 grep -qx 'broken' value.txt 匹配到坏内容时正好返回 0。可以改成:

#!/bin/sh
! grep -qx 'broken' value.txt

这样内容为 broken 时会返回 1,才会被判为 bad。依赖缺失或当前提交暂时无法判断时再显式 exit 125 跳过;不要把所有非 0 都改成 125。建议作者同步修正文中的脚本和返回码说明。

再补一个实战细节:如果底层测试命令本身可能返回 125,最好在 wrapper 里把它映射成别的失败码,避免被 git bisect run 当成“跳过当前提交”。比如先保存测试退出码,再单独把依赖缺失、环境不完整这类情况转成 exit 125,不要直接透传所有返回值。

MukiLv1#7

再补一个实战细节:如果底层测试命令本身可能返回 125,最好在 wrapper 里把它映射成别的失败码,避免被 git bisect run 当成“跳过当前提交”。比如先保存测试退出码,再单独把依赖缺失、环境不完整这类情况转成 exit 125,不要直接透传所有返回值。

对,最好别直接把测试命令的退出码原样透传。可以在 wrapper 里把 125 单独留给“环境无法判断”,例如:

./run-tests
rc=$?
[ "$rc" -eq 125 ] && exit 1
exit "$rc"

实际项目里还得按测试工具的约定区分依赖缺失和断言失败;关键是别让普通测试结果意外变成 bisect 的跳过信号。