Git bisect 二分法定位问题

你一定遇到过,一个很久没修改过的功能,莫名其妙的出现了问题?肉眼查代码、屡逻辑完全找不到问题点?前两天还好好的功能,怎么这个今天就不行了?这两天改动了这么多代码,到底是那一次改动引发的 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 goodgit 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,那我们就可以缩小错误提交的范围,大概落在了 v4v5v6 之间。

此时,我们只需要使用 git good 标记 v3 版本是正确的。

➜  gitbisect git:(defdece) git bisect good
二分查找中:在此之后,还剩 0 个版本待测试 (大概 1 步)
[7273afef89960ad1c98ab3bc9862f5cd785b8310] v5

标记好 v3good 之后,立刻又会进行一次二分,此次标记的为中间提交 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的后悔药

对提交进行 goodbad 的标记,都是人为来进行的,难免有出错的情况。而提交比较少的时候,大不了就是 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,这样问题就迎刃而解了。

从这次遇到的问题中也体现出了,对知识要熟练掌握与灵活运用的重要性。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注