Git之深入解析如何使用Git调试项目源码中的问题

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git之深入解析如何使用Git调试项目源码中的问题相关的知识,希望对你有一定的参考价值。

一、前言

二、文件标注

  • 如果在追踪代码中的一个 bug,并且想知道是什么时候以及为何会引入,文件标注通常是最好用的工具,它能显示任何文件中每行最后一次修改的提交记录。所以,如果在代码中看到一个有 bug 的方法,可以使用 git blame 标注这个文件,查看哪一次提交引入了这行。
  • 如下所示,用 git blame 确定了 Linux 内核源码顶层的 Makefile 中每一行分别来自哪个提交和提交者,此外用 -L 选项还可以将标注的输出限制为该文件中的第 69 行到第 82 行:
$ git blame -L 69,82 Makefile
b8b0618cf6fab (Cheng Renquan  2019-05-26 16:03:07 +0800 69) ifeq ("$(origin V)", "command line")
b8b0618cf6fab (Cheng Renquan  2019-05-26 16:03:07 +0800 70)   KBUILD_VERBOSE = $(V)
^1da177e4c3f4 (Linus Torvalds 2020-04-16 15:20:36 -0700 71) endif
^1da177e4c3f4 (Linus Torvalds 2020-04-16 15:20:36 -0700 72) ifndef KBUILD_VERBOSE
^1da177e4c3f4 (Linus Torvalds 2020-04-16 15:20:36 -0700 73)   KBUILD_VERBOSE = 0
^1da177e4c3f4 (Linus Torvalds 2020-04-16 15:20:36 -0700 74) endif
^1da177e4c3f4 (Linus Torvalds 2020-04-16 15:20:36 -0700 75)
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 76) ifeq ($(KBUILD_VERBOSE),1)
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 77)   quiet =
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 78)   Q =
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 79) else
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 80)   quiet=quiet_
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 81)   Q = @
066b7ed955808 (Michal Marek   2020-07-04 14:29:30 +0200 82) endif
  • 请注意,第一个字段是最后一次修改该行的提交的部分 SHA-1 值。接下来两个字段的值是从提交中提取出来的,作者的名字以及提交的时间,所以就可以很轻易地知道是谁在什么时候修改了那一行。然后就是行号和文件内容,注意一下 ^1da177e4c3f4 这个提交的几行,其中的前缀 ^ 指出了该文件自第一次提交后从未修改的那些行。这会带来小小的困惑,因为目前至少已经看到三种 Git 使用 ^ 来修饰一个提交的 SHA-1 值的不同含义,但这里确实就是这个意思。
  • 另一件比较酷的事情是 Git 不会显式地记录文件的重命名,它会记录快照,然后在事后尝试计算出重命名的动作,这其中有一个很有意思的特性就是可以让 Git 找出所有的代码移动,如果在 git blame 后面加上一个 -C,Git 会分析正在标注的文件,并且尝试找出文件中从别的地方复制过来的代码片段的原始出处。比如,将 GITServerHandler.m 这个文件拆分为数个文件,其中一个文件是 GITPackUpload.m,对 GITPackUpload.m 执行带 -C 参数的 blame 命令,就可以看到代码块的原始出处:
