咦?這個問題是從什麼時候就有的?

#git
高見龍
技術文章
咦?這個問題是從什麼時候就有的?

「咦?為什麼系統的訂單金額一直都是顯示成 0 元?」「從什麼時候開始有這問題的?」

主管看到訂單系統的顯示結果不正常,想問到底是怎麼一回事。但這個專案的開發團隊有二十多人,大家每天都有許多的 Commit,也許某位同事無意間改了計算稅務的功能,結果不小心跟著改到訂單顯示金額了。

如果很明確的知道問題是出在哪一個檔案的話,我在「為你自己學 Git」一書的「【狀況題】等等,這行程式誰寫的?」章節曾經介紹過 git blame 指令可以找出指定檔案的第幾行是誰在什麼時候寫的,一下子就可以把犯人抓出來。

但是,有時候一個問題的造成,背後可能是多個程式執行之後的結果,或像是開發 iOS App 需要進行編譯才能看到顯示結果為什麼不正確,所以可能不是單純光用 git blame 就能輕鬆找出問題來。更何況,這個問題可能早在幾週前就發生了,只是當時沒人發現,直到現在才曝光。在這麼多的 Commit 裡,要怎麼知道從哪個 Commit 開始是有這個問題的?特別是如果大家在 Commit 訊息都沒有特別註明自己改了什麼內容的話,面對這幾百甚至是幾千個 Commit,難道只能 Checkout 到每一個 Commit,然後檢查訂單的顯示結果是不是正確嗎?

使用「二分法」

不知道大家有沒有玩過「猜數字」遊戲:

「請你從 1 到 100 之間猜一個數字,如果沒猜中,我會跟你說你猜的數字比答案大還是比答案小,然後繼續猜,直到猜到為止」

「50!」

「比 50 大」

「75!」

「比 75 小」

「65!」

「恭喜你,答對了」

通常一開始我們會使用「二分法」從中間的 50 開始猜,為什麼?因為這樣一次就能刪掉一半的數字,沒猜中的話,就再對剩下的另一半再切一半繼續猜,這樣應該沒幾次就能猜到答案了。

在 Git 裡有個叫做 bisect 的指令,也是用類似的「猜數字」的方式,可以很快把有問題的點找出來。

檔案下載:https://ubin.io/git-bisect

在這個範例裡,目前總共有 23 次 Commit:

$ git log --oneline
6e593bc (HEAD -> master) update CSS
e67e3cf add footer
da11948 update CSS
84ee212 add links
8b59e89 add Tiger page
711bc24 update title of Fish page
1ee10be update page
6a8fb89 update title
6e3fab8 update title
b031a2c update page
73860fa update page
f53c5ea add Fish page
afa4537 add cover image
2d15868 remove js code
8f744f8 add Dog page
b51f5e8 add Cat page
da3d4b9 update CSS
7a51f8e add Sign in form
7e64d30 update script
1086842 link style and script file in index
f6b72af add stylesheet and script file
3de629a add HTML content
f9f14ff init commit

不知道什麼時候開始,在 index.html 頁面上的 Sign In 按鈕沒辦法按了(被 disabled 了)。看看上面這些 Commit 訊息,寫得實在很糟糕...根本看不出來 update 了什麼內容。

所以,除了一個一個去看到底每個 Commit 改了什麼之外,我們可以試著使用 Git 裡的 bisect 指令來抓問題:

$ git bisect start 起始點 結束點

因為我確定在 f6b72af 的時候功能還是正常的,所以可以這樣做:

$ git bisect start HEAD f6b72af

意思是針對目前的所在地(也就是 HEAD)到 f6b72af 這段範圍開始來做二分法。按下 Enter 鍵執行之後:

$ git bisect start HEAD f6b72af
Bisecting: 9 revisions left to test after this (roughly 3 steps)
[73860fa1ba395a7db7dd34c966644f667b0b87e3] update page

這時 Git 會直接跳到差不多一半的地方,也就是 73860fa 這個 Commit。這時候發現功能還是正常的,Sign In 按鈕是可以按的,所以就可以跟 Git 說目前的結果是好的:

$ git bisect good
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[711bc2458ac4c9c87b41110a4f23224ff1536a13] update title of Fish page

這時 Git 會自動跳到差不多中間的位置,也就是 711bc24 這個 Commit。重新整理頁面,發現這時候功能是壞掉的...所以繼續跟 Git 說現在是壞掉的:

$ git bisect bad
Bisecting: 2 revisions left to test after this (roughly 1 step)
[6e3fab83aeb14d9640337357027c167194f0c628] update title

現在看起來是正常的,所以:

$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[1ee10be0c8b8565a8d2e66e1707d90c3ef3fa07a] update page

如果結果是正常的,就是 git bisect good,如果結果是不正確的,就是 git bisect bad,在不斷的 good 跟 bad 的交錯幾次之後,最後找到答案了:

$ git bisect bad
6a8fb890400553b95e27996ec948fefb836cb2d6 is the first bad commit
commit 6a8fb890400553b95e27996ec948fefb836cb2d6
Author: Eddie Kao <eddie@digik.com.tw>
Date:   Wed Aug 7 16:18:23 2019 +0800

    update title

:100644 100644 bd266562ec6d1ec64e5c29f32c7190582db95958 39f7c9edc892bd2b2e916e233ad1bc3989f7a3d4 M  index.html

抓到了!看看在第一行出現的訊息,Git 告訴你「6a8fb89...cb2d6 is the first bad commit」,就是從這個地方開始壞掉的,原來是某位同事在修改頁面的時候暫時把那個按鈕關掉,結果忘記改回來...

如果 bisect 做一半不想做了,或是做完了,可以使用 reset 參數:

$ git bisect reset
Previous HEAD position was 6a8fb89 update title
Switched to branch 'master'

然後就回到原本的 master 分支了。

雖然在這個範例只有少少 23 個 Commit,可能感受不出來效果,感覺自己一個一個 Commit 查也是可以查得到,但如果是 230 個或是 2,300 個就不一樣了。生命應該浪費在寶貴的事物上,而不是浪費在這種地方呀 :)

本文轉載自「為你自己學 Git」電子書之追加章節


👩‍🏫 課務小幫手:

✨ 想掌握 Git 觀念和原理嗎?

我們有開設 🔮 坐上 Git 時光機 - 版本控制 課程唷 ❤️️