提交规范靠吼没用,看我用“shell+husky螺丝刀”,一键给40多个项目上锁

背景

最近公司一声令下,开始搞"标准化开发"大行动,意思是:代码可以不惊艳,但提交必须整整齐齐、仪式感拉满。于是,一份"神圣不可更改"的规范文档横空出世,赫然写着:

  • Git 分支名必须像早操队伍一样整齐划一;
  • 提交信息不能胡写,必须有理有据、感情真挚;
  • 提交信息必须绑定 Issue ID,不然你都不知道自己为哪桩需求流的汗;
  • 提交代码前,还得通过 ESLint 的"质检",不然就别想进主仓。

规矩定得挺好,但问题来了------谁来确保这套规则不是贴在墙上的"企业文化标语"?

这个"项目督查"的荣誉任务,砸到了我的头上。

好在我和 Husky 有点过节------啊不,是革命友谊。当年用它给项目提交代码时"上纲上线",快,准,稳。现在领导提的这些功能,怎么看都像是 husky 的拿手好戏,妥妥的熟面孔,不慌!

但,故事总得有点挑战才精彩------这次不是给一个项目搞定就完事,而是......四十多个项目!

一个个手动配置?那得配到退休。

幸好我懂点 shell 脚本,husky 配 shell,那简直是"黑科技联姻",兵器谱前十都有它们的位置。就这样,我撸起袖子,开干。

整体思路

想象你是一个偏爱效率(也稍微有点懒)的开发者,眼前有 40 多个项目等着你去"上锁" ------ 锁什么?当然是锁代码提交规范、代码质量底线,还有开发者随心所欲的"放飞自我"!

一个个手动配置?那怕是要从青春配到秃顶。你需要帮手,于是灵机一动,写下了一段全自动驯狗大师脚本(别慌,说的是那只叫 Husky 🐶 的"钩子狗"),让它替你在每个项目里立下规矩、震慑八方。


🔒 脚本的整体思路是这样的:

  1. 问你用的什么狗粮(包管理工具)

    脚本先抬头看你一眼:你到底是 npmyarn 还是 pnpm 派的?不给狗粮不干活!

  2. 喂它 git-hooks-config(远程配置)

    它拿着你选的工具,去远程仓库拉取"训狗秘籍"(git-hooks-config),也就是一堆标准化配置,省得你一个项目一个项目配得吐血。

  3. 检查秘籍里缺不缺"内功心法"

    它偷偷翻了下 package.json,看看有没有把秘籍中定义的必备依赖(devDependencies)都装上------这些可是 Husky 能否顺利开口"咬人"的关键。缺了?那赶紧装上,不然就是没有牙齿的看家犬!

  4. 开始训狗(安装 husky)

    它比较"喜新厌旧"------一看你家角落还蹲着只旧 Husky,立刻开门送走(rm -rf .husky),转身就牵来一只全新的,重新从零训练,绝不恋旧,只认新欢!

  5. 复制招式(配置文件和 hooks)

    把那些"拷打开发者提交"的钩子(hook)及其附属配置文件commitlint.config.js,header-pattern 通通拷进项目里,放好位置,安排妥当。

  6. 狗训完了,把秘籍销毁

    一切搞定后, 它最后很讲究,把那本"git-hooks-config"秘籍原地销毁(卸载),只留下精华在项目里,一点也不拖泥带水, 优雅退场。

bash 复制代码
#!/bin/bash
set -e
export PUPPETEER_SKIP_DOWNLOAD=true

TOOL=$1

if [ -z "$TOOL" ]; then
  echo "❌ 请输入要使用的包管理工具,例如:bash init-git-hooks.sh pnpm"
  exit 1
fi

INSTALL_CMD=""
ADD_CMD=""
case $TOOL in
npm)
  INSTALL_CMD="npm install git+ssh://git@gitee.com:getbetter/git-hooks-config.git -D"
  ADD_CMD="npm install -D"
  ;;
yarn)
  INSTALL_CMD="yarn add git+ssh://git@gitee.com:getbetter/git-hooks-config.git -D"
  ADD_CMD="yarn add -D"
  ;;
