avatar

veedrin

Git 专题 11 —— position

程序遇到bug的时候,我们需要快速定位。

定位有两种,第一种是定位bug在哪个提交上,第二种是定位特定文件的某一行是谁最近提交的。

bisect

有时候我们发现程序有bug,但是回退几个版本都不解决问题。说明这个bug是一次很老的提交导致的,也不知道当时怎么就没察觉。

那怎么办呢?继续一个一个版本的回退?

估计Linus Torvalds会鄙视你吧。

为了专注于工作,不分心来鄙视你,Linus Torvalds在git中内置了一套定位bug的命令。

大家都玩过猜数字游戏吧。主持人悄悄写下一个数,给大家一个数字区间,然后大家轮流开始切割,谁切到主持人写的那个数就要自罚三杯了。

对,这就是二分法。git利用二分法定位bug的命令是git bisect

使用

假设目前的git项目历史是这样的。

C0 -- C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7 -- C8 -- C9(HEAD -> master)

这里面有一次commit藏了一个bug,但幸运的是,你不知道是哪一次。

运行git bisect start命令,后跟你要定位的区间中最新的commit和最老的commit。

$ git bisect start HEAD C0

Bisecting: 4 revisions left to test after this (roughly 2 steps)
[ee27077fdfc6c0c9281c1b7f6957ea2b59a461dd] C4

然后你就发现HEAD指针自动的指向了C4commit。如果范围是奇数位,那取中间就行了,如果范围是偶数位,则取中间更偏老的那个commit,就比如这里的C4commit。

$ git bisect good

Bisecting: 2 revisions left to test after this (roughly 1 step)
[97cc0e879dc09796bd56cfd7c3a54deb41e447f6] C6

HEAD指针指向C4commit后,你应该运行一下程序,如果没问题,那说明有bug的提交在它之后。我们只需要告诉git当前commit以及更老的commit都是好的。

然后HEAD指针就自动指向C6commit。

继续在C6commit运行程序,结果复现了bug。说明问题就出在C6commit和C4commit之间。

$ git bisect bad

Bisecting: 0 revisions left to test after this (roughly 0 steps)
[a7e09bd3eab7d1e824c0338233f358cafa682af0] C5

C6commit标记为bad之后,HEAD指针自动指向C5commit。再次运行程序,依然能复现bug。话不多说,标记C5commit为bad

$ git bisect bad

a7e09bd3eab7d1e824c0338233f358cafa682af0 is the first bad commit

因为C4commit和C5commit之间已经不需要二分了,git会告诉你,C5commit是你标记为bad的最早的commit。问题就应该出在C5commit上。

git bisect reset

Previous HEAD position was a7e09bd... C5
Switched to branch 'master'

既然找到问题了,那就可以退出git bisect工具了。

另外,git bisect oldgit bisect good的效果相同,git bisect newgit bisect bad的效果相同,这是因为git考虑到,有时候开发者并不是想定位bug,只是想定位某个commit,这时候用good bad就会有点别扭。

后悔

git bisect确实很强大,但如果我已经bisect若干次,结果不小心把一个goodcommit标记为bad,或者相反,难道我要reset重来么?

git bisect还有一个log命令,我们只需要保存bisect日志到一个文件,然后擦除文件中标记错误的日志,然后按新的日志重新开始bisect就好了。

git bisect log > log.txt

该命令的作用是将日志保存到log.txt文件中。

看看log.txt文件中的内容。

# bad: [4d5e75c7a9e6e65a168d6a2663e95b19da1e2b21] C9
# good: [c2fa7ca426cac9990ba27466520677bf1780af97] add a.md
git bisect start 'HEAD' 'c2fa7ca426cac9990ba27466520677bf1780af97'
# good: [ee27077fdfc6c0c9281c1b7f6957ea2b59a461dd] C4
git bisect good ee27077fdfc6c0c9281c1b7f6957ea2b59a461dd
# good: [97cc0e879dc09796bd56cfd7c3a54deb41e447f6] C6
git bisect good 97cc0e879dc09796bd56cfd7c3a54deb41e447f6

将标记错误的内容去掉。

