Git:用 bisect run 自动定位首次引入回归的提交

8 次浏览1 条回复

当回归夹在一串提交中时,逐个 checkout 验证会很慢。git bisect 用二分查找缩小范围,git bisect run 还能反复执行同一个判定脚本,自动找出首个失败提交。

环境:Git 2.39+;Linux 或 macOS;Bash;示例还使用 coreutils 的 cut。下面只操作新建的临时仓库。

set -Eeuo pipefail

root=$(mktemp -d)
repo="$root/demo"

git init -b main "$repo"
git -C "$repo" config user.name "Bisect Demo"
git -C "$repo" config user.email "demo@example.invalid"

# 前三个提交正常,从 limit=4 开始视为回归。
for value in 1 2 3 4 5; do
  printf 'limit=%s\n' "$value" >"$repo/app.conf"
  git -C "$repo" add app.conf
  git -C "$repo" commit -m "set limit to $value"
done

cat >"$repo/check.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
value=$(cut -d= -f2 app.conf)
test "$value" -lt 4
EOF
chmod +x "$repo/check.sh"

# HEAD 已知失败,HEAD~4 已知正常。
git -C "$repo" bisect start HEAD HEAD~4
git -C "$repo" bisect run ./check.sh

# 此时 HEAD 停在首个失败提交,应显示 set limit to 4。
git -C "$repo" show -s --format='%h %s' HEAD
git -C "$repo" bisect reset
rm -rf -- "$root"

判定脚本返回 0 代表正常,返回 1127125 除外)代表失败;当前提交无法可靠测试时可返回 125,Git 会跳过它。脚本应尽量确定、快速,并避免依赖波动的网络服务,否则一次偶发失败就可能把搜索带到错误区间。

实际仓库中,边界也可以直接写标签或提交哈希,例如 git bisect start v2.4.1 v2.4.0。无论成功还是中断,最后都应执行 git bisect reset,恢复开始二分前的分支与工作区位置。

补一个真实仓库里很重要的收尾细节:示例末尾直接调用 git bisect reset,但如果判定脚本异常退出,或执行过程中收到 Ctrl-C,在 set -e 下可能走不到这一行。可以让 EXIT trap 统一恢复二分前的位置,同时保留原退出码。

环境:Bash 4.4+,Git 2.39+。

#!/usr/bin/env bash
set -Eeuo pipefail

bisect_active=0
cleanup() {
  status=$?
  trap - EXIT
  if (( bisect_active )); then
    git bisect reset >/dev/null 2>&1 || true
  fi
  exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

git bisect start "$bad_commit" "$good_commit"
bisect_active=1
git bisect run ./check.sh

这里不要在主流程里再次 reset;让 trap 只负责一次收尾即可。若 git bisect start 本身失败,标记尚未置为 1,也不会误改仓库状态。