手寫 git hooks 腳本(pre-commit、commit-msg)

2021-12-24 前端編程技術分享
簡介

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

相關焦點

  • Git commit message規範制定與實踐
    /scripts/git/commit-msg.js -E HUSKY_GIT_PARAMS",            "pre-commit": "pretty-quick --staged"        }    }pre-commitgit commit 之前會觸發 pre-commit hooks,執行腳本 pretty-quick
  • 如何規範你的Git commit?
    以上就是我們整個監控服務的相關內容,告警信息通過如下形式發送到對應的釘釘群裡:我們也有整體git commit的統計,統計個人的提交次數、不規範次數、不規範率等如下圖:git hooks分為客戶端hook和服務端hook。客戶端hook又分為pre-commit、prepare-commit-msg、commit-msg、post-commit等,主要用於控制客戶端git的提交工作流。
  • GIT實踐之生成規範的commit message
    以後,凡是用到git commit命令,一律改為使用git cz。這時,就會出現選項,用來生成符合格式的 Commit message。四、validate-commit-msgNode 插件 validate-commit-msg 來檢查項目中 Commit message 是否規範,插件安裝在項目中.git的hook
  • Git 提交的正確姿勢:Commit message 編寫指南
    $ git commit -m "hello world"上面代碼的-m參數,就是用來指定 commit mesage 的。如果一行不夠,可以只執行git commit,就會跳出文本編譯器,讓你寫多行。
  • 前端必須知道的Git和SVN的區別
    /hooks/commit-msg.sample./hooks/pre-rebase.sample./hooks/pre-commit.sample./hooks/applypatch-msg.sample./hooks/fsmonitor-watchman.sample./hooks/pre-receive.sample.
  • 前端必須知道的 Git 和 SVN 的區別
    git在處理代碼方面功能比svn要強大一些3..git文件動態分析3.1 add階段1.執行git init會生成一個初始化的.git,會發現上面有些目錄文件沒有,因為有些文件是指定的命令後才會生成2.新建一個test.txt,隨便寫點內容,執行git statusOn branch master  // 默認一個master 分支No co
  • Git提交的正確姿勢:Commit message 和 Change log 編寫指南
    $ git commit -m "hello world"上面代碼的-m參數,就是用來指定 commit mesage 的。如果一行不夠,可以只執行git commit,就會跳出文本編譯器,讓你寫多行。
  • 這糟糕的git commit記錄
    先來簡單嘗試一下,隨意一個個提交git commit -m "feat: xxxx"安裝自動生成 Changelog 的組件,npm 自行安裝npm install -g conventional-changelog
  • 使用 pre-commit 配合 black、isort 自動格式化 Python 文件
    為了保證 Python 的代碼規範,在使用 `git commit` 提交代碼之前,需要使用 blake
  • 如何在項目中使用git-hooks
    比如pre-commit.sample pre-push.sample。回到正題,我們期望在 git 提交(commit)前,對我們的代碼進行檢測,如果不能通過檢測,就無法提交我們的代碼。那麼自然而然的,這個動作的時機應該是pre commit,也就是 commit 之前。
  • Git commit message規範
    全局安裝commitizen & cz-conventional-changelogcommitizen是一個撰寫合格commit message的工具,用於代替git commit 指令,而cz-conventional-changelog適配器提供conventional-changelog標準(約定式提交標準)。基於不同需求,也可以使用不同適配器。
  • Git-初始化代碼倉庫
    # 左邊執行$ mkdir git-demo$ cd git-demo && git init$ rm -rf .git/hooks/*.sample# 右邊執行$ watch -n 1 -d find .
  • 優雅的提交你的 Git Commit Message
    git commit 模板如果你只是個人的項目, 或者想嘗試一下這樣的規範格式, 那麼你可以為 git 設置 commit template, 每次 git commit 的時候在 vim 中帶出, 時刻提醒自己:修改 ~/.gitconfig, 添加:[commit]template = ~/.gitmessage新建 ~/.
  • Git命令解析 - init、add、commit
    中的忽略模式description//文件- 僅供GitWeb 程序使用hooks //目錄- 存放可在某些指令前後觸發運行的鉤子腳本(hook scripts),默認包含一些腳本樣例refs//目錄- 存儲各個分支指向的目標提交
  • 如何寫好 commit message
    🤔git log 不會處理換行,因此如果行太長則很難閱讀。如果提交的描述一行的長度不限於 72 個字符 那麼提交信息將跨越命令行的整個寬度,使 commit message 難以閱讀。以下是當您不限制提交正文長度時,提交消息在 git 中的樣子。
  • git hooks在哪裡?如何使用husky?
    重點內容一 git hooks對於前端開發人員來說,很多人每天都在用git,可能也聽說過git hooks,git 的鉤子方法。但是仍然有一部分同學不知道它到底是做什麼用的。如果對linux命令稍微熟悉一點,可以在你本地的項目文件夾下執行如下命令。
  • 用了5年的Git,你竟然還不曉得它的實現原理!
    $ git init demo1 && cd demo1$ tree .git.git├── HEAD├── config├── description├── hooks│ ├── applypatch-msg.sample│ ├── commit-msg.sample│ ├── fsmonitor-watchman.sample
  • git commit emoji 使用指南
    README.md執行 git commit 時使用 emoji 為本次提交打上一個 "標籤", 使得此次 commit 的主要工作得以凸現,也能夠使得其在整個提交歷史中易於區分與查找。截取的 gitmoji 快照:
  • Git Commit Log 的小型團隊最佳實踐
    這些良莠不齊的commit log充斥在我們的項目中,不僅影響了查閱的效果,還會對code review產生負面的影響。因此,本文是意圖從commit log的書寫規範入手,並提供相應的解決方案。撰寫工具有工具輔助,一定比手寫好,這裡我們使用Commitizen這個庫。
  • git commit --amend 用法
    文件,修改好之後,git add 該出問題.java然後git commit –-amend –-no-edit最後git push origin HEAD:refs/for/branches當我們想要對上一次的提交進行修改時,我們可以使用 git commit –-amend 命令。