11.4.6 二分查找:git bisect

11.4.6 二分查找:git bisect - 图1

1.使用二分查找

11.4.6 二分查找:git bisect - 图2

在下面的试验中定义坏提交的依据很简单,如果在doc目录中包含文件B.txt,则此版本是“坏”的。(这个示例太简陋,不要见笑,聪明的读者可以直接通过doc/B.txt文件就可追溯到B提交。)

下面开始通过手动测试(查找doc/B.txt存在与否),借助Git二分查找定位“问题”版本,具体操作步骤如下。

(1)首先确认工作在master分支。


$cd/path/to/my/workspace/gitdemo-commit-tree/

$git checkout master

Already on 'master'


(2)开始二分查找。


$git bisect start


(3)当前版本已经是“坏提交”,因为存在文件doc/B.txt。而G版本是“好提交”,因为不存在文件doc/B.txt。


$git cat-file-t master:doc/B.txt

blob

$git cat-file-t G:doc/B.txt

fatal:Not a valid object name G:doc/B.txt


(4)将当前版本(HEAD)标记为“坏提交”,将G版本标记为“好提交”。


$git bisect bad

$git bisect good G

Bisecting:5 revisions left to test after this(roughly 2 steps)

[0cd7f2ea245d90d414e502467ac749f36aa32cc4]commit C.


(5)自动定位到C提交。没有文件doc/B.txt,也是一个好提交。


$git describe

C

$ls doc/B.txt

ls:无法访问doc/B.txt:没有那个文件或目录


(6)标记当前版本(C提交)为“好提交”。


$git bisect good

Bisecting:3 revisions left to test after this(roughly 2 steps)

[212efce1548795a1edb08e3708a50989fcd73cce]Commit D:merge G with H


(7)现在定位到D版本,这也是一个“好提交”。


$git describe

D

$ls doc/B.txt

ls:无法访问doc/B.txt:没有那个文件或目录


(8)标记当前版本(D提交)为“好提交”。


$git bisect good

Bisecting:1 revision left to test after this(roughly 1 step)

[776c5c9da9dcbb7e463c061d965ea47e73853b6e]Commit B:merge D with E and F


(9)现在定位到B版本,这是一个“坏提交”。


$git bisect bad

Bisecting:0 revisions left to test after this(roughly 0 steps)

[83be36956c007d7bfffe13805dd2081839fd3603]commit E.


(10)现在定位到E版本,这是一个“好提交”。当标记E为好提交之后,输出显示已经成功定位到引入坏提交的最接近的版本。


$git bisect good

776c5c9da9dcbb7e463c061d965ea47e73853b6e is the first bad commit


(11)最终定位的坏提交用引用refs/bisect/bad标识。可以用如下方法切换到该版本。


$git checkout bisect/bad

Previous HEAD position was 83be369…commit E.

HEAD is now at 776c5c9…Commit B:merge D with E and F


(12)当对"Bug"定位和修复后,撤销二分查找在版本库中遗留的临时文件和引用。撤销二分查找后,版本库切换回执行二分查找之前所在的分支。


$git bisect reset

Previous HEAD position was 776c5c9…Commit B:merge D with E and F

Switched to branch 'master'


2.把“好提交”标记成了“坏提交”该怎么办?

在执行二分查找的过程中,一不小心就有可能犯错,将“好提交”标记为“坏提交”,或者相反。这将导致前面的查找过程也前功尽弃。为此,Git的二分查找提供了一个恢复查找进度的办法,具体操作过程如下。

(1)例如对提交E,本来是一个“好版本”却被错误的标记为“坏版本”。


$git bisect bad

83be36956c007d7bfffe13805dd2081839fd3603 is the first bad commit


(2)用git bisect log命令查看二分查找的日志记录。

把二分查找的日志保存在一个文件中。


$git bisect log>logfile


(3)编辑这个文件,删除记录了错误动作的行。

以井号(#)开始的行是注释。


$cat logfile

bad:[6652a0dce6a5067732c00ef0a220810a7230655e]Add Images for git treeview.

good:[e80aa7481beda65ae00e35afc4bc4b171f9b0ebf]commit G.

git bisect start 'master' 'G'

good:[0cd7f2ea245d90d414e502467ac749f36aa32cc4]commit C.

git bisect good 0cd7f2ea245d90d414e502467ac749f36aa32cc4

good:[212efce1548795a1edb08e3708a50989fcd73cce]Commit D:merge G with H

git bisect good 212efce1548795a1edb08e3708a50989fcd73cce

bad:[776c5c9da9dcbb7e463c061d965ea47e73853b6e]Commit B:merge D with E and F

git bisect bad 776c5c9da9dcbb7e463c061d965ea47e73853b6e


(4)结束正在进行的出错的二分查找。


$git bisect reset

Previous HEAD position was 83be369…commit E.

Switched to branch 'master'


(5)通过日志文件恢复进度,重启二分查找。


$git bisect replay logfile

We are not bisecting.

Bisecting:5 revisions left to test after this(roughly 2 steps)

[0cd7f2ea245d90d414e502467ac749f36aa32cc4]commit C.

Bisecting:0 revisions left to test after this(roughly 0 steps)

[83be36956c007d7bfffe13805dd2081839fd3603]commit E.


(6)再一次回到了提交E,这一次不要标记错了哦。


$git describe

E

$git bisect good

776c5c9da9dcbb7e463c061d965ea47e73853b6e is the first bad commit


3.二分查找使用自动化测试

Git的二分查找命令支持run子命令,可以运行一个自动化测试脚本,实现自动的二分查找。

如果脚本的退出码是0,正在测试的版本是一个“好版本”。

如果脚本的退出码是125,正在测试的版本被跳过。

如果脚本的退出码是1到127(125除外),正在测试的版本是一个“坏版本”。

为本例写一个自动化测试太简单了,无非就是判断文件是否存在,存在则返回错误码1,不存在则返回错误码0。测试脚本good-or-bad.sh如下:


! /bin/sh

[-f doc/B.txt]&&exit 1

exit 0


用此脚本实现自动化二分查找的过程非常简单,具体操作步骤如下。

(1)从已知的坏版本master和好版本G开始新一轮的二分查找。


$git bisect start master G

Bisecting:5 revisions left to test after this(roughly 2 steps)

[0cd7f2ea245d90d414e502467ac749f36aa32cc4]commit C.


(2)自动化测试,使用脚本good-or-bad.sh。


$git bisect run sh good-or-bad.sh

running sh good-or-bad.sh

Bisecting:3 revisions left to test after this(roughly 2 steps)

[212efce1548795a1edb08e3708a50989fcd73cce]Commit D:merge G with H

running sh good-or-bad.sh

Bisecting:1 revision left to test after this(roughly 1 step)

[776c5c9da9dcbb7e463c061d965ea47e73853b6e]Commit B:merge D with E and F

running sh good-or-bad.sh

Bisecting:0 revisions left to test after this(roughly 0 steps)

[83be36956c007d7bfffe13805dd2081839fd3603]commit E.

running sh good-or-bad.sh

776c5c9da9dcbb7e463c061d965ea47e73853b6e is the first bad commit

bisect run success


(3)定位到的“坏版本”是B。


$git describe refs/bisect/bad

B