維持團隊開發程式碼品質的好工具 — Git Hooks & Husky

Sean Chou
Recording everything
8 min readDec 1, 2019

--

https://www.seekpng.com/png/detail/321-3210662_git-hooks-git.png

最近在開發的時候,有用到一些 git hooks,也有遇過要寫很複雜的 git hook script 情況,趁有時間來紀錄一下,順便加強自己對 git hooks 的認識。

Why git hooks?

為什麼需要 git hooks & git hooks 是什麼呢?

在一般開發的時候,我們常常會使用一些工具,來維持團隊開發的程式碼的品質與一致性,例如像是 Linter 或是 Unit test,以前端開發來說的話,我們最常使用就是 Eslint 來讓大家寫的程式碼保持一致性,使用 Jest 來維持專案的品質。

But!!!

實務上常常會遇到,有的開發者自己開發完之後,忘記去執行 Linter 或是Unit Test,甚至 IDE 沒有裝上相對應的 plug-in,也不會發現 Linter 檢查其實根本沒過。導致這些不良的 code 推上 git 之後,被其他人抓下來,或是直接到了 server build process 才發現這些本來就可以預防的錯誤。

Pull 到爛 code 的人

這時候,Git Hooks 就是一個可以用來解決這個問題的工具。

How to use it?

Hooks,意為掛鉤,git hooks 簡單說也就是讓你可以在 git action 的同時去進行一些客製化的行為。

要怎麼安裝呢?

其實預設在 git 目錄底下就會有 hooks 的檔案了,並且在 Git 1.6 版本之後,這些都會是 template file,檔名都是以 .sample 結尾,所以當你修改了這些 .sample 檔案,並且把檔名的 .sample 拿掉,git hooks 就會在相對應的時間點來執行這些檔案。

  • 進入 .git 的目錄底下
  • Hooks template file 都會被放在 .git/hooks

Client-Side and Server-Side Hooks

Hooks 實際上依據功能,區分為 Client-Side 和 Server-Side,但是詳細內容就不細究了,有興趣要使用可以去官網查詢看看。

Git Hooks with Eslint

Git hooks 其實根據需求,可以有多種不同的使用情境,但就我認知最常用的,就是搭配 Linter 或是 Unit test 來使用了,這裡就簡單拿 Eslint 來做個範例。

package.json

首先先在 package.json 加上 run script,指定 lint 來執行 Eslint 。當然不寫這段也是沒有問題,但個人偏好把這些指令集合起來,不僅方便管理,之後要維護也會比較好找地方。

{  ...  
"scripts": {
"lint": "node_modules/.bin/eslint --ext .js ./src" }
...
}

Hooks Script

由於 git hooks 的目的是可以讓你在做 git action 的時候,執行自己的客製化 script,所以我們要先來寫一個簡單的驗證 shell script。

這個 script 分成兩段,第一段先檢查你有沒有安裝 yarn,沒有的話就會直接中斷這個 script。第二段就是實際去執行 Eslint check,如果失敗的話一樣會直接中斷這個 script。只要 script 被中斷,那 git action 也不會成功執行。

Add into git hooks

最後一步就是要把這個 script copy 到 .git 的資料夾下,如果你想要綁在 commit 之前,就 append 到 pre-commit,如果想要在 git push 之前在執行這個 script ,就把這些內容 append 到 pre-push 裡面。

這樣就大功告成了嗎?

在團隊開發時來使用 Git Hooks

如果你的專案的 member 只有你自己一個人,或是你要叫其他你的 team member 都手動做一次複製 script 的動作,或許就結束了。

但實務上,總不可能每次 script 修改的時候,還要請大家都去手動做一次更改 git hooks 裡面的 script 吧?

Do it by yourself

在 yarn 或是 npm install 完,其實可以使用 postinstall 的 action 來做到你想做的客製化需求。

{  ...  
"scripts": {
"postinstall": "copy your script into .git/hooks/" }
...
}

如上所述,可以使用任何指令的方式來達成目標,不論你要 cp 過去直接覆蓋之前的檔案,還是要使用 append 的方式加進去 pre-push/ pre-commit 的 script 裡面。

但是當你們專案中,還有其他需要 git hooks 的行為的時候, 例如 git-lfs 也會修改到 pre-push script ,這時候 script 的處理就會變得有點麻煩,如果每次 postinstall 都是直接複製覆蓋過原本的 hooks script 的話,那當上傳大型檔案用到 git-lfs 的時候就可能會發生不預期的問題。

當遇到上述總總情況,我們只好再拿另一個 library 來解決這個問題。

Husky

Husky 是個可以方便解決上述問題的一個工具。

安裝完 Husky 之後,你的 .git/hooks 目錄底下會多了很多檔案。

仔細看的話,發現 Husky 幫你實作了 hooks 本來 .sample 的那些 script,每一個 script 打開內容都是一樣的。

當你執行 git action 的時候,Husky 會根據你在 package.json 內 Run Husky 定義的內容來執行。舉例來說,我們就在裡面定義一個 pre-push 的行為,去執行我們剛剛寫好的那一隻客製化 script 。

{  ...
"husky": {
"hooks": { "pre-push": "sh scripts/git-hook-prepush.sh" }
...
}

所以,每當 git push action 之前,就會去執行我們的 script 做 Eslint 的檢查,如果沒有通過,你的 git push action 就會被擋住不成功。

這樣就可以達成我們一開始想要的目標,讓大家寫的程式碼保持一致性了!

不過呢,其實這也還是防君子不防小人的機制,如果想繞過 git hooks 檢查,只要在 action 的時候加上參數 — no-verify 就可以 bypass 了。

Sample Code

最後附上很簡單的小範例:

如果你覺得這篇文章對你有幫助,歡迎買杯咖啡贊助 ☕️ 謝謝

--

--