IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    一个 git 仓库下拥有多个项目的 git hooks 配置方案

    谭光志发表于 2023-08-01 16:38:25
    love 0

    前言

    通常情况下,一个 git 仓库就是一个项目,只需要配置一套 git hooks 脚本就可以执行各种校验任务。对于 monorepo 项目也是如此,monorepo 项目下的多个 packages 之间,它们是有关联的,可以互相引用,所以当成一个项目也没问题。

    但是也有一种情况,一个 git 仓库下的多个项目之间是彼此独立的,比如 git 仓库下存在前端项目、后端项目、文档项目等等。这时候就需要为每个项目配置不同的 git hooks 脚本了,因为不同的项目有可能校验规则不一样。

    本文主要探讨一下如何为不同的项目配置 git hooks 脚本。

    PS:配置 git hooks 脚本使用 huksy。

    方案一:每个项目下都配置一套 git hooks 脚本

    假设仓库拥前后端两个项目:

    frontend
    backend

    那么我们需要在每个项目下安装 husky,同时要在 package.json 中配置一下 prepare 脚本(这里以前端项目为示例):

    # package.json
    {
        "scripts" {
            "prepare": "cd .. && husky install frontend/.husky"
        }
    }

    然后按照 husky 文档创建 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

    上面展示的是前端项目的 git hooks 创建过程,后端项目按照同样的过程创建即可。目前仓库的目录结构如下:

    frontend
        .husky
            - pre-commit
            - commit-msg
    backend
        .husky
            - pre-commit
            - commit-msg

    运行一段时间后,发现这个方案有问题,那就是每次触发的 git hooks 脚本都是前端项目的,后端项目提交代码根本不触发 git hooks。排查问题后发现是 git 仓库的配置引起的,打开 .git/config 文件:

    [core]
        hooksPath = frontend/.husky

    上面 hooksPath 路径对应的就是 git hooks 的目录位置,目前 git 只支持指定一个目录作为 git hooks 的位置。所以第一个方案不靠谱,达不到我们想要的效果。

    方案二:只在根目录下配置一套 git hooks 脚本

    第二个方案是将 git hooks 放在项目根目录下,统一在根目录里执行各个子项目的校验脚本。这个方案有以下几个步骤:

    修改 husky 安装位置

    在每个项目下安装 husky 时,要把 git hooks 钩子目录设置在根目录:

    # package.json
    {
        "scripts" {
            "prepare": "cd .. && husky install .husky" # 放到根目录
        }
    }

    同时 .git/config 文件也要修改一下:

    [core]
        hooksPath = .husky # 改为根目录

    在 git hooks 中进行各个子项目的校验操作

    这里以 commit-msg 作为示例编写一个脚本:

    #!/usr/bin/env sh
    . "$(dirname -- "$0")/_/husky.sh"
    
    # 拿到所有改动的文件名
    changedFiles=$(git diff --cached --name-only --diff-filter=ACM)
    
    # 判断目录是否改动
    isBackendChanged=false
    isFrontendChanged=false
    
    for file in $changedFiles
    do
        if [[ $file == frontend/* ]]
        then
            isFrontendChanged=true
        elif [[ $file == backend/* ]]
        then
            isBackendChanged=true
        fi
    done
    
    # 改动的目录需要执行校验命令
    # $1 $2 代表传给函数的第一个、第二个参数
    execTask() {
        echo "root $1 commit-msg"
        cd $1
        FORCE_COLOR=1 node scripts/verifyCommitMsg.mjs $2
    }
    
    if $isFrontendChanged
    then
        execTask "frontend" $1 & # 使用 & 让任务在后台执行
        task1=$! # 保存任务 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."

    上面脚本的逻辑是这样的:

    1. 每次 git 提交代码时,判断一下当前所有改动的文件是属于哪个项目
    2. 文件发生改动的项目需要执行校验任务
    3. 每个校验任务都使用子进程去执行
    4. 等待所有校验任务执行结束后,输出 All tasks finished.

    测试一段时间后,发现第二个方案没发生什么问题,完全满足需求。



沪ICP备19023445号-2号
友情链接