讓 Git Bisect 幫助你

edithfang發表於2014-12-09
Bisect 基於二分查詢演算法。給定一個有序的元素序列,它會返回要你要查詢的元素的序號(或者告訴你該元素是否在序列中)。它基於如下的不變式:當你對比完一個元素,你就能根據比較的結果拋棄掉它之前或者之後的所有元素(從而大大縮小下次查詢的範圍)。

如果你有去過圖書館,那你可能注意到了每個專區內的書籍都是按作者姓名來排序的。比方說你要找的一本書的作者的姓為 Martin,而你剛剛看到某個書架上的最後一本書的作者的姓是 F 開頭的,那麼你就能確定你要找的書一定放在後面的某個書架上。依此方法繼續下次,你就能快速的縮小搜尋範圍直到找到你要的書為止。 

二分查詢法亦是如此。如果你想在有 n 個元素的序列(有序的)中查詢元素 x,你挑出第 n/2 個元素並將其與元素 x 比較。如果 x 大,那麼就對從 n/2+1 到 n 的子序列重複上述步驟,反之,就對從 1 到 n/2-1 的子序列重複上述步驟, 這樣一直遞迴下去。這裡就有一個 關於此演算法的有趣的演示。二分查詢演算法所需的比較次數通常都比你傻傻的去拿序列中的每個元素與 x 比較所需的比較次數少得多。事實證明在有序序列中查詢元素,二分查詢更快更有用。 

Bisect

Bisect 就是利用二分查詢發來查詢在你的某一分支中到底是哪一次提交引入了特定的變更。 

首先,也是最起碼的,你得有辦法檢測出一個給定的提交點是不是有 bug。通常你可以寫個測試程式碼,如果這也不可能的話,那至少你得有一套步驟來使 bug 再現出來。 有了這套檢測方法,你就可以開始用 bisect 查詢你的提交歷史來以最快的速度發現引入 bug 的時間點了。等等,你還得準備好兩個提交點:一個是你確定 bug 還沒有被引入的提交點,另一個則是確定 bug 已經引入了的提交點(這樣就縮小了初始的查詢範圍)。在 bisect 命令上,你可以用雜湊值(hash)或者標籤(tag)來引用這兩個提交點。bisect 查詢時,查詢範圍裡的提交點要麼被標記為好的(通過測試,沒有 bug 的),要麼被標記為壞的(通不過測試,有 bug 的)。

   



上圖演示了 bisect 的執行步驟(綠點是好的提交點,紅點的就是壞的):bisect 被要求在提交點 1 到 8 這個範圍內查詢首次引入 bug 的提交點(看圖一目瞭然是提交點 6),這裡提交點 1 和 8 就是上段中提及的,我們需要首先準備好給 bisect 命令的兩個(一好一壞)提交點。bisect 首先用呼叫者提供的檢測方法來測試 1 到 8 中間的提交點 4,如果它是好的(圖上的情況),那麼 bisect 就縮小範圍為它右邊的區域,重複上述步驟,反之,則選擇其左邊的區域為新的搜尋範圍。如此遞迴下去,在上圖的例子中,只需要 3 步就確定了罪魁禍首是提交點 6.

我這裡建立了一個 git 倉庫,裡面共有 1024 個提交記錄,每個提交都往一個文字檔案後面新增一個遞增的數字,從 1 到 1024,一行一個數字。我們的任務是要找出是哪個提交點向文字檔案附加了 1013 這個數字(我們假定它就是一個 bug)。 

首先,我們定出一個方法來判斷檔案裡面有沒有這個數字。這就是一個測試了。方法很簡單:

[        ubbcodeplace_1        ]nbsp;grep 1013 file.txt


有了這條命令,我們就可以開始 bisect 了:

[        ubbcodeplace_2        ]nbsp;git bisect start


對 master 分支(的頭部提交點)執行這個測試,沒有得到想要的結果(就是沒有任何輸出),所以我們把它標記為壞的。

[        ubbcodeplace_3        ]nbsp;git bisect bad


現在讓我們指定一個好的提交點:假設第一個提交點(7c0dcfa)是沒有 bug 的。我們可以檢出(check out)這個提交點並標記它為好的提交點(git bisect good + 該提交點的雜湊值)。

[        ubbcodeplace_4        ]nbsp;git bisect good 7c0dcfa 
Bisecting: 511 revisions left to test after this (roughly 9 steps) 
[8950f7db7e7cad0b2dc394ff9b75fc3d38c9d72a] added 512


也可以用下面的命令,完成上面的所有步驟:

[        ubbcodeplace_5        ]nbsp;git bisect start master 7c0dcfa


git bisect start 命令可以接受兩個引數,第一個是壞的提交點,第二是好的提交點。很好,git bisect 自動檢出了正中間的提交點,執行我們的檢測命令,沒有任何的輸出(沒有 bug),因此我們把該提交點標記為好的。

[        ubbcodeplace_6        ]nbsp;grep 1013 file.txt 
 
[        ubbcodeplace_6        ]nbsp;git bisect good 
Bisecting: 剩餘 255 個修訂待測試 (大概還需要 8 步) 
[a01ba83f3500b48da97c5f5c33052623aaa4161a] added 768


