手寫 git hooks 腳本

我們的 Git 倉(cāng)庫(kù)中包含了編譯后的代碼,所以每次修改了源碼,都需要運(yùn)行一下編譯命令,然后把源碼和編譯后的代碼一起提交到 Git 倉(cāng)庫(kù),這個(gè)流程沒什么問題。但是,人腦不是電腦,總會(huì)有疏忽的時(shí)候,經(jīng)常會(huì)出現(xiàn)這樣一種情況:修改了源碼,卻忘記了運(yùn)行編譯命令,最后只把源碼提交到了 Git 倉(cāng)庫(kù),導(dǎo)致線上倉(cāng)庫(kù)的源碼和編譯產(chǎn)物不一致、
這個(gè)問題雖然不是特別嚴(yán)重,但老是出現(xiàn)也總歸不好。所以我們就想了一個(gè)辦法,不再手動(dòng)編譯,把編譯任務(wù)交給 CI 去做,這樣就不存在這樣的問題。類似一些自動(dòng)化管理version版本,tag 操作, hooks腳本管理 version文件等。
git hooks 可以幫助我們做到這些工作,在 Git 中也有許多的事件(commit、push 等等),每個(gè)事件也是對(duì)應(yīng)了有不同的鉤子的(如 commit 前,commit 后),那么我們就可以在這些鉤子這里配置一些自己需要執(zhí)行的操作來實(shí)現(xiàn)各種各樣的需求。
下面來分享git hooks 腳本:https://git-scm.com/docs/githooks

簡(jiǎn)介
Git 能在特定的重要?jiǎng)幼靼l(fā)生時(shí)觸發(fā)自定義腳本,其中比較常用的有:pre-commit、commit-msg、pre-push 等鉤子(hooks)。我們可以在 pre-commit 觸發(fā)時(shí)進(jìn)行代碼格式驗(yàn)證,在 commit-msg 觸發(fā)時(shí)對(duì) commit 消息和提交用戶進(jìn)行驗(yàn)證,在 pre-push 觸發(fā)時(shí)進(jìn)行單元測(cè)試、e2e 測(cè)試等操作。
Git 在執(zhí)行 git init 進(jìn)行初始化時(shí),會(huì)在 .git/hooks 目錄生成一系列的 hooks 腳本:

從上圖可以看到每個(gè)腳本的后綴都是以 .sample 結(jié)尾的,在這個(gè)時(shí)候,腳本是不會(huì)自動(dòng)執(zhí)行的。我們需要把后綴去掉之后才會(huì)生效,即將 pre-commit.sample 變成 pre-commit 才會(huì)起作用。
本文主要是想介紹一下如何編寫 git hooks 腳本,并且會(huì)編寫兩個(gè) pre-commit、commit-msg 腳本作為示例,幫助大家更好的理解 git hooks 腳本。當(dāng)然,在工作中還是建議使用現(xiàn)成的、開源的解決方案 husky[1]。
正文
用于編寫 git hooks 的腳本語言是沒有限制的,你可以用 nodejs、shell、python、ruby等腳本語言,非常的靈活方便。
下面我將用 shell 語言來演示一下如何編寫 pre-commit 和 commit-msg 腳本。另外要注意的是,在執(zhí)行這些腳本時(shí),如果以非零的值退出程序,將會(huì)中斷 git 的提交/推送流程。所以在 hooks 腳本中驗(yàn)證消息/代碼不通過時(shí),就可以用非零值進(jìn)行退出,中斷 git 流程。
exit 1
pre-commit
在 pre-commit 鉤子中要做的事情特別簡(jiǎn)單,只對(duì)要提交的代碼格式進(jìn)行檢查,因此腳本代碼比較少:
npm run lint
# 獲取上面腳本的退出碼exitCode="$?"exit $exitCode
由于我在項(xiàng)目中已經(jīng)配置好了相關(guān)的 eslint 配置以及 npm 腳本,因此在 pre-commit 中執(zhí)行相關(guān)的 lint 命令就可以了,并且判斷一下是否正常退出。
// 在 package.json 文件中已配置好 lint 命令"scripts": {"lint": "eslint --ext .js src/"},
下面看一個(gè)動(dòng)圖,當(dāng)代碼格式不正確的時(shí)候,進(jìn)行 commit 就報(bào)錯(cuò)了:

在修改代碼格式后再進(jìn)行提交,這時(shí)就不報(bào)錯(cuò)了:

