前言
通常情況下,一個(gè) git 倉(cāng)庫(kù)就是一個(gè)項(xiàng)目,只需要配置一套 git hooks 腳本就可以執(zhí)行各種校驗(yàn)任務(wù)。對(duì)于 monorepo 項(xiàng)目也是如此,monorepo 項(xiàng)目下的多個(gè) packages 之間,它們是有關(guān)聯(lián)的,可以互相引用,所以當(dāng)成一個(gè)項(xiàng)目也沒(méi)問(wèn)題。
但是也有一種情況,一個(gè) git 倉(cāng)庫(kù)下的多個(gè)項(xiàng)目之間是彼此獨(dú)立的,比如 git 倉(cāng)庫(kù)下存在前端項(xiàng)目、后端項(xiàng)目、文檔項(xiàng)目等等。這時(shí)候就需要為每個(gè)項(xiàng)目配置不同的 git hooks 腳本了,因?yàn)椴煌捻?xiàng)目有可能校驗(yàn)規(guī)則不一樣。
本文主要探討一下如何為不同的項(xiàng)目配置 git hooks 腳本。
PS:配置 git hooks 腳本使用 huksy。
方案一:每個(gè)項(xiàng)目下都配置一套 git hooks 腳本
假設(shè)倉(cāng)庫(kù)擁前后端兩個(gè)項(xiàng)目:
frontend
backend
那么我們需要在每個(gè)項(xiàng)目下安裝 husky
,同時(shí)要在 package.json
中配置一下 prepare
腳本(這里以前端項(xiàng)目為示例):
# package.json
{
"scripts" {
"prepare": "cd .. && husky install frontend/.husky"
}
}
然后按照 husky
文檔創(chuàng)建 pre-commit
和 commit-msg
鉤子文件:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# pre-commit
cd frontend
npx lint-staged
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# commit-msg
cd frontend
FORCE_COLOR=1 node scripts/verifyCommitMsg.mjs $1
上面展示的是前端項(xiàng)目的 git hooks 創(chuàng)建過(guò)程,后端項(xiàng)目按照同樣的過(guò)程創(chuàng)建即可。目前倉(cāng)庫(kù)的目錄結(jié)構(gòu)如下:
frontend
.husky
- pre-commit
- commit-msg
backend
.husky
- pre-commit
- commit-msg
運(yùn)行一段時(shí)間后,發(fā)現(xiàn)這個(gè)方案有問(wèn)題,那就是每次觸發(fā)的 git hooks 腳本都是前端項(xiàng)目的,后端項(xiàng)目提交代碼根本不觸發(fā) git hooks。排查問(wèn)題后發(fā)現(xiàn)是 git 倉(cāng)庫(kù)的配置引起的,打開(kāi) .git/config
文件:
[core]
hooksPath = frontend/.husky
上面 hooksPath
路徑對(duì)應(yīng)的就是 git hooks 的目錄位置,目前 git 只支持指定一個(gè)目錄作為 git hooks 的位置。所以第一個(gè)方案不靠譜,達(dá)不到我們想要的效果。
方案二:只在根目錄下配置一套 git hooks 腳本
第二個(gè)方案是將 git hooks 放在項(xiàng)目根目錄下,統(tǒng)一在根目錄里執(zhí)行各個(gè)子項(xiàng)目的校驗(yàn)?zāi)_本。這個(gè)方案有以下幾個(gè)步驟:
修改 husky 安裝位置
在每個(gè)項(xiàng)目下安裝 husky 時(shí),要把 git hooks 鉤子目錄設(shè)置在根目錄:
# package.json
{
"scripts" {
"prepare": "cd .. && husky install .husky" # 放到根目錄
}
}
同時(shí) .git/config
文件也要修改一下:
[core]
hooksPath = .husky # 改為根目錄
在 git hooks 中進(jìn)行各個(gè)子項(xiàng)目的校驗(yàn)操作
這里以 commit-msg
作為示例編寫(xiě)一個(gè)腳本:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 拿到所有改動(dòng)的文件名
changedFiles=$(git diff --cached --name-only --diff-filter=ACM)
# 判斷目錄是否改動(dòng)
isBackendChanged=false
isFrontendChanged=false
for file in $changedFiles
do
if [[ $file == frontend/* ]]
then
isFrontendChanged=true
elif [[ $file == backend/* ]]
then
isBackendChanged=true
fi
done
# 改動(dòng)的目錄需要執(zhí)行校驗(yàn)命令
# $1 $2 代表傳給函數(shù)的第一個(gè)、第二個(gè)參數(shù)
execTask() {
echo "root $1 commit-msg"
cd $1
FORCE_COLOR=1 node scripts/verifyCommitMsg.mjs $2
}
if $isFrontendChanged
then
execTask "frontend" $1 & # 使用 & 讓任務(wù)在后臺(tái)執(zhí)行
task1=$! # 保存任務(wù) id
fi
if $isBackendChanged
then
execTask "backend" $1 &
task2=$!
fi
if [[ -n $task1 ]]; then
wait $task1
fi
if [[ -n $task2 ]]; then
wait $task2
fi
echo "All tasks finished."
上面腳本的邏輯是這樣的:
- 每次 git 提交代碼時(shí),判斷一下當(dāng)前所有改動(dòng)的文件是屬于哪個(gè)項(xiàng)目
- 文件發(fā)生改動(dòng)的項(xiàng)目需要執(zhí)行校驗(yàn)任務(wù)
- 每個(gè)校驗(yàn)任務(wù)都使用子進(jìn)程去執(zhí)行
- 等待所有校驗(yàn)任務(wù)執(zhí)行結(jié)束后,輸出
All tasks finished.
pre-push 腳本編寫(xiě)
與 pre-commit
和 commit-msg
不同,在 pre-push
鉤子中需要通過(guò)其他方式來(lái)拿到發(fā)生改動(dòng)的文件,大家直接看代碼:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 判斷目錄是否改動(dòng)
isFrontendChanged=false
isComponentTemplateChanged=false
isComponentAttrPanelChanged=false
# 獲取遠(yuǎn)程倉(cāng)庫(kù)的名字和 URL
remote="$1"
url="$2"
# 定義一個(gè)空的 git 哈希值
z40=0000000000000000000000000000000000000000
# 這個(gè)循環(huán)從 stdin 讀取數(shù)據(jù),這些數(shù)據(jù)是 git 在調(diào)用 pre-push 鉤子時(shí)傳遞的。
# 每一行數(shù)據(jù)包括 4 個(gè)字段:本地引用名,本地最新的提交哈希值,遠(yuǎn)程引用名,遠(yuǎn)程最新的提交哈希值。
while read local_ref local_sha remote_ref remote_sha
do
# 這段代碼檢查是否正在刪除一個(gè)引用(例如,刪除一個(gè)分支)。如果是,那么本地的 sha 值將被設(shè)置為一個(gè)空哈希值。
if [ "$local_sha" = $z40 ]
then
# Handle delete
:
else
# 這段代碼確定要檢查哪些提交。如果遠(yuǎn)程的 sha 值是一個(gè)空哈希值,那么我們正在創(chuàng)建一個(gè)新的引用,所以我們需要檢查所有的提交。
# 否則,我們正在更新一個(gè)已經(jīng)存在的引用,所以我們只需要檢查新的提交。
if [ "$remote_sha" = $z40 ]
then
# New branch, examine all commits
range="$local_sha"
else
# Update to existing branch, examine new commits
range="$remote_sha..$local_sha"
fi
# 這個(gè)循環(huán)對(duì)每一個(gè)包含在 range 變量中的提交執(zhí)行 git rev-list 命令,這個(gè)命令會(huì)返回一系列的提交哈希值。
# 然后,對(duì)每個(gè)提交,我們使用 git diff-tree 命令來(lái)找到在那個(gè)提交中修改的文件。這些文件的名字被存儲(chǔ)在 files 變量中。
for commit in $(git rev-list "$range"); do
# 拿到所有改動(dòng)的文件名
files=$(git diff-tree --no-commit-id --name-only -r $commit)
for file in $files
do
if [[ $file == frontend/* ]]
then
isFrontendChanged=true
elif [[ $file == component-attr-panel/* ]]
then
isComponentAttrPanelChanged=true
elif [[ $file == component-template/* ]]
then
isComponentTemplateChanged=true
fi
done
done
fi
done
# 改動(dòng)的目錄需要執(zhí)行校驗(yàn)命令
execTask() {
echo "root $1 pre-push"
cd $1
npm run type-check
}
if $isFrontendChanged
then
execTask "frontend" & # 使用 & 讓任務(wù)在后臺(tái)執(zhí)行
task1=$! # 保存任務(wù) id
fi
if $isComponentTemplateChanged
then
execTask "component-template" &
task2=$!
fi
if $isComponentAttrPanelChanged
then
execTask "component-attr-panel" &
task3=$!
fi
if [[ -n $task1 ]]; then
wait $task1
fi
if [[ -n $task2 ]]; then
wait $task2
fi
if [[ -n $task3 ]]; then
wait $task3
fi
echo "All tasks finished."
測(cè)試一段時(shí)間后,發(fā)現(xiàn)第二個(gè)方案沒(méi)發(fā)生什么問(wèn)題,完全滿足需求。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-657835.html
PS:在寫(xiě)腳本的時(shí)候要注意各個(gè)任務(wù)是否能并發(fā)執(zhí)行,比如 lint-staged
這個(gè)任務(wù)就不能并發(fā)執(zhí)行,所以在編寫(xiě) pre-commit
腳本執(zhí)行代碼校驗(yàn)的時(shí)候,得改為串行。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-657835.html
到了這里,關(guān)于一個(gè) git 倉(cāng)庫(kù)下?lián)碛卸鄠€(gè)項(xiàng)目的 git hooks 配置方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!