Git 能在特定的重要動作發生時觸發自定義腳本,其中比較常用的有:pre-commit、commit-msg、pre-push 等鉤子(hooks)。我們可以在 pre-commit 觸發時進行代碼格式驗證,在 commit-msg 觸發時對 commit 消息和提交用戶進行驗證,在 pre-push 觸發時進行單元測試、e2e 測試等操作。
Git 在執行 git init 進行初始化時,會在 .git/hooks 目錄生成一系列的 hooks 腳本:
從上圖可以看到每個腳本的後綴都是以 .sample 結尾的,在這個時候,腳本是不會自動執行的。我們需要把後綴去掉之後才會生效,即將 pre-commit.sample 變成 pre-commit 才會起作用。
本文主要是想介紹一下如何編寫 git hooks 腳本,並且會編寫兩個 pre-commit、commit-msg 腳本作為示例,幫助大家更好的理解 git hooks 腳本。當然,在工作中還是建議使用現成的、開源的解決方案 husky[1]。
正文用於編寫 git hooks 的腳本語言是沒有限制的,你可以用 nodejs、shell、python、ruby等腳本語言,非常的靈活方便。
下面我將用 shell 語言來演示一下如何編寫 pre-commit 和 commit-msg 腳本。另外要注意的是,在執行這些腳本時,如果以非零的值退出程序,將會中斷 git 的提交/推送流程。所以在 hooks 腳本中驗證消息/代碼不通過時,就可以用非零值進行退出,中斷 git 流程。
pre-commit在 pre-commit 鉤子中要做的事情特別簡單,只對要提交的代碼格式進行檢查,因此腳本代碼比較少:
#!/bin/shnpm run lint
exitCode="$?"exit $exitCode由於我在項目中已經配置好了相關的 eslint 配置以及 npm 腳本,因此在 pre-commit 中執行相關的 lint 命令就可以了,並且判斷一下是否正常退出。
"scripts": { "lint": "eslint --ext .js src/" },下面看一個動圖,當代碼格式不正確的時候,進行 commit 就報錯了:
在修改代碼格式後再進行提交,這時就不報錯了:
從動圖中可以看出,這次 commit 已正常提交了。
commit-msg在 commit-msg hooks 中,我們需要對 commit 消息和用戶進行校驗。
#!/bin/sh
commit_msg=`cat $1`
email=`git config user.email`msg_re="^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,100}"
if [[ ! $commit_msg =~ $msg_re ]]then echo "\n不合法的 commit 消息提交格式,請使用正確的格式:\ \nfeat: add comments\ \nfix: handle events on blur (close #28)\ \n詳情請查看 git commit 提交規範:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md"
exit 1fi在 commit-msg 鉤子觸發時,對應的腳本會接收到一個參數,這個參數就是 commit 消息,通過 cat $1 獲取,並賦值給 commit_msg 變量。
驗證 commit 消息的正則比較簡單,看代碼即可。如果對 commit 提交規範有興趣,可以看看我另一篇文章[2]。
對用戶權限做判斷則比較簡單,只需要檢查用戶的郵箱或用戶名就可以了(假設現在只有 abc 公司的員工才有權限提交代碼)。
email_re="@abc\.com"if [[ ! $email =~ $email_re ]]then echo "此用戶沒有權限,具有權限的用戶為: xxx@abc.com"
exit 1fi下面用兩個動圖來分別演示一下校驗 commit 消息和判斷用戶權限的過程:
設置 git hooks 默認位置腳本可以正常執行只是第一步,還有一個問題是必須要解決的,那就是如何和同一項目的其他開發人員共享 git hooks 配置。因為 .git/hooks 目錄不會隨著提交一起推送到遠程倉庫。對於這個問題有兩種解決方案:第一種是模仿 husky 做一個 npm 插件,在安裝的時候自動在 .git/hooks 目錄添加 hooks 腳本;第二種是將 hooks 腳本單獨寫在項目中的某個目錄,然後在該項目安裝依賴時,自動將該目錄設置為 git 的 hooks 目錄。
接下來詳細說說第二種方法的實現過程:
1.在 npm install 執行完成後,自動執行 git config core.hooksPath hooks 命令。2.git config core.hooksPath hooks 命令將 git hooks 目錄設置為項目根目錄下的 hooks 目錄。
"scripts": { "lint": "eslint --ext .js src/", "postinstall": "git config core.hooksPath hooks"},踩坑demo 源碼在 windows 上是可以正常運行的,後來換成 mac 之後就不行了,提交時報錯:
hint: The 'hooks/pre-commit' hook was ignored because it's not set as executable.原因是 hooks 腳本默認為不可執行,所以需要將它設為可執行:
為了避免每次克隆項目都得修改,最好將這個命令在 npm 腳本上加上:
"scripts": { "lint": "eslint --ext .js src/", "postinstall": "git config core.hooksPath hooks && chmod 700 hooks/*"},當然,如果是 windows 就不用加後半段代碼了。
nodejs hooks 腳本為了幫助前端同學更好的理解 git hooks 腳本,我用 nodejs 又重寫了一版。
pre-commit
#!/usr/bin/env nodeconst childProcess = require('child_process');
try { childProcess.execSync('npm run lint');} catch (error) { console.log(error.stdout.toString()); process.exit(1);}commit-msg
#!/usr/bin/env nodeconst childProcess = require('child_process');const fs = require('fs');
const email = childProcess.execSync('git config user.email').toString().trim();const msg = fs.readFileSync(process.argv[2], 'utf-8').trim(); const commitRE = /^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,100}/;
if (!commitRE.test(msg)) { console.log(); console.error('不合法的 commit 消息格式,請使用正確的提交格式:'); console.error('feat: add \'comments\' option'); console.error('fix: handle events on blur (close #28)'); console.error('詳情請查看 git commit 提交規範:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md。'); process.exit(1);}
if (!/@qq\.com$/.test(email)) { console.error('此用戶沒有權限,具有權限的用戶為: xxx@qq.com'); process.exit(1);}總結其實本文適用的範圍不僅僅局限於前端,而是適用於所有使用了 git 作為版本控制的項目。例如安卓、ios、Java 等等。只是本文選擇了前端項目作為示例。
最近附上項目源碼:https://github.com/woai3c/git-hooks-demo
參考資料•自定義 Git - 使用強制策略的一個例子[3]•Shell 教程[4]
References[1] husky: https://github.com/typicode/husky
[2] 文章: https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md
[3] 自定義 Git - 使用強制策略的一個例子: https://git-scm.com/book/zh/v2/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-%E4%BD%BF%E7%94%A8%E5%BC%BA%E5%88%B6%E7%AD%96%E7%95%A5%E7%9A%84%E4%B8%80%E4%B8%AA%E4%BE%8B%E5%AD%90
[4] Shell 教程: https://www.runoob.com/linux/linux-shell.html