24、Git疑难杂症(二):git bisect、git blame、git grep
好,咱们接着聊Git里的疑难杂症。上一章我们讲了怎么找回丢失的数据,这一章我带你看看另外三个非常实用的工具:git bisect、git blame 和 git grep。
这三个工具,说白了就是帮你回答三个问题:
- 这个bug是什么时候引入的?
- 这行代码是谁写的?
- 这段代码在项目里哪里出现过?
嗯,听起来很简单对吧?但实际用起来,你会发现它们能救你于水火之中。
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