24、Git疑难杂症(二):git bisect、git blame、git grep

好,咱们接着聊Git里的疑难杂症。上一章我们讲了怎么找回丢失的数据,这一章我带你看看另外三个非常实用的工具:git bisectgit blamegit grep

这三个工具,说白了就是帮你回答三个问题:

  • 这个bug是什么时候引入的?
  • 这行代码是谁写的?
  • 这段代码在项目里哪里出现过?

嗯,听起来很简单对吧?但实际用起来,你会发现它们能救你于水火之中。

Git 疑难杂症(二)核心工具 git bisect 二分查找bug引入点 git blame 逐行代码追溯 git grep 代码内容搜索 自动二分查找 git bisect start / bad / good 手动标记 git bisect run <script> 查看文件每一行 git blame file.txt 指定行范围 git blame -L 10,20 file.txt 基本搜索 git grep "function_name" 正则匹配 git grep -E "pattern"

24.1 git bisect:二分查找bug的引入点

先说说 git bisect。这玩意儿我特别喜欢,因为它能帮你省下大量排查bug的时间。

想象一下这个场景:你发现项目里有个bug,但不知道是哪个提交引入的。项目有几百个提交,你总不能一个一个去试吧?

git bisect 就是干这个的。它用二分查找算法,快速定位到引入bug的那个提交。

核心思路:你告诉Git一个「好的」提交(没bug的版本)和一个「坏的」提交(有bug的版本),Git就会自动在中间切来切去,让你测试。每次你告诉它当前版本是好是坏,它就会缩小范围,直到找到罪魁祸首。

具体用法是这样的:

# 开始二分查找
git bisect start

# 标记当前版本为坏
git bisect bad

# 标记一个已知的好版本
git bisect good v1.0

# Git会切到一个中间版本,你测试后标记
git bisect good   # 如果这个版本没问题
git bisect bad    # 如果这个版本有问题

# 重复直到找到第一个坏提交
# 最后Git会告诉你:xxx是第一个坏提交

我记得有一次线上出了个性能问题,排查了两天没找到原因。后来我用 git bisect,只花了20分钟就定位到了——是一个同事不小心提交了一个死循环的测试代码。嗯,从那以后我就养成了习惯,遇到诡异bug先跑 bisect。

小技巧:如果测试过程可以自动化,用 git bisect run <script> 让它全自动跑。脚本返回0表示好,返回1-127表示坏。我经常写个简单的shell脚本,一晚上跑完,第二天直接看结果。

注意:bisect 会切换你的工作目录,所以开始之前一定要确保工作区是干净的(没有未提交的修改)。我吃过这个亏,改了一半的代码被覆盖了,差点没哭出来。

24.2 git blame:逐行代码追溯

接下来是 git blame。这名字听着有点吓人,其实它的作用就是告诉你「每一行代码是谁在什么时候写的」。

用法很简单:

# 查看整个文件
git blame app.js

# 只看某几行
git blame -L 50,70 app.js

# 只看某几行(从第50行开始看20行)
git blame -L 50,+20 app.js

输出大概是这样的:

^abc1234 (张三 2024-01-15 10:30:00 +0800 50) function login() {
^def5678 (李四 2024-01-20 14:20:00 +0800 51)   const user = getUser();
^abc1234 (张三 2024-01-15 10:30:00 +0800 52)   return user;

每一行前面都标了:提交哈希、作者、日期、行号、代码内容。

我个人习惯在code review的时候用 git blame 看看某段代码的历史。有时候你会发现一段看起来很奇怪的代码,blame一下发现是三个月前为了修一个紧急bug临时加的,后来就没人管了。

避坑指南:我曾经遇到过一个情况,blame出来的作者是「我」,但我完全不记得写过那行代码。后来才发现,是因为那次重构时我用了全局替换,把别人的代码格式化了。所以 blame 只能告诉你「谁最后修改了这行」,不一定是「谁最初写了这行」。这个区别很重要。

另外,git blame 还有一些高级用法:

  • git blame -C:检测代码移动,如果某行是从别处复制过来的,会显示原始出处
  • git blame -w:忽略空白字符的修改,只看实际代码变化
  • git blame -M:检测同一文件内的代码移动

你想想看,这三个参数组合起来,基本就能还原一段代码的「前世今生」了。

24.3 git grep:代码内容搜索

最后一个是 git grep。你可能觉得「这不就是 grep 吗?我用系统自带的 grep 不就行了?」

嗯,区别在于 git grep 只搜索Git跟踪的文件,不会去翻 node_modules.git 这些乱七八糟的目录。而且它支持很多好用的选项。

# 基本搜索
git grep "function_name"

# 显示行号
git grep -n "function_name"

# 统计匹配次数
git grep -c "function_name"

# 只显示文件名
git grep -l "function_name"

# 正则搜索
git grep -E "pattern[0-9]+"

# 搜索时忽略大小写
git grep -i "function_name"

我在项目中经常用它来查找某个函数或变量的引用。比如你要重构一个函数,想知道它被哪些文件调用了,直接 git grep "old_function_name" 就能列出来。

进阶用法:你可以指定搜索某个历史版本中的内容。比如 git grep "bug" v1.0 会搜索 v1.0 标签那个版本里的所有文件。这个在排查历史问题时特别有用。

还有一个我常用的组合:

# 搜索所有 .js 文件中包含 "TODO" 的行
git grep -- "TODO" -- "*.js"

# 搜索所有文件中包含 "FIXME" 的行,并显示行号
git grep -n "FIXME"

说白了,git grep 就是Git自带的、针对版本库优化的搜索工具。它比系统自带的 grep 更懂Git,也更安全——不会误搜到不该搜的文件。

小结

这三个工具,我建议你平时多练练手。遇到bug先跑 bisect,看到可疑代码先 blame,需要搜索代码就用 grep。用熟了之后,你会发现排查问题的效率能提升好几倍。

嗯,Git的疑难杂症系列还有不少内容,咱们后面接着聊。


公众号:蓝海资料掘金营,微信deep3321