從動(dòng)圖中可以看出,這次 commit 已正常提交了。
commit-msg
在 commit-msg hooks 中,我們需要對(duì) commit 消息和用戶進(jìn)行校驗(yàn)。
# 用 `` 可以將命令的輸出結(jié)果賦值給變量# 獲取當(dāng)前提交的 commit msgcommit_msg=`cat $1`
# 獲取用戶 emailemail=`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 ]]thenecho "\n不合法的 commit 消息提交格式,請(qǐng)使用正確的格式:\\nfeat: add comments\\nfix: handle events on blur (close #28)\\n詳情請(qǐng)查看 git commit 提交規(guī)范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md"
# 異常退出exit 1fi
在 commit-msg 鉤子觸發(fā)時(shí),對(duì)應(yīng)的腳本會(huì)接收到一個(gè)參數(shù),這個(gè)參數(shù)就是 commit 消息,通過 cat $1 獲取,并賦值給 commit_msg 變量。
驗(yàn)證 commit 消息的正則比較簡(jiǎn)單,看代碼即可。如果對(duì) commit 提交規(guī)范有興趣,可以看看我另一篇文章[2]。
對(duì)用戶權(quán)限做判斷則比較簡(jiǎn)單,只需要檢查用戶的郵箱或用戶名就可以了(假設(shè)現(xiàn)在只有 abc 公司的員工才有權(quán)限提交代碼)。
email_re="@abc\.com"if [[ ! $email =~ $email_re ]]thenecho "此用戶沒有權(quán)限,具有權(quán)限的用戶為:[email protected]"
# 異常退出exit 1fi
下面用兩個(gè)動(dòng)圖來分別演示一下校驗(yàn) commit 消息和判斷用戶權(quán)限的過程:


設(shè)置 git hooks 默認(rèn)位置
腳本可以正常執(zhí)行只是第一步,還有一個(gè)問題是必須要解決的,那就是如何和同一項(xiàng)目的其他開發(fā)人員共享 git hooks 配置。因?yàn)?nbsp;.git/hooks 目錄不會(huì)隨著提交一起推送到遠(yuǎn)程倉(cāng)庫(kù)。對(duì)于這個(gè)問題有兩種解決方案:第一種是模仿 husky 做一個(gè) npm 插件,在安裝的時(shí)候自動(dòng)在 .git/hooks 目錄添加 hooks 腳本;第二種是將 hooks 腳本單獨(dú)寫在項(xiàng)目中的某個(gè)目錄,然后在該項(xiàng)目安裝依賴時(shí),自動(dòng)將該目錄設(shè)置為 git 的 hooks 目錄。
接下來詳細(xì)說說第二種方法的實(shí)現(xiàn)過程:
1.在 npm install 執(zhí)行完成后,自動(dòng)執(zhí)行 git config core.hooksPath hooks 命令。 2.git config core.hooksPath hooks 命令將 git hooks 目錄設(shè)置為項(xiàng)目根目錄下的 hooks 目錄。
"scripts": {"lint": "eslint --ext .js src/","postinstall": "git config core.hooksPath hooks"},
踩坑
demo 源碼在 windows 上是可以正常運(yùn)行的,后來?yè)Q成 mac 之后就不行了,提交時(shí)報(bào)錯(cuò):
hint: The 'hooks/pre-commit' hook was ignored because it's not set as executable.
原因是 hooks 腳本默認(rèn)為不可執(zhí)行,所以需要將它設(shè)為可執(zhí)行:
chmod 700 hooks/*
為了避免每次克隆項(xiàng)目都得修改,最好將這個(gè)命令在 npm 腳本上加上:
"scripts": {"lint": "eslint --ext .js src/","postinstall": "git config core.hooksPath hooks && chmod 700 hooks/*"},
當(dāng)然,如果是 windows 就不用加后半段代碼了。
nodejs hooks 腳本
為了幫助前端同學(xué)更好的理解 git hooks 腳本,我用 nodejs 又重寫了一版。
pre-commit
const childProcess = require('child_process');
try {childProcess.execSync('npm run lint');} catch (error) {console.log(error.stdout.toString());process.exit(1);}
commit-msg
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 對(duì)應(yīng)的 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 消息格式,請(qǐng)使用正確的提交格式:');console.error('feat: add \'comments\' option');console.error('fix: handle events on blur (close #28)');console.error('詳情請(qǐng)查看 git commit 提交規(guī)范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md。');process.exit(1);}
if (!/@qq\.com$/.test(email)) {console.error('此用戶沒有權(quán)限,具有權(quán)限的用戶為:[email protected]');process.exit(1);}
總結(jié)
其實(shí)本文適用的范圍不僅僅局限于前端,而是適用于所有使用了 git 作為版本控制的項(xiàng)目。例如安卓、ios、Java 等等。只是本文選擇了前端項(xiàng)目作為示例。
最近附上項(xiàng)目源碼:https://github.com/woai3c/git-hooks-demo
參考資料
? 自定義 Git - 使用強(qiáng)制策略的一個(gè)例子[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 - 使用強(qiáng)制策略的一個(gè)例子: 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
???????????????? END ????????????????
推薦閱讀
【1】jetson nano開發(fā)使用的基礎(chǔ)詳細(xì)分享
【2】Linux開發(fā)coredump文件分析實(shí)戰(zhàn)分享
【3】CPU中的程序是怎么運(yùn)行起來的 必讀
【4】cartographer環(huán)境建立以及建圖測(cè)試
【5】設(shè)計(jì)模式之簡(jiǎn)單工廠模式、工廠模式、抽象工廠模式的對(duì)比
本公眾號(hào)全部原創(chuàng)干貨已整理成一個(gè)目錄,回復(fù)[ 資源 ]即可獲得。