譯者注:按照二分查詢演算法,bisect 根據你標記的結果,決定是檢出左半邊的中間提交點(如果你標記為 bad)還是右半邊的中間提交點(如果你標記為 good) 

在 bisect 新檢出的提交點上,我們再次執行測試命令。這次依然是個好的提交點。

[        ubbcodeplace_7        ]nbsp;grep 1013 file.txt 
 
[        ubbcodeplace_7        ]nbsp;git bisect good 
Bisecting:剩餘 127 個修訂待測試 (大概還需要 7 步)
[4a4a668bf3363d09af5fd1906bc4272aacdb4495] added 896


再次檢測,還是好的提交點。

[        ubbcodeplace_8        ]nbsp;grep 1013 file.txt 
 
[        ubbcodeplace_8        ]nbsp;git bisect good 
Bisecting: 剩餘 63 個修訂待測試 (大概還需要 6 步) 
[9059c5b8b898159e8d1d797bff3b1febd1fd6a1c] added 960


讓我們看看這些訊息:除了告訴你當前新檢出的提交點以及新的搜尋範圍內有多少個待測試的提交點外,它還估計出你最多還需要重複多少次測試命令就能找到你要的提交點。這次又是一個好的提交點。

[        ubbcodeplace_9        ]nbsp;grep1013 file.txt
 
$ git bisect good
Bisecting: 剩餘 31 個修訂待測試 (大概還需要 5 步)
[0c844d0b33ef297b742206ebc293f4925705b083] added 992


繼續,依然是一個好的提交點。

[        ubbcodeplace_10        ]nbsp;grep 1013 file.txt 
 
[        ubbcodeplace_10        ]nbsp;git bisect good 
Bisecting: 剩餘 15 個修訂待測試 (大概還需要 4 步) 
[0ee17eb17bd96b321a01c73eb13a8929a68b1239] added 1008


還有 4 步,這次依然 good。

[        ubbcodeplace_11        ]nbsp;grep 1013 file.txt 
 
[        ubbcodeplace_11        ]nbsp;git bisect good 
Bisecting: 剩餘 7 個修訂待測試 (大概還需要 3 步) 
[dfb1e71736dcfffa2a30aecd7299f45f757c057e] added 1016


這次測試命令終於有了輸出,bug 現身了!我們把這個提交點標記為壞的。

[        ubbcodeplace_12        ]nbsp;grep 1013 file.txt 
1013
[        ubbcodeplace_12        ]nbsp;git bisect bad 
Bisecting: 剩餘 3 個修訂待測試 (大概還需要 2 步) 
[6e6d08c374df5162fed65fed82859b69f86b936e] added 1012


終點近在咫尺,測試通過,我們把它標記為好的。

[        ubbcodeplace_13        ]nbsp;grep 1013 file.txt 
 
[        ubbcodeplace_13        ]nbsp;git bisect bad 
Bisecting: 剩餘 1 個修訂待測試 (大概還需要 1 步) 
254efa859d7fc66f1f58a59f0] added 1014


壞的提交點。

[        ubbcodeplace_14        ]nbsp;grep 1013 file.txt 
1013
[        ubbcodeplace_14        ]nbsp;git bisect bad 
Bisecting: 剩餘 0 個修訂待測試 (大概還需要 0 步) 
8e16d98ec7039a7c53855dd9ed6] added 1013


最後一步,這次是壞的。

$ git bisect bad 
458eab0eb8d808e16d98ec7039a7c53855dd9ed6 is the first bad commit
commit 458eab0eb8d808e16d98ec7039a7c53855dd9ed6
Author: Rodrigo Flores <mail@rodrigoflores.org>
Date:   Tue Oct 21 22:31:05 2014 -0200

    added 1013

:100644 100644 7bc3db7f48a43ccf1a8cc7c26146912cc88c1009 b393a2138a96c1530f41f70
1ab43cca893226976 M  file.txt


我們終於得到了那個引入 1013 數字的提交點。命令 git bisect log 可以回放整個過程。

