1000 个提交里找出引入 bug 的那一个,要测试几次?bisect 选提交的依据是什么?历史里有合并时它怎么选?脚本不能测试某些提交怎么办?这篇用 1000 个提交的历史做了 100 次实验。
$ 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 次测试。
脚本被调用 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 commitgit 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 排除它们、换一个相邻的提交继续,可能多花几步。