11.4.6 二分查找:git bisect

    空标题文档 - 图1

    1.使用二分查找

    空标题文档 - 图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