章节 ▾ 第二版

7.10 Git 工具 - 使用 Git 进行调试

使用 Git 进行调试

除了主要用于版本控制外,Git 还提供了一些命令来帮助你调试源代码项目。 因为 Git 被设计用来处理几乎任何类型的内容,所以这些工具非常通用,但它们通常可以帮助你在出现问题时查找错误或罪魁祸首。

文件注释

如果你在代码中找到了一个 bug,并想知道它是什么时候引入的以及为什么,文件注释通常是你最好的工具。 它会显示你每个文件的每一行最后一次被修改的 commit。 因此,如果你看到代码中的一个方法有 bug,你可以用git blame来注释该文件,以确定哪个 commit 负责引入该行。

下面的例子使用git blame来确定哪个 commit 和提交者负责 Linux 内核顶级Makefile中的行, 此外,使用-L选项将注释的输出限制在该文件的第 69 行到第 82 行。

$ git blame -L 69,82 Makefile
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 69) ifeq ("$(origin V)", "command line")
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 70)   KBUILD_VERBOSE = $(V)
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 71) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 72) ifndef KBUILD_VERBOSE
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 73)   KBUILD_VERBOSE = 0
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 74) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 75)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 76) ifeq ($(KBUILD_VERBOSE),1)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 77)   quiet =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 78)   Q =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 79) else
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 80)   quiet=quiet_
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 81)   Q = @
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 82) endif

请注意,第一个字段是最后一次修改该行的 commit 的部分 SHA-1。接下来的两个字段是从该 commit 中提取的值 — 作者姓名和该 commit 的创作日期 — 这样你就可以很容易地看到谁修改了该行以及何时修改的。 在那之后是行号和文件的内容。 另请注意^1da177e4c3f4提交行,其中^前缀表示在仓库的初始提交中引入的行,并且此后一直没有更改。 这有点令人困惑,因为现在你已经看到了 Git 使用^修改 commit SHA-1 的至少三种不同的方式,但这就是它在这里的含义。

关于 Git 的另一个很酷的事情是它不显式地跟踪文件重命名。 它记录快照,然后在事后尝试找出哪些文件被隐式重命名。 这样做的一个有趣的功能是你可以要求它找出各种代码移动。 如果你将-C传递给git blame,Git 会分析你正在注释的文件,并尝试找出其中代码片段最初来自哪里(如果它们是从其他地方复制的)。 例如,假设你正在将名为GITServerHandler.m的文件重构为多个文件,其中一个是GITPackUpload.m。 通过使用-C选项来 blame GITPackUpload.m,你可以看到代码的各个部分最初来自哪里

$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2009-01-04 141)
f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2009-01-04 143) {
70befddd GITServerHandler.m (Scott 2009-03-22 144)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 145)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 146)         NSString *parentSha;
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 147)         GITCommit *commit = [g
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 148)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 149)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 150)
56ef2caf GITServerHandler.m (Scott 2009-01-05 151)         if(commit) {
56ef2caf GITServerHandler.m (Scott 2009-01-05 152)                 [refDict setOb
56ef2caf GITServerHandler.m (Scott 2009-01-05 153)

这真的很有用。 通常,你得到作为原始 commit 的是你复制代码的 commit,因为那是你第一次接触该文件中的那些行。 Git 会告诉你编写这些行的原始 commit,即使它在另一个文件中。

如果你知道问题在哪里,对文件进行注释会很有帮助。 如果你不知道哪里出了问题,并且自从你确定代码可以工作的最后状态以来已经提交了数十甚至数百次,那么你很可能会求助于 git bisect 来寻求帮助。 bisect 命令通过你的提交历史记录进行二分查找,以帮助你尽可能快地识别出哪个提交引入了问题。

假设你刚刚将代码发布到生产环境,你收到了关于某些在你的开发环境中没有发生的问题的错误报告,并且你无法想象代码为什么会这样做。 你回到你的代码中,发现你可以重现这个问题,但你无法弄清楚哪里出了问题。 你可以bisect代码来找出答案。 首先,运行 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 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,并显示一些提交信息以及在该提交中修改了哪些文件,以便你可以弄清楚发生了什么可能引入了这个错误

$ 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

这是一个强大的工具,可以帮助你在几分钟内检查数百个提交中引入的错误。 事实上,如果你的脚本在项目良好时退出 0,在项目错误时退出非 0,你可以完全自动化 git bisect。 首先,你再次通过提供已知的坏提交和好提交来告诉它 bisect 的范围。 如果你愿意,你可以通过使用 bisect start 命令列出它们,首先列出已知的坏提交,然后列出已知的好提交

$ git bisect start HEAD v1.0
$ git bisect run test-error.sh

这样做会自动在每个检出的提交上运行 test-error.sh,直到 Git 找到第一个损坏的提交。 你也可以运行类似 makemake tests 之类的东西,或者任何为你运行自动化测试的东西。

scroll-to-top