pnpm)
  INSTALL_CMD="pnpm add git+ssh://git@gitee.com:getbetter/git-hooks-config.git -D"
  ADD_CMD="pnpm add -D"
  ;;
*)
  echo "❌ 不支持的包管理工具: $TOOL"
  exit 1
  ;;
esac

echo "🔧 使用 $TOOL 安装 git-hooks-config 包..."
eval "$INSTALL_CMD"

echo "📦 动态解析 git-hooks-config 中的 devDependencies 并安装到主项目中..."
PKG_PATH="node_modules/git-hooks-config/package.json"

if [ ! -f "$PKG_PATH" ]; then
  echo "❌ 未找到 package.json: $PKG_PATH"
  exit 1
fi

DEPS=$(node -e "const deps=require('./$PKG_PATH').devDependencies; console.log(Object.entries(deps).map(([k,v])=>k+'@'+v).join(' '))")

if [ -z "$DEPS" ]; then
  echo "⚠️  未发现依赖需要安装"
else
  echo "➡️  安装依赖: $DEPS"
  eval "$ADD_CMD $DEPS"
fi

echo "✅ 安装 husky..."
if [ -d ".husky" ]; then
  echo "🔄 检测到已有 .husky 目录,正在删除..."
  rm -rf .husky
fi
npx husky init

