用 git bisect run 自动定位哪个提交引入了回归

47 次浏览7 条回复

遇到“之前正常、现在坏了”的回归时,git bisect 可以把范围二分缩小;如果有一个能返回成功或失败的检查脚本,还能让它自动跑完。

环境:Git 2.25+,项目里准备好一个退出码可靠的脚本。示例假设 scripts/check-regression.sh 返回 0 表示通过、非 0 表示失败:

git bisect start
git bisect bad HEAD
git bisect good v1.4.0
git bisect run ./scripts/check-regression.sh

命令结束后 Git 会停在第一个导致检查失败的提交附近,并打印 bisect 结果。确认完可以恢复工作区:

git bisect reset

脚本最好只依赖仓库里的固定命令,别把网络请求或不稳定的环境状态带进去;否则同一个提交可能每次得到不同结果。

这个命令还有个小细节:检查脚本遇到当前提交跑不起来时,最好返回 125,Git 会把这个提交当成 skip,不会直接判成好或坏。

比如老提交里依赖还没迁完、测试命令本身不存在,这种就别硬塞成失败,不然最后定位出来的提交可能会偏。

补充一个容易踩的点:脚本最好显式处理退出码,别让最后一条命令的状态偶然决定好坏。测试失败返回非 0、当前提交无法构建或环境不满足时返回 125;另外记得清理生成物,避免前一个提交留下的文件影响后面的检查。

我还会给脚本加一层环境隔离:每次检查前把构建目录、缓存目录放到临时路径,或者明确清理后再跑。bisect 会反复切换提交,未跟踪的生成物如果留在工作区,可能让后面的提交“继承”到前一次结果。git clean -fdx 虽然方便,但本地有未保存文件时要谨慎。

Ryan小满Lv1#3

我还会给脚本加一层环境隔离:每次检查前把构建目录、缓存目录放到临时路径,或者明确清理后再跑。bisect 会反复切换提交,未跟踪的生成物如果留在工作区,可能让后面的提交“继承”到前一次结果。git clean -fdx 虽然方便,但本地有未保存文件时要谨慎。

还有一点是结果的单调性:bisect run 默认把每次非 0 都当成坏提交,测试如果偶发超时或受并发影响,定位会漂。脚本里可以对这类错误重试;仍然无法确定时返回 125 跳过当前提交,并把失败日志单独留出来复核。这样比把一次 flaky 失败直接当回归更靠谱。

NiroLv1#4

还有一点是结果的单调性:bisect run 默认把每次非 0 都当成坏提交,测试如果偶发超时或受并发影响,定位会漂。脚本里可以对这类错误重试;仍然无法确定时返回 125 跳过当前提交,并把失败日志单独留出来复核。这样比把一次 flaky 失败直接当回归更靠谱。

再补一个实际会卡住的问题:git bisect run 切到旧提交时,仓库里的检查脚本本身可能还不存在,或者依赖的参数已经变了。比较稳的是把驱动脚本放在工作树外,用绝对路径调用;如果逻辑很短,也可以直接用 git bisect run sh -c '...'。这样被切换的提交只负责提供代码,检查器不会跟着版本一起消失。

隔壁空白Lv1#5

再补一个实际会卡住的问题:git bisect run 切到旧提交时,仓库里的检查脚本本身可能还不存在,或者依赖的参数已经变了。比较稳的是把驱动脚本放在工作树外,用绝对路径调用;如果逻辑很短,也可以直接用 git bisect run sh -c '...'。这样被切换的提交只负责提供代码,检查器不会跟着版本一起消失。

如果回归只在合并后的主线出现,普通二分有时会切到分支中间的提交,结果不太符合排查路径。可以试试 git bisect start --first-parent,只沿主线的第一父提交找;定位到合并提交后,再对对应分支单独跑一轮。这样能把“哪次合并带进来”和“分支里哪次改动出问题”分开。

Nathan03Lv1#6

如果回归只在合并后的主线出现,普通二分有时会切到分支中间的提交,结果不太符合排查路径。可以试试 git bisect start --first-parent,只沿主线的第一父提交找;定位到合并提交后,再对对应分支单独跑一轮。这样能把“哪次合并带进来”和“分支里哪次改动出问题”分开。

还可以把这次二分过程留成可复现记录:结束前跑 git bisect log > bisect.log,里面会记录 good/bad/skip 的判定。之后在同一仓库里用 git bisect replay bisect.log 重放,适合把临时排查过程交给同事复核;不过复现前要确认测试脚本和依赖环境没变。