Git bisect 命令解析 #1 : 基础介绍 & 案例 1 之 "线性提交"什么是 git bisect? 如何利
系列文章
引言
bisect
: 中文释义为 "把 ... 一分为二",在算法中常说的 "二分查找法" 即为 bisection method (亦有翻译为 binary search method 等)。
本系列文章的主角,git 中的一个实用命令,就使用了该词作为命令名称 ( git bisect
)
关于 bisect 的意义
- 网络有一个有意思的说法 : "工程师一生的活动可以总结成两大类 : 写 bug 和 debug"。
- 这虽然是一句调侃的话,但我们确实可以感受到,日常工作中来自众多不同形态的软件臭虫 (bug) 的深深的恶意和伤害,无论它是产生于别人的还是自己的。
- debug,确实也是我们工作中,神圣而规律的一种日常活动.
debug 工作,简单来说,无外乎就是 :
- 明确 bug 现象和复现规律
- 梳理并把控产生 bug 的范围
- 排查和进行验证,逐步找到根源
- 提出解决方案,并修复之
- 验证,并按需补一些自动测试
其中,梳理并把控范围是尤为重要的一个环节,因为每个研发同学的生命和精力都是有限的,debug 时不考虑先梳理就 "不经思索的随便改东西看看能不能解决问题",这么做就像一只无头苍蝇般扎入任意的代码片段,那么很可能燃烧了宝贵的青春但全无收获。
举个一听就懂的例子,"如果 bug 出现在 A 模块,你肯定不会优先去 B 模块看代码"。
所以,如何快速地缩小范围并最终定位到相关的问题代码呢?
我们首先要坚定一个信念 :
"特定的 bug 肯定是在特定的某个时刻,特定的某个位置开始出现的。"
( 当然,这里说的 "位置" 不仅是指代码,可能是上下游或环境等 )。
之所以要坚定这个信念,是为了避免在 debug 中由于暂时的挫折而轻易转向各类妥协的解决方案。
有一类很常见的 bug,我们称之为 "回归型缺陷" ( regression bugs ),尤为符合上述的 "特定的某个时刻,在特定的某个位置出现" 这种说法。
- 我们可以利用以下的两个特征,来大致识别一个 bug 是否为 "回归型缺陷" :
- 以前确定是没问题的
- 在某个时候突然被发现有问题
- 对于这种 bug,我们不仅可以利用各种方式来进行位置上的定位,也可以考虑在引入时间上的定位,来辅助快速地缩小出问题代码的范围。
- 因为,如果我们能找到一个特定的 Commit,它本身存在我们关注的问题,而它的 Parent Commit 没有问题的话,
- 那么这个 Commit 所产生的代码更改 ( Changeset ) 极大概率是引入 bug 的范围.
本系列的文章介绍的 git bisect
命令,正是有效的方法之一,可以 帮助我们快速定位到 "第一次引入 bug 的 Commit" 。
关于本系列的内容计划
关于 bisect 命令的系列,预计将以七篇文章来进行分享,以下为大致的内容提纲 ( 免责声明 : 在创作过程中,有一定概率会根据实际情况进行内容的调整,亦可能会涉及本提纲的变动 ) :
基础介绍
git bisect
是一个 git 命令,利用二分法查找方法,定位出首次引入bug
的 Commit.- 但更广义来说,
git bisect
可用于找到任意的符合一定要求的代码变更
的首次引入的 Commit.- 不一定要求必须在
debug
的使用场景才能运用该命令 - 例如,你可以用它来找到某个特定 feature 的首次完成开发的 Commit
- 甚至反过来,你可以用它来定位某个
bug
首次被 fix 的 Commit
- 不一定要求必须在
相关概念
根据 git 中定位 Commit 的上下文,我们明确一些相关的基础概念 :
- 代码变更 : 指的是一个特定的代码修改,由于该修改,会产生特定的代码或非代码的特征
old commit
: 不 包含这种代码变更的 commitnew commit
: 包含这种代码变更的 commit
以上的概念在 debug
的范畴中,可进一步理解如下 :
- 代码变更可理解为直接引起目标 bug 的代码变更
good commit
: 对应old commit
,不包含产生 bug 的代码变更bad commit
: 对应new commit
,包含产生 bug 的代码变更
Commit 状态的单调性
其次,二分查找法
是 git bisect
的基本思路,我们需要做一个设定,那就是 :
"commit 的 old (good) | new (bad) 状态,应在 commit 的提交顺序上,呈现单调性。"
用如下的图示所述 : ( 其中红色代表 new (bad), 绿色代表 old (good), 灰色代表 未确定 )
在线性 Commit 中
- 当一个 Commit 被识别为 new (bad) 时, 后续的 Commit 都为 new (bad)
- 当一个 Commit 被识别为 old (good) 时, 前置的 Commit 都为 old (good)
在涉及一次合并的相关 Commit 中
- 当合并 Commit 被识别为 new (bad) 时, 和线性 Commit 一样, 后续的 Commit 都为 new (bad)
- 当合并 Commit 被识别为 old (good) 时, 则其 Parent Commits 皆为 old (good)
- 当其中一个 Parent Commit 被识别为 new (bad) 时, 含合并 Commit 及其后续 Commit 都为 new (bad)
- 当其中一个 Parent Commit 被识别为 old (good) 时, 和线性 Commit 一样, 只有该 Parent Commit 的前置 Commit 为 old (good)
不合法的情况
- 相反的, 若实际存在如下的情形, 则
bisect
方法无法正确查找- 如果遇到这种情况, 要么需要重新定义 old (good) / new (bad) 的含义, 要么需要严格控制好二分查找的基础范围
- NOTE : 这个其实也是为什么
bisect
在 good 和 new 之外, 还提供了 old 和 new 的另外一套术语, 甚至允许操作者自行指定两者的术语 (terms
)
简单案例
接下来, 我们会从三个例子进行分享, 以了解 bisect
的运行过程 :
- 案例 1. 线性提交 (无合并提交)
- 案例 2. 符合规则的包含合并提交
- 案例 3. 包含回退型合并提交 ( 这种属于使用 bisect 易翻车的例子 )
Case 1. 线性提交 (无 Merge Commit)
关于这个场景, 以下为一些基础背景 :
- 以下的文字描述, 可配合下面的配图进行理解
- repo 仅有 master 分支, 共 18 个 Commit
- repo 中, 仅有一个
hello.md
文件, 代表我们关心的代码内容
- repo 中, 仅有一个
- 已明确第 5 个提交是好的 (good), 包含
IMPORTANT CODE
这行代码- 注意 : 为了便于理解, 我们暂时用 good / bad 这组 terms
- commit #5 的 hash 为 fd49...
- 并且, 已明确最后一个提交 (第 18 个提交), 是有问题, 这行关键代码不见了
- commit #18 的 hash 为 73b8...
- 这个 case 的目标是, 快速定位到 "这行代码被删除的首个提交"
额外的一个小解释 :
- 之所以要用
代码行丢失
作为 bug 依据, 是因为 :git blame
本身, 也是一个非常有价值的用于追踪问题代码的方式- 在某些能从关键代码特征推断问题的场景, 用
blame
可能也是一种思路 😉 (希望大家有更多的思路打开)- 但在
代码行丢失
的情况下,blame
相对来讲在复杂的真实提交环境下, 会略显不足
在开始之前, 我们利用线性提交这个场景, 理解一下 git bisect
是如何进行 二分查找
的, 如下图所示 :
- 设置好初始化的 good commit 和 bad commit 之后, 查找就开始了
- 这时,
git bisect
算法会自动定位到一个中位 Commit
, 我们只需要检查这个 Commit 是好是坏 - 如果是好的, 那么根据上面的规则推断, 前置的 Commit 都应该是好的, 那么
git bisect
会在剩余的一半 Commit 中, 再定位出一个中位 Commit
, 继续检查 - 如果这个是坏的, 那么说明后续的 Commit 都是坏的, 以此继续执行下去
- ... 直到
bisect
程序定位到第一个有问题的 Commit (bad commit)
bisect start
首先, 我们要先开始一次 bisect
追查 :
# git bisect start [<bad> [<good>...]]
git bisect start 73b8 fd49
# 以上的命令, 可等价于 :
git bisect start
git checkout 73b8
git bisect bad
git checkout fd49
git bisect good
可以看到, bisect
程序通过 二分法
的方式, 将当前的中位 Commit 设置在 Commit #18 与 Commit #5 之间的 Commit #11
bisect good / bad
通过查看 Commit #11 所在的代码, 可以知道 Commit #11 是好的 ( 因为包含我们想要的 IMPORTANT CODE
这行代码 )
所以, 我们告诉 bisect
这个 : Commit #11 是好的 (good), 接下来让我们看 #14
Commit #14 代码行缺失, 是坏的 (bad)
于是, 将 Commit #14 标识为 bad, 接下来要检查 Commit #13
审查知道 Commit #13 是好的,
根据 Commit #14 的实际代码提交确认, 它确实是第一个引入问题的 Commit
bisect log / replay / reset
- ok, 定位到问题之后, 我们可以利用
git bisect log
对查找过程进行打印和存底- 关于 log, 我们还可以使用
git bisect replay
这个命令, 可以很方便地复现查找过程 - 这个方式会很有用, 因为如果在中间过程若出现任何的人为失误, 可以通过导出 log, 剔除掉错误的步骤, 快速回退到之前的某个操作步骤
- 关于 log, 我们还可以使用
- 最后, 可以使用
git bisect reset
这个命令结束查找, 退回到最初所在的 commit
系列文章
转载自:https://juejin.cn/post/7079974974949179406