本节所在位置:
- 活用 git stash(一)
- 保存并应用 stash(一)
- 用 git bisect 进行调试(二)✔️
- 使用 git blame 命令(二)✔️
- 设置彩色命令行界面
- 自动补全
- 让 Bash 自带状态信息
- 更多别名
- 交互式新增提交
- 用 Git 的图形界面交互式新增
- 忽略文件
- 展示与清理忽略文件
11.3 用 git bisect 进行调试
实际工作中经常遇到类似“究竟是哪次提交引入的某个 Bug
”的问题,而底层应用二分算法的 git bisect
命令是该类问题的绝佳工具,尤其适用于在较长的提交历史记录中快速锁定引入 bug
具体 commit
。二分查找法(binary search method
)或二分法(bisection method
),是一种在有序数组中查找目标位置的搜索方法。算法会在每一步与数组的中间值进行比较,如果匹配成功则返回该位置;否则,根据比较结果,选择中间值的右侧或左侧的子数组继续搜索,直至找到目标位置。在 Git
中,历史提交记录对应一组可供测试的值数组,若程序能在某个 commit
编译成功则为目标位置。二分查找的算法复杂度为 O(log n)。
本节演示如何从一个提交了 23 次的带 bug
分支利用 git bisect
命令定位该 bug
的全过程(假设带 bug
提交的 SHA-1
为 83c22a39955ec10ac1a2a5e7e69fe7ca354129af
):
# init repo
$ git clone https://github.com/PacktPublishing/Git-Version-Control-Cookbook-Second-Edition_tips_and_tricks.git bugHunting
$ cd bugHunting
$ git checkout bug_hunting
该 bug
位于 map.txt
文件第 38 行:
接下来是找到检测该 bug
的方法来测试不同版本。比如编译代码,执行测试等等。这里简化为定位代码中是否存在 oo
字样的行,编写测试脚本 test.sh
如下:(注:该测试脚本只适用于 Linux
环境下)
$ echo "! grep -q oo map.txt " > ../test.sh
$ chmod +x ../test.sh
为了避免测试脚本不受项目签出、编译等影响,最好将测试脚本放到 git
库外面。
小提示
经实测,测试脚本
test.sh
第一行末尾须加一个空格,否则脚本只能在Linux
环境运行成功,而Windows
下,Windows
的换行符号会与文件名test.sh
连在一起,从而导致目标文件定位失败。
接下来开始测试:
$ git bisect start
将当前版本标记为【bad】:
# mark current commit as bad
$ git bisect bad
将上一个正常版本标记为【good】:
# mark the last known good commit (master HEAD) as good
$ git bisect good master
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[7fb98059857aa0361cf14329d027573afd5a17b2] Build map part 10
启动第一轮排查:
# 1st round
$ ../test.sh; test $? -eq 0 && git bisect good || git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[de6b768ceebd451591ee41ff4708ca889bafd3f2] Build map part 15
根据提示,启动第二轮排查:
# 2nd round
$ ../test.sh; test $? -eq 0 && git bisect good || git bisect bad
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[865ff6f8a62d61e4a59f7bc1b2478aa6c71c345a] Build map part 13
再启动第三、四轮:
# 3rd round
$ ../test.sh; test $? -eq 0 && git bisect good || git bisect bad
Bisecting: 0 revisions left to test after this (roughly 1 step)
[27952d82d1f9067948b37e39b9bc4d0e6d846567] Build map part 14
# 4th round
$ ../test.sh; test $? -eq 0 && git bisect good || git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[83076f97e8312f8d59976c6373c897aef284c26d] Bugs...
此时,剩余步骤提示为 0,来到最后一轮排查:
# 5th round
$ ../test.sh; test $? -eq 0 && git bisect good || git bisect bad
83076f97e8312f8d59976c6373c897aef284c26d is the first bad commit
commit 83076f97e8312f8d59976c6373c897aef284c26d
Author: HAL 9000 <aske.olsson@switch-gears.dk>
Date: Tue May 13 09:53:45 2014 +0200
Bugs...
map.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
由此锁定引入 Bug 的那次提交(版本标识为:83076f97e8312f8d59976c6373c897aef284c26d
)。最后是状态重置:
# reset bisect, get SHA-1 (83076f9):
$ git bisect reset
Previous HEAD position was 83076f9 Bugs...
Switched to branch 'bug_hunting'
Your branch is up to date with 'origin/bug_hunting'.
$ git show 83076f9
commit 83076f97e8312f8d59976c6373c897aef284c26d
Author: HAL 9000 <aske.olsson@switch-gears.dk>
Date: Tue May 13 09:53:45 2014 +0200
Bugs...
diff --git a/map.txt b/map.txt
index 8a13f6b..1afeaaa 100644
--- a/map.txt
+++ b/map.txt
@@ -34,6 +34,6 @@ Australia:
.-./ |. : :,
/ '-._/ \_
/ ' \
- .' *: Brisbane
- .-' ;
- | |
+ .' \__/ *: Brisbane
+ .-' (oo) ;
+ | //||\\ |
演示过程的示意图如下:
注意:其实倒数第二轮时已经找到引入 bug 的版本了,但需要进一步确认 bug 是该版本自己产生的,还是其他版本传播到这里的,因此多运行了一轮。
发散
上述示例的一个弊端就是手动执行每一轮测试脚本,直到找出目标位置为止。若想让该过程自动进行,可以给 git 指定一个脚本(script
),或 makefile
,或一个测试脚本,使得二分查找自动进行。传入的脚本,会在命中目标时用返回一个为零状态(exit with a zero-status
),或者在未命中时返回一个非零状态(a non-zero status
):
# Automatic bisect
$ git bisect start HEAD master
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[7fb98059857aa0361cf14329d027573afd5a17b2] Build map part 10
# pass the test script
$ git bisect run ../test.sh
running '../test.sh'
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[de6b768ceebd451591ee41ff4708ca889bafd3f2] Build map part 15
running '../test.sh'
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[865ff6f8a62d61e4a59f7bc1b2478aa6c71c345a] Build map part 13
running '../test.sh'
Bisecting: 0 revisions left to test after this (roughly 1 step)
[27952d82d1f9067948b37e39b9bc4d0e6d846567] Build map part 14
running '../test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[83076f97e8312f8d59976c6373c897aef284c26d] Bugs...
running '../test.sh'
83076f97e8312f8d59976c6373c897aef284c26d is the first bad commit
commit 83076f97e8312f8d59976c6373c897aef284c26d
Author: HAL 9000 <aske.olsson@switch-gears.dk>
Date: Tue May 13 09:53:45 2014 +0200
Bugs...
map.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
bisect found first bad commit
$ git bisect reset
Previous HEAD position was 83076f9 Bugs...
Switched to branch 'bug_hunting'
Your branch is up to date with 'origin/bug_hunting'.
$ git show 83076f9
# ... (omitted)
11.4 使用 git blame 命令
上一节的 git bisect
适用于只知道项目有 bug
,但不知道 bug
在哪儿、是具体哪次提交引入的场景。如果知道了 bug
在代码中的位置,需要明确谁提交或改动的这行代码,就需要使用 git blame
命令。
继续以 Git-Version-Control-Cookbook-Second-Edition_tips_and_tricks
库为例:
$ git blame --date short -L 30,47 .\map.txt
7fb98059 (Dave Bowman 2014-05-13 30) Australia:
33016136 (Frank Poole 2014-05-13 31)
33016136 (Frank Poole 2014-05-13 32) _,__ .:
33016136 (Frank Poole 2014-05-13 33) Darwin <* / | \
5a5f6ef7 (Frank Poole 2014-05-13 34) .-./ |. : :,
5a5f6ef7 (Frank Poole 2014-05-13 35) / '-._/ \_
5a5f6ef7 (Frank Poole 2014-05-13 36) / ' \
83076f97 (HAL 9000 2014-05-13 37) .' \__/ *: Brisbane
83076f97 (HAL 9000 2014-05-13 38) .-' (oo) ;
83076f97 (HAL 9000 2014-05-13 39) | //||\\ |
27952d82 (Dave Bowman 2014-05-13 40) \ /
27952d82 (Dave Bowman 2014-05-13 41) | /
27952d82 (Dave Bowman 2014-05-13 42) Perth \* __.--._ /
de6b768c (Heywood R. Floyd 2014-05-13 43) \ _.' \:. |
de6b768c (Heywood R. Floyd 2014-05-13 44) >__,-' \_/*_.-'
de6b768c (Heywood R. Floyd 2014-05-13 45) Melbourne
9b49c0f0 (Heywood R. Floyd 2014-05-13 46) snd :--,
9b49c0f0 (Heywood R. Floyd 2014-05-13 47) '/
其中的 -L
表示限定考察代码的行数范围,格式为 -L <from>,<to>
。从反馈的结果可知,bug
出现在 map.txt
文件的第 37 至 39 行,由 HAL
首次引入该 bug
,对应 SHA-1
为 83076f97
,与 git bisect
结果一致。
此外,还可以指定 -M
参数,查看文件被重构或移动到某处的情况;指定 -C
参数,则可以展示目标文件从当前 commit
包含的文件中复制或移入相关代码的情况;指定 -CCC
则范围不仅限于当前 commit
,可包含所有 commit
。