使用 GIT Bisect 指令進行專案除錯快速找到問題

Bisect Example 01

一、使用 GIT 的 Bisect 指令的背景

在系統開發的過程中,難免在無意間製造 bug,但在發現系統有問題之後,該怎麼樣快速的找到產生問題的原始碼,這就是另外一門學問了。

現在開發者通常使用 GIT 作為版本控制工具,而 GIT 可以怎麼樣提供開發者快速找到問題呢?大家可以參考 GIT 的 bisect 這個指令。以下提供一個在 PHP 開發的環境下,使用 Bisect 的案例來供大家參考。

二、關於 GIT Bisect 的介紹

Git Bisect

GIT 的 Bisect 指令是一個幫助開發者從儲存庫中,原本正確的 Commit 點到發現錯誤的 Commit 點的範圍內,以二分法找尋問題點的方法,以底下的例子:

Bisect Example 01

以上圖範例中,假設在開發的過程中,原本在 v2.0 版本確認沒有問題,但突然發現,最新的開發版本上測試發現問題,因此我們要找出從釋出 v2.0 版本後,是在哪個 commit 之後才發生這問題的,這時候就可以利用 bisect 來進行尋找錯誤的動作。

1
git bisect start HEAD v2.0

如上指令,其指令意思是「開始使用 bisect 指令除錯,定義的範圍從目前的 HEAD 一直到 refs v2.0 的位置」,使用指令務必要確定,一定要有一端測試是正常的。

當開始使用 bisect 除錯後,首先 GIT 會幫忙先把目前所在 GIT HEAD 的位置挪移到中間的 commit 上,這時候可以再次地進行「測試」,當在這次測試的過程中確認目前的 commit 還是錯的,則:

1
git bisect bad

可以使用 git bisect bad 來標注目前的 commit 依然是有問題的,這代表,目前所在的 commit 直到開始點的所有 commit 都是有問題的。接著 GIT 會協助繼續幫忙把 HEAD 指標移動到距離目前確認測試是正確的 commit 的一半位置。

Bisect Step 3

到了目前的 commit 位置後,如果發現測試是正確的,則可以使用 git bisect good 來標注目前所在的 commit 測試是正常的,則代表從目前的 commit 到正確端的 commit (v2.0) 都是正確的。接著 GIT 會把 HEAD 的位置移動到下一個 commit 上。

Bisect Step 4

最後,在這個 commit 上再次的進行測試,發現,其還是正確的,同樣的使用 git bisect good 進行標注。

Bisect Step 5

則最終可以找到第一個造成問題的 commit 上。

Bisect Step 6

在找到有問題的 commit 之後,GIT 會提示有問題的這個 commit 的 SHA1,此時 HEAD 會停留在原本的位置。這時候如果要回到該分支的最原始位置,則可以使用指令:

1
git bisect reset

一般來說,如果有進行單元測試,又有進行 CICD 的流程的話,有問題的 commit 在推上 GIT Server 的第一瞬間就會被發現了,並不應該出現上述的案例。

三、以 PHP 專案使用 PHPUnit 除錯建立如上述的案例再次說明

GIT Bisect Command Demo 這個 GitLab 的 Repo 上,我建立了一個以 PHP 搭配 PHPUnit 做單元測試的簡單範例。

1. 測試範例環境

  • PHP 7.0 以上
  • Composer

2. 測試範例環境安裝

Step 1. 安裝

1
2
3
git clone git@gitlab.com:mouson-gitlab-playground/git-feature-demo/git-bisect-command-demo.git
cd git-bisect-command-demo
composer install --prefer-dist

Step 2. 執行第一次測試,應發生測試錯誤

1
./vendor/bin/phpunit

執行畫面如下:

Bisect Example 01

3. 手動 git bisect 測試

Step 1. 啟用二分法測試,執行後應跳到目前 HEAD 與 v2.0 這個版本間的 commit

1
git bisect start HEAD v2.0

執行後可以看到,目前 GIT HEAD 所在的位置挪移到了 48c7706 的這個 SHA1 上。

