最近在学习git
的时候发现了一个有趣的命令,git bisect
。这个命令是 debug 用的。我们往往在开发过程中引入一些bug,但是由于没能立即测试导致这些bug在很多次提交
之后才能被发现,但是这时候距离上次引入的bug可能过去了很长时间,难以复现了。如果我们使用svn的话,往往会不断地svn update
到某个版本然后测试,为了提高搜寻
得效率往往会使用二分查找:在已知的功能正常的版本A和当前版本B找一个中间点C判断,如果C版本也是存在bug的话继续搜索AC区间,否则的话搜索CB区间。在 git 中,
这个操作可以被自动化,bisect
指令就是专门用作这个的。
下面我来简单介绍一下这个用法。
假设有一个 git 库,使用如下的方式构建:
➜ test git init
Initialized empty Git repository in /home/zqc/workspace/test/.git/
➜ test git:(master) for i in {1..10}; do echo $i >> test.log; git add -A; git commit -m "commit $i"; done
[master (root-commit) cc9d416] commit 1
1 file changed, 1 insertion(+)
create mode 100644 test.log
[master 09576a2] commit 2
1 file changed, 1 insertion(+)
[master 9cfabe6] commit 3
1 file changed, 1 insertion(+)
[master f65497a] commit 4
1 file changed, 1 insertion(+)
[master 6292639] commit 5
1 file changed, 1 insertion(+)
[master 301e6fd] commit 6
1 file changed, 1 insertion(+)
[master b440342] commit 7
1 file changed, 1 insertion(+)
[master a4f2dea] commit 8
1 file changed, 1 insertion(+)
[master 8d8ec0a] commit 9
1 file changed, 1 insertion(+)
[master 474b493] commit 10
1 file changed, 1 insertion(+)
其作用是初始化了一个git仓库,并提交了10次,每次豆香test.log
文件末尾增加了一个数字。
所以test.log
最终的内容是:
1
2
3
4
5
6
7
8
9
10
我们假设如果这个文件中所有数字的和超过30
,那么这个程序就是有bug的,现在我们想判断是哪次提交导致和超过了30
。
先要开始二分查找git bisect start
。
现在我们处于HEAD处。此时和为1 + 2 + ... + 10 = 55 > 30
,所以当前是有bug的,因此设置当前的这次提交为bad
, git bisect bad
显然在第一次提交中,和为1,所以其应该是正常的good
,因此设置第一次提交git bisect good cc9d416
。
当我们敲下这条指令是,git会将当前的HEAD指针设置为:
➜ test git:(6292639) git rev-parse HEAD
6292639374ed862233630d2803031f9624e1fa0c
➜ test git:(6292639) git log -1
commit 6292639374ed862233630d2803031f9624e1fa0c
Author: usbuild <xxx@gmail.com>
Date: Tue Jul 11 17:11:08 2017 +0800
commit 5
可以看出是第5次提交,此时我们计算test.log
和为1 + 2 + .. + 5 = 15 < 30
,所以此次提交时好的,那么标识git bisect good
。然后同上,git
将HEAD带到了第7次提交。同样1 + 2 + ... + 7 = 28 < 30
,也是合法的,继续设置为good
。然后到9
,这时和已经超过30
了,所以为bad
。到8
,也是bad
。
这时,git会弹出搜索的结果:
a4f2deaa73f28dffe4cdfb72258d14815cacf185 is the first bad commit
commit a4f2deaa73f28dffe4cdfb72258d14815cacf185
Author: usbuild <xxx@gmail.com>
Date: Tue Jul 11 17:11:08 2017 +0800
commit 8
:100644 100644 06e567b11dfdafeaf7d3edcc89864149383aeab6 535d2b01d3397c2228490875defc92370602ca46 M test.log
它标识处第8次提交引入了这个问题,这也正是符合实际的。
当找到这次提交之后,我们需要调用git bisect reset
来恢复当前的工作区。至此查找工作完成。
虽然说在上述过程中我们不需要手动计算中间版本了,但是对于验证工作还是需要手动计算,稍显麻烦。其实如果有自动化测试脚本的话,bisect
能自动帮我们找到问题的提交。
过程如下:
➜ test git:(master) git bisect start
➜ test git:(master) git bisect good cc9d416
➜ test git:(master) git bisect bad
指定范围后,我们可以使用git bisect run <cmd>
来实现自动查找。当<cmd>
返回值为0
时,标识为good
;否则为bad
。这里我们写一个简单的判断脚本
➜ test git:(6292639) git bisect run python -c "assert(sum(int(x) for x in open('test.log', 'r').read().strip().split('\n')) <= 30)"
running python -c assert(sum(int(x) for x in open('test.log', 'r').read().strip().split('\n')) <= 30)
Bisecting: 2 revisions left to test after this (roughly 1 step)
[b4403425313b8e4a65c7278d2b51ecfcb5a767a3] commit 7
running python -c assert(sum(int(x) for x in open('test.log', 'r').read().strip().split('\n')) <= 30)
Bisecting: 0 revisions left to test after this (roughly 1 step)
[8d8ec0a7976a74d184602070fbe1c148cee49612] commit 9
running python -c assert(sum(int(x) for x in open('test.log', 'r').read().strip().split('\n')) <= 30)
Traceback (most recent call last):
File "<string>", line 1, in <module>
AssertionError
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[a4f2deaa73f28dffe4cdfb72258d14815cacf185] commit 8
running python -c assert(sum(int(x) for x in open('test.log', 'r').read().strip().split('\n')) <= 30)
Traceback (most recent call last):
File "<string>", line 1, in <module>
AssertionError
a4f2deaa73f28dffe4cdfb72258d14815cacf185 is the first bad commit
commit a4f2deaa73f28dffe4cdfb72258d14815cacf185
Author: usbuild <xxx@gmail.com>
Date: Tue Jul 11 17:11:08 2017 +0800
commit 8
:100644 100644 06e567b11dfdafeaf7d3edcc89864149383aeab6 535d2b01d3397c2228490875defc92370602ca46 M test.log
bisect run success
最终出来的结果和我们之前的判断是一样的,只不过减少了人工操作的麻烦。