簡介
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。
正文
用於編寫 git hooks 的腳本語言是沒有限制的,你可以用 nodejs
、shell
、python
、ruby
等腳本語言,非常的靈活方便。
下面我將用 shell 語言來演示一下如何編寫 pre-commit
和 commit-msg
腳本。另外要注意的是,在執行這些腳本時,如果以非零的值退出程序,將會中斷 git 的提交/推送流程。所以在 hooks 腳本中驗證消息/代碼不通過時,就可以用非零值進行退出,中斷 git 流程。
exit 1
pre-commit
在 pre-commit
鈎子中要做的事情特別簡單,只對要提交的代碼格式進行檢查,因此腳本代碼比較少:
#!/bin/sh
npm run lint
# 獲取上面腳本的退出碼
exitCode="$?"
exit $exitCode
由於我在項目中已經配置好了相關的 eslint 配置以及 npm 腳本,因此在 pre-commit
中執行相關的 lint 命令就可以了,並且判斷一下是否正常退出。
// 在 package.json 文件中已配置好 lint 命令
"scripts": {
"lint": "eslint --ext .js src/"
},
下面看一個動圖,當代碼格式不正確的時候,進行 commit 就報錯了:
在修改代碼格式后再進行提交,這時就不報錯了:
從動圖中可以看出,這次 commit 已正常提交了。
commit-msg
在 commit-msg
hooks 中,我們需要對 commit 消息和用戶進行校驗。
#!/bin/sh
# 用 `` 可以將命令的輸出結果賦值給變量
# 獲取當前提交的 commit msg
commit_msg=`cat $1`
# 獲取用戶 email
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 1
fi
在 commit-msg
鈎子觸發時,對應的腳本會接收到一個參數,這個參數就是 commit 消息,通過 cat $1
獲取,並賦值給 commit_msg
變量。
驗證 commit 消息的正則比較簡單,看代碼即可。如果對 commit 提交規范有興趣,可以看看我另一篇文章。
對用戶權限做判斷則比較簡單,只需要檢查用戶的郵箱或用戶名就可以了(假設現在只有 abc 公司的員工才有權限提交代碼)。
email_re="@abc\.com"
if [[ ! $email =~ $email_re ]]
then
echo "此用戶沒有權限,具有權限的用戶為: xxx@abc.com"
# 異常退出
exit 1
fi
下面用兩個動圖來分別演示一下校驗 commit 消息和判斷用戶權限的過程:
設置 git hooks 默認位置
腳本可以正常執行只是第一步,還有一個問題是必須要解決的,那就是如何和同一項目的其他開發人員共享 git hooks 配置。因為 .git/hooks
目錄不會隨着提交一起推送到遠程倉庫。對於這個問題有兩種解決方案:第一種是模仿 husky 做一個 npm 插件,在安裝的時候自動在 .git/hooks
目錄添加 hooks 腳本;第二種是將 hooks 腳本單獨寫在項目中的某個目錄,然后在該項目安裝依賴時,自動將該目錄設置為 git 的 hooks 目錄。
接下來詳細說說第二種方法的實現過程:
- 在
npm install
執行完成后,自動執行git config core.hooksPath hooks
命令。 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 腳本默認為不可執行,所以需要將它設為可執行:
chmod 700 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 node
const 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 node
const 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(); // 索引 2 對應的 commit 消息文件
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