用 git bisect run 自动定位首个引入回归的提交

6 次浏览1 条回复

当一段较长的提交历史里出现回归,并且已有命令能判断当前版本是否正常时,git bisect run 可以把二分查找自动化。测试脚本的退出码就是判定协议:0 表示正常,1127(除 125)表示异常,125 表示当前提交无法测试;其他退出码会中止本次查找。

环境:Git 2.39+、Bash;Linux 或 macOS。下面的示例只在新建的临时目录中运行,不会改动现有仓库。

先构造一段包含回归的历史,并给已知正常、已知异常的两端打标签:

tmp="$(mktemp -d)"
cd "$tmp"
git init -b main
git config user.name "Bisect Demo"
git config user.email "demo@example.invalid"

printf 'mode=fast\n' > app.conf
git add app.conf
git commit -m 'good: use fast mode'

printf 'note=baseline\n' > notes.txt
git add notes.txt
git commit -m 'docs: add baseline note'
git tag known-good

printf 'mode=slow\n' > app.conf
git commit -am 'refactor: change processing mode'

printf 'note=follow-up\n' >> notes.txt
git commit -am 'docs: add follow-up note'

printf 'note=release\n' >> notes.txt
git commit -am 'docs: add release note'
git tag known-bad

再创建判定脚本。这里把 mode=fast 当作正常状态,grep 的退出码恰好符合 bisect 的判定协议:

cat > check.sh <<'EOF'
#!/usr/bin/env bash
grep -qx 'mode=fast' app.conf
EOF
chmod +x check.sh

git bisect start known-bad known-good
git bisect run ./check.sh
git bisect reset

运行过程中 Git 会反复检出中间提交并执行脚本,最后报告 first bad commit;示例中会定位到修改 app.conf 的提交。git bisect reset 会恢复开始二分前检出的分支。

在真实项目中,可以把 ./check.sh 换成单元测试、编译检查或最小复现脚本。需要特别区分三种结果:功能断言失败应返回普通非零值;提交因为缺少生成物等原因无法判断时返回 125;网络中断、测试环境损坏等基础设施故障不应伪装成回归,可以返回 128 让查找停止。开始前也应保持工作区干净,避免反复切换提交时混入本地修改。

再补一个适合真实仓库的细节:最好把 bisect 的判定脚本放在工作树之外。这样脚本不会让 git status 出现未跟踪文件,也不会因为某个历史提交恰好存在同名路径而被覆盖。

环境:Git 2.39+、Bash;Linux 或 macOS。在仓库根目录执行:

driver="$(mktemp)"
cat >"$driver" <<'EOF'
#!/usr/bin/env bash
set -u
grep -qx 'mode=fast' app.conf
EOF
chmod +x "$driver"

repo="$(git rev-parse --show-toplevel)"
cleanup() {
  git -C "$repo" bisect reset >/dev/null 2>&1 || true
  rm -f "$driver"
}
trap cleanup EXIT INT TERM

git bisect start known-bad known-good
git bisect run "$driver"

trap 还能覆盖脚本中断的情况,避免仓库停留在 bisect 状态。判定脚本里不建议直接启用 set -e:如果要把“测试失败”“当前提交不可测”和“基础设施异常”分别映射为 1125128,显式捕获命令退出码会更清楚。对于需要编译的项目,还应清理或按提交隔离构建产物,避免旧产物影响二分结果。