Bisect Example 02

48c7706 這個位置,也就是原本的 master 到 v2.0 之間的「中間」所在的位置。

Bisect Example 02

Step 2. 執行 phpunit 出現錯誤,設定目前 commit 為錯誤

1
2
./vendor/bin/phpunit
git bisect bad

Bisect Example 03

在執行 git bisect bad 宣告該 commit 為錯誤點之後,GIT 會自動的挪移 HEAD 位置到 3526104 的 SHA1 位置,如下圖。

Bisect Example 03

Step 3. 執行 phpunit 通過測試,設定目前 commit 為正確

1
2
./vendor/bin/phpunit
git bisect good

Bisect Example 04

在執行 git bisect good 宣告該 commit 為正確點之後,GIT 會自動的挪移 HEAD 位置到 8f3dca5 的 SHA1 位置,如下圖。

Bisect Example 04

Step 4. 執行 phpunit 通過測試,設定目前 commit 為正確

目前 HEAD 所在的 SHA1 8f3dca5 位置,已經就在第一次剖半的 SHA1 48c7706 旁邊,意味著,當目前所在的位置如測試「正確」,則代表第一次錯誤的位置為 SHA1 48c7706,而如果「錯誤」則表示目前位置就是第一次出現錯誤的位置。

1
./vendor/bin/phpunit

同樣的透過單元測試,進行驗證,在這個步驟測試結果是正確的。因此錯誤點是第一次剖半的 SHA1 48c7706,此時標注目前所在的位置 SHA1 為測試正確。

1
git bisect good

在執行 git bisect good 指令後 GIT 如同上面所描述的,會顯示第一次出現錯誤的 commit 細節資訊,如 commit 內容、作者、時間及對應修改的原始碼內容等。

Bisect Example 05

Step 5. 在找到錯誤點之後,需要重置回到 master HEAD

在找到錯誤的位置之後,還需要透過 git bisect reset 指令來進行「恢復環境」的動作,其執行後 GIT 會幫忙將 HEAD 移回 master 的 HEAD 位置。這時候就可以針對所看到的 commit 位置進行除錯的決策及判斷與修改。

1
git bisect reset

4. 讓 git bisect 自動化執行測試

上面所描述的是使用「手動」透過 git bisect bad 以及 git bisect good 的方式設定目前所在位置是否正確的方法,但 bisect 指令還有提供自動執行測試的功能,只要使用的執行方法,當執行測試發生錯誤的時候,可以產生錯誤訊號,讓 GIT 判斷,那就可以自動找到 commit 的位置。

其自動測試的過程如下:

Step 1. 啟用二分法測試

自動化檢測的開始流程與手動的方法是一樣的,同樣是執行 git bisect start HEAD v2.0

1
git bisect start HEAD v2.0

Step 2. 執行自動化測試,並找到錯誤的 commit

主要是底下的指令,需要宣告要執行測試的指令為何,這邊以 php 的環境來說,使用的是 PHPUnit 單元測試,因此其執行路徑在專案目錄下的 ./vendor/bin/phpunit,因此可以直接執行:

1
git bisect run ./vendor/bin/phpunit

Bisect Example 06

Step 3. 執行 phpunit 出現錯誤,此為發生問題的 commit,重置回到 master HEAD

在自動測試之後 GIT 同樣的會將錯誤的位置停在最後的 commit 並顯示第一個發生錯誤的 commit 位置及資訊,因此在取得錯誤之後,還是需要自己手動執行 git bisect reset 回到原本的位置進行後續的動作。

四、結論:

使用 GIT 的 bisect 指令進行除錯,可以減少自行執行 GIT 指令做 HEAD 位置移動的麻煩,可以加速找到有問題的位置,但平時還是要養成好習慣,如果 commit 的原始碼內容太過龐大或雜亂,對於除錯依然沒有太大幫助,所以,養成好的 commit 習慣還是方便除錯的最重要根本。

五、參考連結:

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器