目录
你一定遇到过,一个很久没修改过的功能,莫名其妙的出现了问题?肉眼查代码、屡逻辑完全找不到问题点?前两天还好好的功能,怎么这个今天就不行了?这两天改动了这么多代码,到底是那一次改动引发的 Bug?
这样非崩溃的 Bug,有时候想要排查出问题,并不是一件容易的事情。我想,这个时候你会需要 git bisect !
一、git bisect 基础使用
git bisect 是 Git 提供的一种 二分法 的调试工具,它可以按照我们选定的提交,进行二分分割,快速定位出出错的提交。以帮我们缩小最小改动的代码,从而快速定位问题。
git bisect 其实很简单,主要是基于几个基本命令:
- git bisect start:准备进行 bisect debug。
- git bisect good:标记一个提交为 “good”。
- git bisect bad:标记一个提交为 “bad”。
- git bisect reset:退出 bisect debug 的状态。
git bisect 涉及到的命令,非常的清晰简单,下面举个实际的例子,结合上面的解释,就更清晰了。
二、git bisect 工作流
我自己造出 6 个 commit,然后使用 git log
看看我的提交记录。
commit 4d5107b5caaaf3699584e1bff0152ea2803ceaac (HEAD -> master)
Author: test <[email protected]>
Date: Fri Oct 8 21:07:36 2021 +0800
v6
commit 7273afef89960ad1c98ab3bc9862f5cd785b8310
Author: test <[email protected]>
Date: Fri Oct 8 21:07:23 2021 +0800
v5
commit 18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a
Author: test <[email protected]>
Date: Fri Oct 8 21:07:10 2021 +0800
v4
commit defdecea184285befc05884e2caa1b4c645a2779
Author: test <[email protected]>
Date: Fri Oct 8 21:06:58 2021 +0800
v3
commit cdf8ca98bdc9b853a7c01c266a6b2377b967dd21
Author: test <[email protected]>
Date: Fri Oct 8 21:05:10 2021 +0800
v2
commit b0e81c1529565abd77cc89805cdaff364eff52f8
Author: test <[email protected]>
Date: Fri Oct 8 21:04:03 2021 +0800
v1
这里假设我正常开发的阶段,到 v6 提交的时候,突然发现有个 Bug ,无法定位到问题,但是能明确的知道,在 v1 提交阶段,并没有这个 Bug。
那么,在这样的情况下,v6 就是一个有问题的版本,而 v1 则是一个好的版本,我们就可以借助 git bisect
来进行二分超找定位问题来自哪个提交。
还记得刚才的命令吗?
我们先用 git bisect start
标记开始 bisect debug ,然后使用 git bisect good
和 git bisect bad
分别标记出正确的和错误的提交。
➜ gitbisect git:(master) git bisect start
➜ gitbisect git:(master) git bisect good b0e81c15
➜ gitbisect git:(master) git bisect bad HEAD
二分查找中:在此之后,还剩 2 个版本待测试 (大概 1 步)
[defdecea184285befc05884e2caa1b4c645a2779] v3
每个提交,都有一个针对这个提交唯一的 SHA-1 值,因为太长不方便输入和阅读,这里可以直接使用前几位作为简写。
当标记出正确的和错误的提交之后,git bisect
立刻就可以帮我们定位出中间提交 v3。
现在 HEAD 就已经指向了 v3 提交的代码了,这个可以使用 git status
查看当前的状态。
➜ gitbisect git:(defdece) git status
头指针分离于 defdece
您在执行从分支 'master' 开始的二分查找操作。
(使用 "git bisect reset" 以回到原有分支)
无文件要提交,干净的工作区
所以我们可以基于 v3 版本的代码,直接运行项目,测试看该提交是否有问题。
经过测试之后,发现 v3 的提交代码版本,并没有复现 Bug,那我们就可以缩小错误提交的范围,大概落在了 v4、v5、v6 之间。
此时,我们只需要使用 git good
标记 v3 版本是正确的。
➜ gitbisect git:(defdece) git bisect good
二分查找中:在此之后,还剩 0 个版本待测试 (大概 1 步)
[7273afef89960ad1c98ab3bc9862f5cd785b8310] v5
标记好 v3 为 good 之后,立刻又会进行一次二分,此次标记的为中间提交 v5。
经过对 v5 提交的版本代码,进行测试之后发现,它是有问题的。我们继续使用 git bisect bad
对它进行标记。
➜ gitbisect git:(7273afe) git bisect bad
二分查找中:在此之后,还剩 0 个版本待测试 (大概 0 步)
[18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a] v4
当 v5 有问题的时候,现在只中间一个 v4 版本,所以会立刻指向 v4 提交。
我们继续对 v4 版本的代码进行测试,发现 v4 版本也有问题,继续标记它为 bad 。
➜ gitbisect git:(18ea19f) git bisect bad
18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a is the first bad commit
commit 18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a
Author: test <[email protected]>
Date: Fri Oct 8 21:07:10 2021 +0800
v4
1.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
此时就很明确了,出错的提交就是 v4,而 Git 也直接帮我们指出来出错的提交。
虽然这里定位到,出错的提交就是 v4 的问题,我们只需要仔细阅读 v4 提交的代码,然后定位出问题代码,就达到了我们的目的。但是我们并不应该在 v4 提交上直接修改 Bug,我们应该退出 bisect debug 状态,在最新的提交版本上进行修改,这里使用 git bisect reset
退出,再进行修改即可。
到这里,就是 git bisect
的完整工作流程。
三、bisect的后悔药
对提交进行 good 和 bad 的标记,都是人为来进行的,难免有出错的情况。而提交比较少的时候,大不了就是 reset 之后,重头来过。
但是如果有几十个提交,再从头进行一次 bisect 就比较麻烦了。Git 考虑到这一点,已经为我们配好了后悔药。
想要擦除之前的标记状态,就涉及到一个命令来重置到某个状态:
git bisect replay
replay 需要制定一个回退的点,这个点是需要使用 git bisect log > log.txt
输出的 Log 文件, 我们需要通过修改这个 Log 文件,来确定回退的点。
举个例子,我们使用 log 命令,输出一个 log.txt 文件。
git bisect start
# good: [b0e81c1529565abd77cc89805cdaff364eff52f8] v1
git bisect good b0e81c1529565abd77cc89805cdaff364eff52f8
# bad: [4d5107b5caaaf3699584e1bff0152ea2803ceaac] v6
git bisect bad 4d5107b5caaaf3699584e1bff0152ea2803ceaac
# good: [defdecea184285befc05884e2caa1b4c645a2779] v3
git bisect good defdecea184285befc05884e2caa1b4c645a2779
# bad: [7273afef89960ad1c98ab3bc9862f5cd785b8310] v5
git bisect bad 7273afef89960ad1c98ab3bc9862f5cd785b8310
# bad: [18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a] v4
git bisect bad 18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a
# first bad commit: [18ea19f80cc2b3c8a4c75d8c4e3d5ee61b58276a] v4
可以看到,这个 log.txt 文件,记录了我们刚才所有的操作。
在这个例子中,假如我们的操作,对 v5 进行 bad 的这个标记错了,那么,我们把这个操作之下的 Log 全部删除掉,然后执行 git bisect replay log.txt
。
➜ gitbisect git:(18ea19f) ✗ git bisect replay log.txt
之前的 HEAD 位置是 18ea19f v4
切换到分支 'master'
二分查找中:在此之后,还剩 0 个版本待测试 (大概 1 步)
[7273afef89960ad1c98ab3bc9862f5cd785b8310] v5
这样就将回退到判断 v5 提交好坏的地方,重新进行标记。
在修改 Log.txt 文件的时候,最好只执行删除操作,不要对其中的顺序有所修改,毕竟我们只是想要一个回滚的动作,并不是要改动我们之前的某些操作。
以上来自 https://www.cnblogs.com/plokmju/p/8072971.html
## 四、拓展延伸
最近在项目中遇到了一个这样的场景:
我们的产品在旧版本上有一个 bug,而这个 bug 在新版本上却又消失了。问了组内的其他成员,没有人专门针对这个 bug 进行过修复。这就说明这个 bug 是被无意间修复的,本着负责的态度还是要查一下这个 bug 的原因以及是怎么被修复的。
那么怎么排查呢?我首先想到的当然是用 git bisect 快速定位是哪个 commit 修复的,于是把旧版本的一个 commit 标记为 bad,把当前版本的一个 commit 标记为 good,但是 git 提示如下信息:
Some good revs are not ancestors of the bad rev.
// 一些 good 版本不是 bad 版本的祖先。
git bisect cannot work properly in this case.
// 这种情况下 git 二分查找无法正常工作。
Maybe you mistook good and bad revs?
// 您可能弄错了 good 和 bad 版本?
当看到上面的信息时,第一反应是我把 good 和 bad 版本搞错了,但是经过检查后,发现并没有搞错,那 git 为什么会提示我搞错了呢?
于是我又温习了一遍 git bisect 的使用方法,终于找到了问题的原因。
git bisect 设计的初衷是定位从哪个 commit 开始出现的 bug,所以在 git 的提交历史中,被认为是 good 的 commit 必然在被认为是 bad 的 commit 之前(也就是上面提示信息中 good 版本是 bad 版本的祖先);但是我这次使用的场景是用来定位哪个 commit 把 bug 给修复了,所以 good 版本不是 bad 版本的祖先,正好是相反的关系。
git 提示错误的原因找到了,于是我思路一转,想到了该怎么利用 git bisect 来解决我的问题。
解决办法就是“反向操作”:把代码能够正常运行没有 bug 的 commit 标记为 bad,把有问题的 commit 标记为 good,这样问题就迎刃而解了。
从这次遇到的问题中也体现出了,对知识要熟练掌握与灵活运用的重要性。