章節 ▾ 第二版

7.10 Git 工具 - 使用 Git 進行除錯

使用 Git 進行除錯

除了作為版本控制的主要工具外,Git 還提供了一些命令來幫助你除錯原始碼專案。由於 Git 的設計可以處理幾乎任何型別的內容,因此這些工具都相當通用,但它們在出現問題時通常可以幫助你查詢 bug 或罪魁禍首。

檔案註解

如果你發現了程式碼中的一個 bug,想知道它是何時以及為何被引入的,檔案註解通常是你最好的工具。它可以顯示修改每一行程式碼的最新提交。因此,如果你發現程式碼中的某個方法有 bug,你可以使用 git blame 命令來註解該檔案,從而確定引入該行程式碼的提交。

下面的示例使用 git blame 命令來確定 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

注意,第一欄位是最後修改該行的提交的 SHA-1 摘要。接下來的兩個欄位是從該提交中提取的值——提交者的姓名和提交日期——因此你可以輕鬆地看到是誰以及何時修改了該行。之後是行號和檔案內容。還要注意 ^1da177e4c3f4 提交行,其中 ^ 字首表示該行在倉庫的初始提交中被引入,並且自那時以來一直未被修改。這有點令人困惑,因為你現在至少看到了 Git 使用 ^ 修飾提交 SHA-1 的三種不同方式,但在這裡它的意思就是這樣。

Git 的另一個有趣之處在於它不顯式跟蹤檔案重新命名。它記錄快照,然後嘗試在事後隱式地找出哪些檔案被重新命名了。其中一個有趣的特性是,你可以要求它找出各種程式碼移動。如果你向 git blame 傳遞 -C 選項,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)

這非常有用。通常情況下,你看到的原始提交是你將程式碼複製過來的那個提交,因為那是你第一次修改該檔案中的這些行。Git 會告訴你你編寫這些行的原始提交,即使它是在另一個檔案中。

如果你一開始就知道問題出在哪裡,檔案註解會很有幫助。如果你不知道是什麼導致了問題,並且自上次程式碼正常工作的提交以來已經有幾十甚至幾百個提交,你可能會求助於 git bisectbisect 命令會在你的提交歷史中進行二分查詢,以幫助你儘可能快地找出引入問題的提交。

假設你剛剛將你的程式碼釋出到了生產環境,你收到了關於一個在開發環境中不存在的 bug 的報告,而你無法想象為什麼程式碼會這樣做。你回到你的程式碼,發現你可以重現該問題,但你無法弄清楚哪裡出了問題。你可以對程式碼進行二分查詢來找出原因。首先,執行 git bisect start 來啟動,然後使用 git bisect bad 來告訴系統你當前所在的提交是有問題的。接下來,你必須告訴 bisect 上一個已知的正常狀態是什麼,使用 git bisect good <good_commit> 命令。

$ 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 摘要,並顯示該提交的一些資訊以及該提交中修改過的檔案,以便你弄清楚是什麼可能引入了這個 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

這樣做會自動在每個檢出的提交上執行 test-error.sh,直到 Git 找到第一個有問題的提交。你也可以執行類似 makemake tests 或任何其他執行自動化測試的命令。