用 git bisect run 自动化二分定位引入 bug 的提交
问题场景
在多人协作或长期维护的仓库中,某个功能今天还能正常工作,更新若干提交后却失败了。手动 checkout 每个提交、构建、运行测试,效率很低。Git 提供了 git bisect 做二分查找,而 git bisect run 可以自动执行这个过程:你只需标记一个已知好的提交和一个已知坏的提交,再传入一个测试脚本,Git 会自动切换提交、运行脚本,最终输出第一个坏提交。
基本原理
git bisect start 开启二分会话;git bisect bad <commit> 标记坏点;git bisect good <commit> 标记好点。Git 会在好点与坏点之间选择中间提交,切换到该提交。之后使用 git bisect run <cmd>,Git 会反复执行 <cmd>,根据退出码判断当前提交是好是坏:
- 退出码
0:good - 退出码
1到127:bad - 退出码
125:skip,跳过该提交 - 其他退出码:中止二分并保留现场
因此,测试脚本必须明确返回合适的状态码,而不是让构建或环境错误返回意外值。
最小可运行示例
下面创建一个演示仓库,共 4 个提交。c1 正确,c3 引入 bug。
#!/usr/bin/env bash
set -euo pipefail
rm -rf git-bisect-demo
mkdir git-bisect-demo
cd git-bisect-demo
git init -q
git config user.email 'dev@example.com'
git config user.name 'Demo Dev'
cat > calc.py <<'PY'
def add(a, b):
return a + b
PY
cat > test_calc.py <<'PY'
import calc
def test_add():
assert calc.add(2, 3) == 5, '2 + 3 应为 5'
print('test passed')
if __name__ == '__main__':
test_add()
PY
git add calc.py test_calc.py
git commit -qm 'c1: 正确的加法实现'
cat > notes.txt <<'TXT'
第一版说明
TXT
git add notes.txt
git commit -qm 'c2: 添加说明文件'
cat > calc.py <<'PY'
def add(a, b):
return a - b
PY
git add calc.py
git commit -qm 'c3: 错误地把加法改成减法'
echo '第二版说明' >> notes.txt
git add notes.txt
git commit -qm 'c4: 更新说明文件'
echo '仓库已创建:'
pwd
保存为 setup-demo.sh 并执行:
chmod +x setup-demo.sh
./setup-demo.sh
cd git-bisect-demo
然后开始二分:
git bisect start
git bisect bad HEAD
git bisect good HEAD~3
git bisect run python3 test_calc.py
git bisect reset
这里 HEAD 是 c4,HEAD~3 是 c1。git bisect run 会自动在 c1 与 c4 之间切换提交,并运行 python3 test_calc.py。测试通过时脚本以 0 退出,Git 标记为 good;断言失败时 Python 以 1 退出,Git 标记为 bad。最终 Git 会输出类似:
<hash> is the first bad commit
commit <hash>
c3: 错误地把加法改成减法
git bisect reset 用于回到原来的分支或提交。如果中途手动中断,也需要执行该命令恢复。
更稳妥的包装脚本
直接把测试命令交给 git bisect run 有时不够稳健。例如依赖安装失败返回了 127,Git 会把它当 bad。可以写一个包装脚本 bisect-test.sh:
#!/usr/bin/env bash
set -u
# 如果当前提交缺少必要模块,视为无法判断
if ! python3 -c 'import calc' >/dev/null 2>&1; then
exit 125
fi
python3 test_calc.py
status=$?
if [ $status -eq 0 ]; then
exit 0
elif [ $status -eq 1 ]; then
exit 1
else
exit 125
fi
然后运行:
git bisect run bash bisect-test.sh
如果包装脚本放在仓库外,可以避免它本身被 bisect 切换所影响。测试脚本应当尽量快速、确定,不依赖网络、随机数或当前时间。
边界与限制
- 工作区必须干净。开始前用
git status确认没有未提交修改,必要时先git stash或提交。 - 好点必须确实是好的。如果好点本身就有问题,二分结果没有意义。
- 测试脚本不能随意修改被跟踪文件。构建产物、日志、缓存应放在忽略目录或临时目录,避免污染 checkout。
- 合并提交和复杂分支可能增加二分复杂度。遇到无法判断的提交可让脚本返回
125跳过,但跳过过多可能导致无法定位。 - 历史提交可能缺少依赖或构建文件。如果早期版本本来就无法运行,二分可能陷入大量 skip;此时需要调整测试范围或手动检查。
git bisect run适合已有可重复测试的项目。如果测试很慢,每次切换都重新构建,耗时会明显增加。- 最终输出的是二分路径上的第一个坏提交,理解结果时仍应结合提交图和提交内容确认。
小结
git bisect run 把“切换提交、运行测试、判断好坏”自动化,是定位回归问题的实用工具。核心步骤是:准备干净的工作区,标记 good 与 bad,提供返回 0/1/125 的测试脚本,然后让 Git 自动完成二分。使用包装脚本明确退出码,并注意工作区、依赖和测试确定性,可以避免多数误判。
- 点赞
- 收藏
- 关注作者
评论(0)