$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2019-01-04 141)
f344f58d GITServerHandler.m (Scott 2019-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2019-01-04 143) {
70befddd GITServerHandler.m (Scott 2019-03-22 144)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2019-03-24 145)
ad11ac80 GITPackUpload.m    (Scott 2019-03-24 146)         NSString *parentSha;
ad11ac80 GITPackUpload.m    (Scott 2019-03-24 147)         GITCommit *commit = [g
ad11ac80 GITPackUpload.m    (Scott 2019-03-24 148)
ad11ac80 GITPackUpload.m    (Scott 2019-03-24 149)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2019-03-24 150)
56ef2caf GITServerHandler.m (Scott 2019-01-05 151)         if(commit) {
56ef2caf GITServerHandler.m (Scott 2019-01-05 152)                 [refDict setOb
56ef2caf GITServerHandler.m (Scott 2019-01-05 153)
  • 这个功能很有用,通常来说,我们会认为复制代码过来的那个提交是最原始的提交,因为那是第一次在这个文件中修改了这几行。但 Git 会告诉我们第一次写这几行代码的那个提交才是原始提交,即使这是在另外一个文件里写的。

三、二分查找

  • 当知道问题是在哪里引入的情况下文件标注可以查找问题,如果不知道哪里出了问题,并且自从上次可以正常运行到现在已经有数十个或者上百个提交,这个时候就可以使用 git bisect 来帮助查找。bisect 命令会对你的提交历史进行二分查找来帮助我们尽快找到是哪一个提交引入了问题。
  • 假设刚刚在线上环境部署了我们的代码,接着收到一些 bug 反馈,但这些 bug 在之前的开发环境里没有出现过,这就百思不得其解,我们重新查看了代码,发现这个问题是可以被重现的,但是不知道哪里出了问题。我们可以用“二分法”来找到这个问题。
  • 首先执行 git bisect start 来启动,接着执行 git bisect bad 来告诉系统当前所在的提交是有问题的,然后必须使用 git bisect good <good_commit>,告诉 bisect 已知的最后一次正常状态是哪次提交:
$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repo
  • Git 发现在标记为正常的提交(v1.0)和当前的错误版本之间有大约 12 次提交,于是 Git 检出中间的那个提交。 现在可以执行测试,看看在这个提交下问题是不是还是存在,如果还存在,说明问题是在这个提交之前引入的;如果问题不存在,说明问题是在这个提交之后引入的。假设测试结果是没有问题的,可以通过 git bisect good 来告诉 Git,然后继续寻找:
$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] secure this thing
  • 现在在另一个提交上了,这个提交是刚刚那个测试通过的提交和有问题的提交的中点,我们再一次执行测试,发现这个提交下是有问题的,因此可以通过 git bisect bad 告诉 Git:
$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] drop exceptions table
  • 这个提交是正常的,现在 Git 拥有的信息已经可以确定引入问题的位置在哪里,它会告诉我们第一个错误提交的 SHA-1 值并显示一些提交说明,以及哪些文件在那次提交里被修改过,这样就可以找出引入 bug 的根源:
$ git bisect good
b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit
commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04
Author: PJ Hyett <pjhyett@example.com>
Date:   Tue Jan 27 14:48:32 2009 -0800

    secure this thing

:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730
f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M  config
  • 当完成这些操作之后,我们应该执行 git bisect reset 重置你的 HEAD 指针到最开始的位置,否则会停留在一个奇怪的状态:
$ git bisect reset
  • 这是一个可以帮助我们在几分钟内从数百个提交中找到 bug 的强大工具。事实上,如果有一个脚本在项目是正常的情况下返回 0,在不正常的情况下返回非 0,可以使 git bisect 自动化这些操作。首先,设定好项目正常以及不正常所在提交的二分查找范围,可以通过 bisect start 命令的参数来设定这两个提交,第一个参数是项目不正常的提交,第二个参数是项目正常的提交:
$ git bisect start HEAD v1.0
$ git bisect run test-error.sh
  • Git 会自动在每个被检出的提交里执行 test-error.sh,直到找到项目第一个不正常的提交,也可以执行 make 或者 make tests 或者其他东西来进行自动化测试。

以上是关于Git之深入解析如何使用Git调试项目源码中的问题的主要内容,如果未能解决你的问题,请参考以下文章

GitHub之深入解析如何将项目迁移到Git

Github之深入解析如何在托管在不同系统的项目上使用Git客户端

Git之深入解析如何替换数据库中的Git对象

Git之深入解析如何解决.git目录过大的问题

Git之深入解析如何在应用中嵌入Git

Git之深入解析如何借助Git的配置方法和钩子机制来自定义Git需求