$ git bisect start 
# bad: [740cdf012013dc41a39b41d4b09b57a970bfe38f] added 1024
git bisect bad 740cdf012013dc41a39b41d4b09b57a970bfe38f
# good: [7c0dcfa7514379151e0d83ffbf805850d2093538] added 1
git bisect good 7c0dcfa7514379151e0d83ffbf805850d2093538
# good: [8950f7db7e7cad0b2dc394ff9b75fc3d38c9d72a] added 512
git bisect good 8950f7db7e7cad0b2dc394ff9b75fc3d38c9d72a
# good: [a01ba83f3500b48da97c5f5c33052623aaa4161a] added 768
git bisect good a01ba83f3500b48da97c5f5c33052623aaa4161a
# good: [4a4a668bf3363d09af5fd1906bc4272aacdb4495] added 896
git bisect good 4a4a668bf3363d09af5fd1906bc4272aacdb4495
# good: [9059c5b8b898159e8d1d797bff3b1febd1fd6a1c] added 960
git bisect good 9059c5b8b898159e8d1d797bff3b1febd1fd6a1c
# good: [0c844d0b33ef297b742206ebc293f4925705b083] added 992
git bisect good 0c844d0b33ef297b742206ebc293f4925705b083
# good: [0ee17eb17bd96b321a01c73eb13a8929a68b1239] added 1008
git bisect good 0ee17eb17bd96b321a01c73eb13a8929a68b1239
# bad: [dfb1e71736dcfffa2a30aecd7299f45f757c057e] added 1016
git bisect bad dfb1e71736dcfffa2a30aecd7299f45f757c057e
# good: [6e6d08c374df5162fed65fed82859b69f86b936e] added 1012
git bisect good 6e6d08c374df5162fed65fed82859b69f86b936e
# bad: [1d23b7045a8accd254efa859d7fc66f1f58a59f0] added 1014
git bisect bad 1d23b7045a8accd254efa859d7fc66f1f58a59f0
# bad: [458eab0eb8d808e16d98ec7039a7c53855dd9ed6] added 1013
git bisect bad 458eab0eb8d808e16d98ec7039a7c53855dd9ed6
# first bad commit: [458eab0eb8d808e16d98ec7039a7c53855dd9ed6] added 1013


這個例子裡一共有 1024 個提交點,遍歷他們我們只用了 10 步。如果提交點數量再多一倍變成 2048 個,根據二分查詢演算法,我們僅僅需要多加一步就能找到想要的提交點,因為二分查詢演算法的時間複雜度為 O(log n)。 

儘管已經如此高效,一遍又一遍的執行測試命令還是很枯燥的。因此,讓我們再進一步,將這個過程自動化吧。Git bisect 能接受一個指令碼檔名作為命令引數,通過它的返回程式碼判斷當前提交點的好壞。如果返回 0,就是好的提交點,返回其他值就是壞的提交點。湊巧的是,grep 命令如果找到了匹配行就會返回 0,反之返回 1。你可以使用 echo $? 命令來檢查測試命令的返回值。

grep 的返回值正好和我們的測試標準相反,所以我們需要寫一個指令碼來反轉它的值(我不太習慣寫 shell 指令碼,如果大家有更好的方法,感謝告知)。

#!/bin/sh
 
if [[ `grep 1013 file.txt` ]]; then
  exit 1
else
  exit 0
fi
儲存上述程式碼為 test.sh,並賦予它執行許可權。

接著在給 bisect 命令指定了初始的好/壞提交點之後,你只需要執行:
git bisect run ./test.sh
running ./ola.sh
Bisecting: 255 revisions left to test after this (roughly 8 steps)
[a01ba83f3500b48da97c5f5c33052623aaa4161a] added 768
running ./ola.sh
Bisecting: 127 revisions left to test after this (roughly 7 steps)
[4a4a668bf3363d09af5fd1906bc4272aacdb4495] added 896
running ./ola.sh
Bisecting: 63 revisions left to test after this (roughly 6 steps)
[9059c5b8b898159e8d1d797bff3b1febd1fd6a1c] added 960
running ./ola.sh
Bisecting: 31 revisions left to test after this (roughly 5 steps)
[0c844d0b33ef297b742206ebc293f4925705b083] added 992
running ./ola.sh
Bisecting: 15 revisions left to test after this (roughly 4 steps)
[0ee17eb17bd96b321a01c73eb13a8929a68b1239] added 1008
running ./ola.sh
Bisecting: 7 revisions left to test after this (roughly 3 steps)
[dfb1e71736dcfffa2a30aecd7299f45f757c057e] added 1016
running ./ola.sh
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[6e6d08c374df5162fed65fed82859b69f86b936e] added 1012
running ./ola.sh
Bisecting: 1 revision left to test after this (roughly 1 step)
[1d23b7045a8accd254efa859d7fc66f1f58a59f0] added 1014
running ./ola.sh
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[458eab0eb8d808e16d98ec7039a7c53855dd9ed6] added 1013
running ./ola.sh
458eab0eb8d808e16d98ec7039a7c53855dd9ed6 is the first bad commit
commit 458eab0eb8d808e16d98ec7039a7c53855dd9ed6
Author: Rodrigo Flores <mail@rodrigoflores.org>
Date:   Tue Oct 21 22:31:05 2014 -0200
 
    added 1013
 
:100644 100644 7bc3db7f48a43ccf1a8cc7c26146912cc88c1009 b393a2138a96c15
30f41f701ab43cca893226976 M  file.txt
bisect run success


不費吹灰之力,你就得到了出問題的提交點。

結論

你很可能不會每天都用到 bisect。也許一週用一次,甚至一個月只用到一次。但是當你想要排查出帶入問題的提交點時,bisect 確實能幫你一把。儘管並非必須,控制好每次提交的改動規模不要太大將對 bisect 排查大有幫助。如果每個提交都包含了大量的改動,那麼就算 bisect 幫你找到這個提交,你也不得不埋頭於大量的改動中苦苦搜尋 bug 的蹤跡。因此,我推薦的提交策略是嫌大不嫌多。

你呢?你經常使用 bisect 嗎?
相關閱讀
評論(1)

相關文章