# bad: [4d5e75c7a9e6e65a168d6a2663e95b19da1e2b21] C9
# good: [c2fa7ca426cac9990ba27466520677bf1780af97] add a.md
git bisect start 'HEAD' 'c2fa7ca426cac9990ba27466520677bf1780af97'
# good: [ee27077fdfc6c0c9281c1b7f6957ea2b59a461dd] C4
git bisect good ee27077fdfc6c0c9281c1b7f6957ea2b59a461dd

然后运行git bisect replay log.txt命令。

$ git bisect replay log.txt

Previous HEAD position was ad95ae3... C8
Switched to branch 'master'
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[ee27077fdfc6c0c9281c1b7f6957ea2b59a461dd] C4
Bisecting: 2 revisions left to test after this (roughly 1 step)
[97cc0e879dc09796bd56cfd7c3a54deb41e447f6] C6

git会根据log从头开始重新bisect,错误的标记就被擦除了。

然后就是重新做人啦。

blame

一个充分协作的项目,每个文件可能都被多个人改动过。当出现问题的时候,大家希望快速的知道,某个文件的某一行是谁最后改动的,以便厘清责任。

git blame就是这样一个命令。blame翻译成中文是归咎于,这个命令就是用来甩锅的。

git blame只能作用于单个文件。

$ git blame a.md

705d9622 (veedrin 2018-12-25 10:09:04 +0800 1) 第一行
74eff2ee (abby 2018-12-25 10:16:44 +0800 2) 第二行
a65b29bd (bob 2018-12-25 10:17:02 +0800 3) 第三行
ee27077f (veedrin 2018-12-25 10:19:05 +0800 4) 第四行
a7e09bd3 (veedrin 2018-12-25 10:19:19 +0800 5) 第五行
97cc0e87 (veedrin 2018-12-25 10:21:55 +0800 6) 第六行
67029a81 (veedrin 2018-12-25 10:22:15 +0800 7) 第七行
ad95ae3f (zhangsan 2018-12-25 10:23:20 +0800 8) 第八行
4d5e75c7 (lisi 2018-12-25 10:23:37 +0800 9) 第九行

它会把每一行的修改者信息都列出来。

第一部分是commit哈希值,表示这一行的最近一次修改属于该次提交。

第二部分是作者以及修改时间。

第三部分是行的内容。

如果文件太长,我们可以截取部分行。

$ git blame -L 1,5 a.md

705d9622 (veedrin 2018-12-25 10:09:04 +0800 1) 第一行
74eff2ee (abby 2018-12-25 10:16:44 +0800 2) 第二行
a65b29bd (bob 2018-12-25 10:17:02 +0800 3) 第三行
ee27077f (veedrin 2018-12-25 10:19:05 +0800 4) 第四行
a7e09bd3 (veedrin 2018-12-25 10:19:19 +0800 5) 第五行

或者这样写。

$ git blame -L 1,+4 a.md

705d9622 (veedrin 2018-12-25 10:09:04 +0800 1) 第一行
74eff2ee (abby 2018-12-25 10:16:44 +0800 2) 第二行
a65b29bd (bob 2018-12-25 10:17:02 +0800 3) 第三行
ee27077f (veedrin 2018-12-25 10:19:05 +0800 4) 第四行

但是结果不是你预期的那样是吧。1,+4的确切意思是从1开始,显示4行。

如果有人重名,可以显示邮箱来区分。添加参数-e或者--show-email即可。

$ git blame -e a.md

705d9622 (veedrin@qq.com 2018-12-25 10:09:04 +0800 1) 第一行
74eff2ee (abby@qq.com 2018-12-25 10:16:44 +0800 2) 第二行
a65b29bd (bob@qq.com 2018-12-25 10:17:02 +0800 3) 第三行
ee27077f (veedrin@qq.com 2018-12-25 10:19:05 +0800 4) 第四行
a7e09bd3 (veedrin@qq.com 2018-12-25 10:19:19 +0800 5) 第五行
97cc0e87 (veedrin@qq.com 2018-12-25 10:21:55 +0800 6) 第六行
67029a81 (veedrin@qq.com 2018-12-25 10:22:15 +0800 7) 第七行
ad95ae3f (zhangsan@qq.com 2018-12-25 10:23:20 +0800 8) 第八行
4d5e75c7 (lisi@qq.com 2018-12-25 10:23:37 +0800 9) 第九行