1. 程式人生 > 其它 >手寫 git hooks 指令碼(pre-commit、commit-msg)

手寫 git hooks 指令碼(pre-commit、commit-msg)

簡介

Git 能在特定的重要動作發生時觸發自定義指令碼,其中比較常用的有:pre-commitcommit-msgpre-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-commitcommit-msg 指令碼作為示例,幫助大家更好的理解 git hooks 指令碼。當然,在工作中還是建議使用現成的、開源的解決方案 husky

正文

用於編寫 git hooks 的指令碼語言是沒有限制的,你可以用 nodejsshellpythonruby等指令碼語言,非常的靈活方便。

下面我將用 shell 語言來演示一下如何編寫 pre-commitcommit-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 "此使用者沒有許可權,具有許可權的使用者為: [email protected]"

	# 異常退出
	exit 1
fi

下面用兩個動圖來分別演示一下校驗 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 指令碼預設為不可執行,所以需要將它設為可執行:

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('此使用者沒有許可權,具有許可權的使用者為: [email protected]');
  process.exit(1);
}

總結

其實本文適用的範圍不僅僅侷限於前端,而是適用於所有使用了 git 作為版本控制的專案。例如安卓、ios、Java 等等。只是本文選擇了前端專案作為示例。

最近附上專案原始碼:https://github.com/woai3c/git-hooks-demo

參考資料