Git Hooks可以在執行各個動作的前後自動去觸發某個腳本來做前置處理或是後置處理,這個功能很常被用來在commit前自動檢查程式碼的排版或是寫法上的問題,確保commit過的程式碼排版和風格是一致的。Git Hooks有分為在客戶端(client-side)觸發的Hook,以及在伺服器端(server-side)觸發的Hook,這篇文章只會介紹在客戶端觸發的Hook。
當我們執行git init
指令替專案加入Git版本控制後,專案目錄底下會多出一個.git
目錄,這個目錄用來存放Git設定與版本控制所需的檔案,它並不能被add進Git倉庫中。在.git/hooks
目錄中可以放置這個專案要使用的Git Hooks腳本,這些腳本會有特定的檔名,用來決定它是在Git執行哪個動作時會被觸發。
客戶端的Hook觸發時機和檔案名稱的關係如下表(非全部):
觸發時機 | 檔案名稱(按觸發的先後順序排列) |
---|---|
git am 執行之前 |
applypatch-msg 、pre-applypatch |
git am 執行之後 |
post-applypatch |
git commit 執行之前 |
pre-commit 、prepare-commit-msg (commit訊息的編輯器打開前)、commit-msg |
git commit 執行之後 |
post-commit |
git merge 執行之前 |
pre-merge-commit |
git rebase 執行之前 |
pre-rebase |
git push 執行之前 |
pre-push |
每個腳本在被觸發執行時,各自會有不同的引數傳入。在.git/hooks
目錄中,會有以.sample
結尾的Shell腳本檔案,會說明該Git Hooks腳本應會被傳入什麼樣的引數,以及它該根據什麼樣的狀況回傳什麼exit status,您也可以查閱官方文件獲得更詳細的說明。雖然.sample
檔案是sh腳本,但要用Bash腳本,甚至是任意執行檔也都是可以的。要被用作Git Hooks腳本的檔案必須具有可執行權限才可以被觸發執行。
撰寫Shell腳本的時候可以用ShellCheck來檢查Shell腳本的問題。
Git Hooks的基本應用
commit前自動檢查專案
可以加入pre-commit
腳本來實現commit前自動檢查專案。如果在加入腳本後,臨時在commit時不需要檢查的話,可以在使用git commit
指令時加上--no-verify
參數來略過腳本。
Node.js專案
#!/bin/bash
npm run lint
通常Node.js專案會在package.json
的scripts
欄位下設定lint
腳本,來呼叫lint工具去檢查JavaScript等程式碼的語法。所以在pre-commit
腳本中去執行npm run lint
。
Cargo專案
#!/bin/bash
set -e
cargo +nightly fmt -- --check
cargo clippy --all-targets -- -D warnings
set -e
指令可以讓Bash在執行一系列指令的過程中,如果其中一行指令出現問題,就立刻回傳它的exit status,而不再執行下去。
保存與共享Git Hooks
由於Git Hooks腳本預設存放在無法被git add
的.git
目錄下,因此如果要能使它被push到遠端的Git倉庫上,就需要換個位置放,而不是放在.git/hooks
。
筆者自己是習慣在專案目錄底下再建立一個.githooks
目錄,並把Git Hooks腳本都放在這個目錄下。
當然,我們還是要讓Git知道Git Hooks腳本放在哪才行,可以執行如下的指令:
用如上的指令將Git的core.hooksPath
欄位設定為Git Hooks腳本所在的目錄即可。注意這個設定也只會在目前的這個本地端專案有效,並不會被push到遠端的Git倉庫上。
我們可以在CONTRIBUTING.md
或README.md
中撰寫說明,讓剛clone這個Git專案的開發者主動去執行這個指令。也可以把這個指令寫在程式專案自動化腳本的某個位置。像是Node.js專案,可以在package.json
的scripts
欄位下設定prepare
腳本,來呼叫這個指令。prepare
腳本會在npm ci
或是npm i
的時候被執行。當我們clone一個Node.js專案後,通常首先要做的事情就是npm ci
或是npm i
,所以利用prepare
腳本來設定Git Hooks的目錄。
不過別的環境裡可能會沒有git
指令工具,此時git config
指令會執行失敗,而導致後續原本要跑的流程中止了。將指令改成以下這樣,再加進程式專案裡應該會比較好一點:
git config core.hooksPath .githooks || exit 0