echo "📂 拷贝 hooks 和配置文件到当前项目..."
cp -r node_modules/git-hooks-config/.husky/* .husky/
cp node_modules/git-hooks-config/commitlint.config.js commitlint.config.js
cp node_modules/git-hooks-config/header-pattern.cjs header-pattern.cjs

case $TOOL in
npm)
  REMOVE_CMD="npm uninstall git-hooks-config"
  ;;
yarn)
  REMOVE_CMD="yarn remove git-hooks-config"
  ;;
pnpm)
  REMOVE_CMD="pnpm remove git-hooks-config"
  ;;
*)
  echo "❌ 不支持的包管理工具: $TOOL"
  exit 1
  ;;
esac

eval "$REMOVE_CMD"
echo "🎉 Git Hooks 配置完成"

训练大纲

别以为把 Husky 装上就万事大吉,它只是个忠诚的执行者,关键还是得有人告诉它该怎么"管人"------是时候祭出我们为它量身定制的《训练大纲》了!

这套大纲一共三式,招招致命、环环相扣,像极了那种武侠小说里的"入门-进阶-终极"三重修炼法。


👮 第一式:.husky/pre-commit · 分支取名不能乱,代码质量不能差

这招的核心思想是:"人可以不帅,分支名必须规范;你可以摸鱼,但代码得过质检。"

脚本一出,先抓住你的分支名瞪一眼:

"你这叫啥?fix-final-final-last-v2??赶紧改成 bugfix/123-修复登录bug!"

接着再盯着你最新提交的 .ts.tsx.vue 文件扫一圈,只要 ESLint 一皱眉,它立马拉你回炉重做:

"风格不对别想走,代码不齐别想提!"

可以说是从分支命名到代码质量,里内管制

bash 复制代码
#!/bin/bash
# set -e
# set -x
#trap 'echo "Error at line $LINENO: $BASH_COMMAND"; exit 1' ERR

# Branch命名检查
# 获取当前Branch的名称
JenkinsBranchName=$1
if [ -n "$JenkinsBranchName" ]; then
  CURRENT_BRANCH="${JenkinsBranchName}" # Jenkins获取分支名
else
  CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) # 本地获取分支名
fi
echo "当前分支名称: $CURRENT_BRANCH"

# 检查是否成功获取Branch名称
if [ -z "$CURRENT_BRANCH" ]; then
  echo "错误:无法获取当前Branch名称。请确保在Git仓库中运行此脚本。"
  exit 1
fi

# 定义Branch命名规范的正则表达式
# 获取公共配置PATTERN
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh"

# 校验分支名称
if ! echo "$CURRENT_BRANCH" | grep -Pq "$PATTERN"; then

  arr=(
    "(1)feat/215-用户登录界面;"
    "(2)hotfix/284-修复用户名重复问题;   "
    "(3)bugfix/233-修复404问题;   "
    "(4)release/v1.0.0;|release/20250516;    "
    "(5)master;   "
    "(5)dev")

  # 使用分号连接
  joined=$(IFS=';' echo "${arr[*]}")

  echo "分支名称 '$CURRENT_BRANCH' 不符合命名规范。命名规则:[issue_type]/[issue_id]-description, 请使用以下命名规范之一:$joined"

  exit 1
fi

# 注意这里加了 || true
files=$(git diff --cached --name-only --diff-filter=AM | grep -E '\.(ts|tsx|vue)$' || true)

if [ -z "$files" ]; then
  exit 0
fi

set +e
output=$(echo "$files" | xargs -r npx eslint 2>&1)
exit_code=$?

if [ $exit_code -ne 0 ]; then
  echo "ESLint 检查未通过,请修复后再提交!"
  echo "$output"
  exit $exit_code
fi

👨‍⚖️ 第二式:.husky/commit-msg · 文案不走心,脚本揪着你重写

你以为名字过关就能安心 commit?太天真。

这个阶段,Husky 摇身一变,成了一个吹毛求疵的主编,一边看着你提交信息一边念叨:

"不加类型、不带议题 ID?感情不真挚,态度不严谨,退回!"

它连 Git 的"自动提交"都替你考虑周全了:像 Merge branchsquash 这种系统自动生成的提交信息,它都会识趣地选择跳过检查,毕竟这些不是你亲手写的,其余情况嘛------一律按模板来,不讲情面。

而你要是实在记不住格式,别怕,它还贴心地提供了一整套例子模板让你对照参考(如下所示),温柔又坚定:

yaml 复制代码
(1) feat: 新增用户登录功能 #123  
(2) fix: 修复登录跳转错误 #456  
...
bash 复制代码
#!/bin/bash
commit_msg=$(cat "$1")
# 如果提交信息包含 'Merge branch' 或 'Merge pull request',则跳过 commitlint 检查
if echo "$commit_msg" | grep -qE '^Merge (branch|remote|pull request)'; then
  exit 0
fi

if [ "$2" = "merge" ] || [ "$2" = "squash" ]; then
  exit 0
fi

commit_msg=$(cat "$1")
npx --no -- commitlint --edit "$1" >/dev/null || {

  arr=(
    "(1)feat: 新增用户登录功能 #议题Id1#议题Id2;   "
    "(2)fix: 修复登录跳转错误 #议题Id1#议题Id2;   "
    "(3)docs: 更新项目文档 #议题Id1#议题Id2;  "
    "(4)chore: 升级npm依赖库 #议题Id1#议题Id2; "
    "(5)perf: 优化页面加载速度 #议题Id1#议题Id2; "
    "(6)refactor: 用户登录重构 #议题Id1#议题Id2;"
    "(7)style: 用户登录样式调整 #议题Id1#议题Id2; "
    "(8)test: 用户登录测试 #议题Id1#议题Id2; "
    "(9)revert: 撤销用户登录提交 #议题Id1#议题Id2;"
  )

  joined=$(IFS=';' echo "${arr[*]}")
  echo "提交信息 '$commit_msg' 不符合规范,请使用规范的格式提交!正确示例:${joined}"
  exit 1
}

issues=$(echo "$commit_msg" | grep -oE '#[0-9]+' | sort -u | tr -d '\n')

chmod +x "$(dirname "$0")/check-issue"
echo "commit_msg: $issues"
"$(dirname "$0")/check-issue" "$issues"
exit $?

🕵️ 第三式: .husky/check-issue · 瞎写 Issue-ID?它可不吃这套!

到了终极验收阶段------你以为在提交信息里图省事,随便捏造几个不存在的 Issue ID 就能糊弄过去?不行!脚本表示:没有调查就没有发言权,它得亲自去 GitLab 核实每一个 ID 的真伪。

它会穿越你配置的多个项目路径,只要 Issue ID 在任意一个项目里存在,就算你过关 ;但如果有哪个 ID 在所有项目里都查无此号,那它立马当场发难:

❌ "#274?你这不是糊弄我吗?我把所有项目都翻遍了,连根毛都没找到!"

只有当所有 Issue ID 至少各自能在一个项目里落脚,脚本才会欣然点头、拍案通过:

🎉 "完美!所有议题均已实名验证,提交合法,驯狗三连通关成功!"

bash 复制代码
#!/bin/bash
set -e
# 读取公共常量配置参数 GITLAB_URL + ACCESS_TOKEN + PROJECT_PATHS
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config.sh"

# Parameters
ISSUE_IDS="$1" # 格式: #274#275

# Validate input
if [ ${#PROJECT_PATHS[@]} -eq 0 ] || [ -z "$ISSUE_IDS" ] || [ -z "$ACCESS_TOKEN" ]; then
  echo "Usage: $0 <issue-ids: #1#2#3>"
  exit 1
fi

command -v curl >/dev/null 2>&1 || {
  echo >&2 "Error: curl required but not installed."
  exit 1
}

# 解析 issue id:去掉前后 #,按 # 分割
IFS="#" read -ra ISSUE_ARRAY <<<"$ISSUE_IDS"

# 遍历所有 issue id
for iid in "${ISSUE_ARRAY[@]}"; do
  echo "Checking issue #$iid..."
  if [ -z "$iid" ]; then
    continue
  fi

  issue_found=false

  for project_path in "${PROJECT_PATHS[@]}"; do
    encoded_project_path=$(jq -rn --arg x "$project_path" '$x|@uri')
    response_file=$(mktemp)
    status_code=$(curl -s -w "%{http_code}" -o "$response_file" \
      -H "PRIVATE-TOKEN: $ACCESS_TOKEN" \
      "${GITLAB_URL}/api/v4/projects/${encoded_project_path}/issues/${iid}")

    if [ "$status_code" -eq 200 ]; then
      echo "✅ Issue #$iid exists in project $project_path."
      issue_found=true
      rm -f "$response_file"
      break
    fi

    rm -f "$response_file"
  done

  if [ "$issue_found" = false ]; then
    echo "❌ Issue #$iid not found in any configured project path."
    exit 1
  fi
done

echo "🎉 All issues exist in at least one project path."

🐶 从此以后,Husky 不再只是条"会叫的狗",它是你开发流程里的:

  • 👮 分支命名警察
  • 👨‍⚖️ 提交信息法官
  • 🕵️‍♀️ 议题真伪鉴定师
  • 🧹 代码风格质检员

规范就像隐形的电围栏,让你不知不觉中告别"野生开发习惯",走向"高质量工程师"之路------别说你不配,Husky 配得上每一位想偷懒又被迫优秀的你。

最后

提交规范靠吼没用,只需一条命令:

bash 复制代码
curl -sSL https://gitee.com/getbetter/git-hooks-config/raw/master/init-git-hooks.sh | bash -s pnpm

用我这把 Shell + Husky 螺丝刀,一键就能给四十多个项目上锁,自此开发规范不用"靠人盯",自动校验、自动上锁、自动守规矩, 不怕你手抖乱提交,就怕你不用这条命令。本文的所有脚本已上传到码云仓库,欢迎讨论交流。

相关推荐
超人不会飛11 分钟前
就着HTTP聊聊SSE的前世今生
前端·javascript·http
蓝胖子的多啦A梦14 分钟前
Vue+element 日期时间组件选择器精确到分钟,禁止选秒的配置
前端·javascript·vue.js·elementui·时间选选择器·样式修改
夏天想17 分钟前
vue2+elementui使用compressorjs压缩上传的图片
前端·javascript·elementui
今晚打老虎z25 分钟前
dotnet-env: .NET 开发者的环境变量加载工具
前端·chrome·.net
用户38022585982431 分钟前
vue3源码解析:diff算法之patchChildren函数分析
前端·vue.js
烛阴36 分钟前
XPath 进阶:掌握高级选择器与路径表达式
前端·javascript
小鱼小鱼干39 分钟前
【JS/Vue3】关于Vue引用透传
前端
JavaDog程序狗41 分钟前
【前端】HTML+JS 实现超燃小球分裂全过程
前端
独立开阀者_FwtCoder1 小时前
URL地址末尾加不加 "/" 有什么区别
前端·javascript·github
独立开阀者_FwtCoder1 小时前
Vue3 新特性:原来watch 也能“暂停”和“恢复”了!
前端·javascript·github