bigywater.tech
Git · 55

bisect:二分次数与 bisect run

1000 个提交里找出引入 bug 的那一个,要测试几次?bisect 选提交的依据是什么?历史里有合并时它怎么选?脚本不能测试某些提交怎么办?这篇用 1000 个提交的历史做了 100 次实验。

10 次
1000 个提交里找到引入 bug 的那个所需的测试次数上限(⌈log2 1000⌉)
100 / 100
随机的 100 个 bug 位置全部找对,测试次数 9~10,平均 9.95
exit 125
脚本用它表示“跳过这个无法测试的提交”

二分

实测 · 本机执行输出
  $ git bisect start main $(git rev-list --max-parents=0 HEAD) 2>&1 | sed -E "s/[0-9a-f]{40}/<sha>/g; s/\[[0-9a-f]+\]/[<sha>]/"
  Bisecting: 499 revisions left to test after this (roughly 9 steps)
  [<sha>] 提交 500
  测试当前提交 f.txt=“版本 500” → 标记 good
    $ git bisect good 2>&1 | head -1 | sed -E "s/[0-9a-f]{40}/<sha>/g"
    Bisecting: 249 revisions left to test after this (roughly 8 steps)
  测试当前提交 f.txt=“版本 750” → 标记 bad
    $ git bisect bad 2>&1 | head -1 | sed -E "s/[0-9a-f]{40}/<sha>/g"
    Bisecting: 124 revisions left to test after this (roughly 7 steps)
  测试当前提交 f.txt=“版本 625” → 标记 good
    $ git bisect good 2>&1 | head -1 | sed -E "s/[0-9a-f]{40}/<sha>/g"
    Bisecting: 62 revisions left to test after this (roughly 6 steps)
  测试当前提交 f.txt=“版本 687” → 标记 bad
    $ git bisect bad 2>&1 | head -1 | sed -E "s/[0-9a-f]{40}/<sha>/g"
    Bisecting: 30 revisions left to test after this (roughly 5 steps)
  $ git bisect log | grep -E "^git bisect" | sed -E "s/[0-9a-f]{40}/<sha>/g" | head -5
  git bisect start 'main' '<sha>'
  git bisect good <sha>
  git bisect bad <sha>
  git bisect good <sha>
  git bisect bad <sha>

git bisect 在“好的提交”和“坏的提交”之间检出中点,你回答 good 或 bad,可疑范围折半。1000 个提交最多 10 次测试。

bisect run

实测 · 本机执行输出
  脚本被调用 10 次(用于测试的提交数);找到的第一个坏提交: '提交 637'(期望 “提交 637”)
  bisect run 的输出里每一步都有一行 Bisecting: …,共 9 行:
    Bisecting: 249 revisions left to test after this (roughly 8 steps)
    Bisecting: 124 revisions left to test after this (roughly 7 steps)
    Bisecting: 62 revisions left to test after this (roughly 6 steps)
        …
    Bisecting: 1 revision left to test after this (roughly 1 step)
    Bisecting: 0 revisions left to test after this (roughly 0 steps)
    <sha> is the first bad commit

git bisect run 脚本 用脚本的退出码自动回答:0 为 good,1~124/126/127 为 bad,125 为跳过,≥128 终止。整个过程不需要人工参与,最后输出 is the first bad commit。

次数

实测 · 本机执行输出
  找对了 100/100;测试次数 最少 9 最多 10 平均 9.95(log2(1000) = 9.97)
  分布: 9 次×5  10 次×95

随机 100 个 bug 位置的实验里,测试次数全部在 9 到 10 之间,和 ⌈log₂1000⌉ 吻合,而且全部找对。

非线性历史

实测 · 本机执行输出
  $ git log --graph --format='%s' | sed -E 's/^/  /'
    * M5
    * M4
    *   Merge branch 'side'
    |\
    | * S8
    | * S7
    | * S6
    | * S5
    | * S4
    | * S3
    | * S2
    | * S1
    * | M3
    * | M2
    |/
    * M1
  $ git bisect start main c3d1a526787418d686aff0f74449993dedd23352 2>&1 | sed -E 's/[0-9a-f]{40}/<sha>/g; s/\[[0-9a-f]+\]/[<sha>]/'; git log -1 --format='当前检出: %s'; git bisect reset >/dev/null
  Bisecting: 6 revisions left to test after this (roughly 3 steps)
  [<sha>] S6
  当前检出: S6
  Previous HEAD position was 4ac3082 S6
  Switched to branch 'main'
  main 比 M1 多 13 个提交(可疑集合)。每个候选提交“自己加上它的祖先里属于可疑集合的个数”:
   S1=1  S2=2  S3=3  S4=4  S5=5  S6=6  S7=7  S8=8  M2=1  M3=2  Merge branch 'side'=11  M4=12  M5=13
  可疑集合是 13 个,折半约 6.5;计数最接近一半的是 S6(=6),bisect 选的就是它

历史里有合并时,bisect 选的是能把“可疑集合”(坏提交的祖先减去好提交的祖先)分成最接近两半的提交,按祖先个数而不是提交时间。

跳过

实测 · 本机执行输出
  遇到“版本 500~520 无法编译”的提交(脚本 exit 125),bisect 跳过它们继续,共 10 步;前三步:
    Bisecting: 499 revisions left to test after this (roughly 9 steps)
    Bisecting: 439 revisions left to test after this (roughly 9 steps)
    Bisecting: 219 revisions left to test after this (roughly 8 steps)
    …
    <sha> is the first bad commit | 提交 637
  退出码:0 = good,1~124 与 126~127 = bad,125 = 跳过,≥128 = 终止整个 bisect(文档约定)

无法编译或无法测试的提交让脚本 exit 125,bisect 排除它们、换一个相邻的提交继续,可能多花几步。

分步演示

bisect
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。1000 个提交由 git fast-import 生成;“bug”由测试脚本模拟(f.txt 里的版本号 ≥ K 为坏),而不是真正的代码缺陷;步数是脚本被调用的次数;输出里的 hash 做了省略
    • 没有验证:bisect 在含大量合并的真实历史上的步数;git bisect skip 手工跳过多个提交后“只剩被跳过的提交”的情形;自定义术语 git bisect terms(old/new);--first-parent 选项;bisect visualize;子模块或构建很慢时的使用技巧
    京ICP备2026067502号-1京公网安备11010502063487号