用 git bisect run 自动定位引入测试失败的提交
问题场景
主干上有一条回归测试失败,但可疑提交有几十个。手工 checkout 每个提交、安装依赖、运行测试,既耗时又容易因为环境不一致得到错误结论。git bisect 可以用二分法缩小范围,git bisect run 则把“判断当前提交是好是坏”交给脚本自动完成。
关键不是让脚本打印日志,而是让脚本用退出码表达结论:测试通过返回 0,测试失败返回 1,无法判断返回 125。
退出码约定
git bisect run 根据命令退出码判断:
- 0:当前提交为 good。
- 1–127(125 除外):当前提交为 bad。
- 125:当前提交无法测试,跳过。
- 大于 127:中止 bisect run。
因此,测试脚本必须稳定地区分“测试失败”和“环境或配置失败”。若测试框架用 2、3 等退出码表示配置错误,应先封装成 0、1、125 三种结果。
准备可重复运行的测试脚本
脚本放在仓库外,或确保它在已知 good 提交中已存在。否则 git bisect 检出较早提交时,脚本可能不存在。下面示例假设脚本从仓库根目录运行,项目使用 make build 和固定回归测试入口:
#!/usr/bin/env bash
set -u
set -o pipefail
# 构建失败通常是依赖或环境问题,跳过该提交
if ! make build > /tmp/bisect-build.log 2>&1; then
echo 'build unavailable' >&2
exit 125
fi
# 只运行能稳定复现问题的测试
./tests/run-regression.sh > /tmp/bisect-test.log 2>&1
status=$?
if [ $status -eq 0 ]; then
exit 0
fi
# 按项目约定,2 表示测试配置错误,不是代码坏提交
if [ $status -eq 2 ]; then
exit 125
fi
exit 1
保存为可执行文件,例如 /tmp/bisect-test.sh:
chmod +x /tmp/bisect-test.sh
脚本中不要执行 git checkout、git reset 等会改变 bisect 状态的操作;也不要把构建产物写进被 Git 跟踪的路径。测试应使用临时目录,并在每次运行前清理缓存。
运行 bisect
先确认工作区干净。未提交修改可能被 checkout 覆盖,或让测试结果失真:
git status --porcelain
输出为空再继续。然后启动 bisect:
git bisect start
git bisect bad HEAD
git bisect good <known-good-sha>
git bisect run /tmp/bisect-test.sh
<known-good-sha> 是确认测试通过的提交。git bisect run 会自动检出中间提交并调用脚本,直到输出类似:
<bad-sha> is the first bad commit
定位后可查看提交内容:
git show <bad-sha>
git bisect log
最后退出 bisect 状态,回到开始前的位置:
git bisect reset
若想保留现场,可先记录 git bisect log 的输出;git bisect reset 会清理 bisect 状态并切回原分支。
边界与限制
- 测试必须可重复。随机数、当前时间、网络、外部服务和未固定依赖都会让二分结果不可信。
- good 提交必须确实通过测试,bad 提交必须确实失败;否则二分范围本身错误。
- 构建失败不一定是坏提交。依赖缺失、缓存污染等环境问题应返回 125,而不是 1。
- 大量提交被跳过会降低结论可信度。若最终提示无法确定首个坏提交,应缩小范围或修复测试环境后重跑。
git bisect run只负责定位提交,不负责修复。定位后仍需阅读 diff、补测试并验证修复。- 在共享仓库中操作时,优先使用独立克隆或工作树,避免清理命令误删未跟踪文件。
- 点赞
- 收藏
- 关注作者
评论(0)