你好,我是小桔,是一個沒有感情的代碼崽。
今天給大家介紹一下 Git Hooks,相信 Git 大家都在用吧,Git 除了用作版本控制,還有許多高級功能,Git Hooks 就是其中之一。
本文環境:
- Git 版本:2.27.0
- Husky 版本:4.2.5
- Node.js 版本:12.16.2
前言
做過前端的同學對 Hook 這個東西應該很了解吧,后端也是有 Hook 這種概念的,比如 Java 的@PostConstruct
,也是一種 Hook 的體現。簡單來說,Hook 就是在執行某個事件之前或之后進行一些其他額外的操作。
舉個栗子,張三現在要吃飯,那么吃飯就是一個事件,吃飯前和吃飯后就可以稱為兩個鉤子。現在你想讓張三在吃飯前洗下手,那么我們就可以在吃飯前這個鉤子這里,設置一個洗手的動作。張三在每次吃飯前,都會檢查一下這個鉤子,有什么要做的,都會照做。這樣,就實現了我們的需求。
Git 也是如此,在 Git 中也有許多的事件(commit、push 等等),每個事件也是對應了有不同的鉤子的(如 commit 前,commit 后),那么我們就可以在這些鉤子這里配置一些自己需要執行的操作來實現各種各樣的需求。
使用
真實場景
可能初次了解 Git Hooks 的同學會有一些疑問:這個東西到底能干嘛?我以前沒用過它不也一樣好好的嗎?我干嘛要用它?
其實這說得很對,**任何技術都是有需求采用,沒有需求就別去硬塞,永遠記住:技術為業務服務。**但是這并不妨礙你先去了解它,畢竟,只有你先知道了這項技術能解決什么樣的問題,日后當你遇到相應的問題時,你才知道該用什么技術去解決。
這里我給出一個真實的場景,是我們團隊在開發 Lin UI 的時候遇到的:
我們的 Git 倉庫中包含了編譯后的代碼,所以每次修改了源碼,都需要運行一下編譯命令,然后把源碼和編譯后的代碼一起提交到 Git 倉庫,這個流程沒什么問題。但是,人腦不是電腦,總會有疏忽的時候,經常會出現這樣一種情況:修改了源碼,卻忘記了運行編譯命令,最后只把源碼提交到了 Git 倉庫,導致線上倉庫的源碼和編譯產物不一致、
這個問題雖然不是特別嚴重,但老是出現也總歸不好。所以我們就想了一個辦法,不再手動編譯,把編譯任務交給 CI 去做,這樣就不存在這樣的問題。
但事情總是沒那么順利,因為我們在本地開發調試的時候是需要編譯代碼的,所以就會生成一部分編譯代碼,在使用 Git 時,我們經常會使用git add .
命令,會把所有修改了的代碼都提交到倉庫,這顯示是不行的。因為現在我們已經把編譯交給 CI 去做了,并且為了 Code Review 方便,編譯代碼不應該再提交到倉庫了。如果每次手動去把編譯代碼去除,又非常麻煩,那該怎么辦呢?
這種情況,就可以使用 Git Hooks 幫我們在每次提交前自動把編譯代碼去掉了。
PS:這個場景雖然不那么常見和通用,但確實是在開發中真實遇見的。
Git Hooks 介紹
Git Hooks 的實現其實非常簡單,就是就.git/hooks
文件下,保存了一些 shell 腳本,然后在對應的鉤子中執行這些腳本就行了。比如下圖中,這是一個還沒有配置 Git Hooks 的倉庫,默認會有很多.sample
結尾的文件,這些都是示例文件

我們打開pre-commit.sample
文件看一下其中的內容,大致意思是說這是一個示例,做了一些格式方面的檢測,這個腳本默認是不生效的,如果要生效,把文件名改為pre-commit.sample
即可

