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

相关推荐
前端老宋Running6 分钟前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔6 分钟前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654268 分钟前
Android的自定义View
前端
WILLF9 分钟前
HTML iframe 标签
前端·javascript
枫,为落叶26 分钟前
Axios使用教程(一)
前端
小章鱼学前端31 分钟前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah33 分钟前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript
流星稍逝34 分钟前
手搓一个简简单单进度条
前端
倚栏听风雨1 小时前
详解 TypeScript 中,async 和 await
前端
小皮虾1 小时前
告别服务器!小程序纯前端“图片转 PDF”工具,隐私安全又高效
前端·javascript·微信小程序