pre-commit
這個鉤子是在git commit
命令執行之前觸發
Git 支持的所有鉤子見下表(加粗的為常用鉤子):
Git Hook | 調用時機 | 說明 |
---|---|---|
pre-applypatch | git am 執行前 | |
applypatch-msg | git am 執行前 | |
post-applypatch | git am 執行后 | 不影響git am 的結果 |
pre-commit | git commit 執行前 | 可以用git commit --no-verify 繞過 |
commit-msg | git commit 執行前 | 可以用git commit --no-verify 繞過 |
post-commit | git commit 執行后 | 不影響git commit 的結果 |
pre-merge-commit | git merge 執行前 | 可以用git merge --no-verify 繞過。 |
prepare-commit-msg | git commit 執行后,編輯器打開之前 | |
pre-rebase | git rebase 執行前 | |
post-checkout | git checkout 或git switch 執行后 | 如果不使用--no-checkout 參數,則在git clone 之后也會執行。 |
post-merge | git commit 執行后 | 在執行git pull 時也會被調用 |
pre-push | git push 執行前 | |
pre-receive | git-receive-pack 執行前 | |
update | ||
post-receive | git-receive-pack 執行后 | 不影響git-receive-pack 的結果 |
post-update | 當 git-receive-pack 對 git push 作出反應并更新倉庫中的引用時 | |
push-to-checkout | 當``git-receive-pack對 git push做出反應并更新倉庫中的引用時,以及當推送試圖更新當前被簽出的分支且 receive.denyCurrentBranch配置被設置為 updateInstead`時 | |
pre-auto-gc | git gc --auto 執行前 | |
post-rewrite | 執行git commit --amend 或git rebase 時 | |
sendemail-validate | git send-email 執行前 | |
fsmonitor-watchman | 配置core.fsmonitor 被設置為.git/hooks/fsmonitor-watchman 或.git/hooks/fsmonitor-watchmanv2 時 | |
p4-pre-submit | git-p4 submit 執行前 | 可以用git-p4 submit --no-verify 繞過 |
p4-prepare-changelist | git-p4 submit 執行后,編輯器啟動前 | 可以用git-p4 submit --no-verify 繞過 |
p4-changelist | git-p4 submit 執行并編輯完changelist message 后 | 可以用git-p4 submit --no-verify 繞過 |
p4-post-changelist | git-p4 submit 執行后 | |
post-index-change | 索引被寫入到read-cache.c do_write_locked_index 后 |
PS:完整鉤子說明,請參考官網鏈接
Husky 配置
從上面的介紹中,我們知道 Git Hook 保存在 .git 文件夾中。不知你有沒有發現這會有一個問題?可能細心的同學已經知道了,Git 是一個多人協作工具,那按理說 Git 倉庫中的所有文件都應該被跟蹤并且上傳至遠程倉庫的。但是有個例外,.git
文件夾不會,這就導致一個問題,我們在本地配置好 Git Hook 后,怎么分享給其他小伙伴兒呢?copy 嗎?那未免太 low 了,都用 Git 了,還 copy,也太不優雅了。這時候,就輪到 Husky 出場了。
Husky 是一個讓配置 Git 鉤子變得更簡單的工具(題外話:Husky 是哈士奇的意思,我猜可能是作者養了條二哈)
下面這些流行的項目都在使用 Husky,可見它確實是一個非常好用的工具:
- webpack
- babel
- create-react-app
- ……
Husky 的原理是讓我們在項目根目錄中寫一個配置文件,然后在安裝 Husky的時候把配置文件和 Git Hook 關聯起來,這樣我們就能在團隊中使用 Git Hook 了。
下面開始配置 Husky
第一步
使用 npm 初始化你的項目(如果項目已有 package.json,請跳至第二步)
npm?init?-y
第二步
安裝 Husky
//?注意?Node.js?版本要?>=10
npm?install?husky?-D
第三步
書寫配置文件,4.2.5 版本的 Husky 共支持以下幾種格式的配置文件:
- .huskyrc
- .huskyrc.json
- .huskyrc.yaml
- .huskyrc.yml
- .huskyrc.js
- husky.config.js
個人習慣,這里我采用的是.huskyrc
,在其中書寫 json 格式的配置,如下:
{
??"hooks":?{
????"pre-commit":?"git?restore?-W?-S?dist?examples/dist"
??}
}
是不是很簡單,我們來解讀一下這個配置文件。hooks
這個對象中,key 就是鉤子名,而 value 就是需要執行的命令。上面這個配置的含義就是,在每次執行 git commit
之前,都會把dist
和examples/dit
目錄下的修改回滾(這兩個目錄就是編譯產生的代碼),就不用擔心誤把編譯后的代碼提交到倉庫中了。
上面我們只寫了一條命令,如果想執行兩條命令怎么辦呢?比如我還想在git commit
之前用 EsLint 檢查一下代碼質量,我們可以像下面這樣寫:
{
??"hooks":?{
????"pre-commit":?"git?restore?-W?-S?dist?examples/dist?&&?eslint?."
??}
}
是的,就是這么簡單。如果 EsLint 檢測不通過,那么git commit
是會被阻止的,就不用擔心"垃圾代碼"被提交到線上倉庫了。
Husky 注意事項
Husky 讓我們可以很方便的配置 Git Hooks,同時,也提供了一些實用方便的小技巧以及一些我們需要注意的點
不支持的鉤子
Husky 不支持服務端 Git 的鉤子:
- pre-receive
- update
- post-receive
跳過所有鉤子
有時你可能不想運行鉤子,那么可以像下面這樣跳過:
HUSKY_SKIP_HOOKS=1?git?rebase?...
禁用自動安裝
如果你不想 Husky 為你自動安裝鉤子(比如 clone 了一個第三方的庫,想要自己開發時),可以這樣做:
HUSKY_SKIP_INSTALL=1?npm?install
最后
本文介紹了 Git Hooks 具體有哪些,并講解了如何用 Husky 便捷的配置 Git Hook。下一篇文章,我會教你如何用 commitlint 結合 Husky 來規范團隊的 commit 信息,如果有興趣的話,記得一定要關注我哦!
我是小桔,歡迎關注我的微信公眾號,帶你了解更多前后端知識。
