惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
I
InfoQ
C
Check Point Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
Last Week in AI
Last Week in AI
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
B
Blog
T
The Blog of Author Tim Ferriss
H
Help Net Security
云风的 BLOG
云风的 BLOG

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
如何为GIT设置全局勾子,为每次提交追加信息
SKILL·NULL · 2026-04-16 · via 博客园_首页

全局 Git 钩子 + Husky 合并

本文说明:如何用单脚本 global-git-hooks + 符号链接,在任意环境复现「全局钩子与仓库内 Husky 链式执行」,并在指定目录下的仓库为提交标题追加 [AI]


一、原理与数据流

  1. Git 只认一个 core.hooksPath
    指向的目录里必须有名为 pre-commitprepare-commit-msg文件(Git 按文件名调用)。
    因此:目录里放多个同名符号链接,全部指向同一个可执行脚本 global-git-hooks

  2. 脚本如何知道当前是哪个钩子
    Git 执行的是 …/hooks/pre-commit 这类路径,$0 为 symlink 路径,basename "$0" 得到 pre-commitprepare-commit-msg 等,在脚本里 case 分发。

  3. 与 Husky 的关系

    • core.hooksPath=.husky/_:只会跑 Husky 自带桩脚本,不会进本目录。
    • core.hooksPath=$HOME/.config/git/hooks(或你自定义的目录):由 global-git-hooks 按顺序调用 .husky/<钩子名>.git/hooks/<钩子名>
    • 仓库 package.jsonprepare 里在 husky 之后有条件地把本仓库 core.hooksPath 指到全局目录:本机 global-git-hooks 才合并;没有的同事保持 Husky 默认 .husky/_,互不干扰。
  4. prepare-commit-msg 顺序(与「其它钩子」不同)

    .git/hooks/prepare-commit-msg(若存在且可执行)
         → .husky/prepare-commit-msg(若存在)
         → 满足 PREFIX 且未设 NO_AI 时,给说明文件首行末追加「 [AI]」
    
  5. 其它已链接钩子pre-commitcommit-msg 等)

    .husky/<同名>(若存在)
         → .git/hooks/<同名>(若存在,最后一个用 exec)
    
  6. _run_husky 必须先 shift 钩子名
    调用形式为 _run_husky "$HOOK" "$@"。函数内第一参数是逻辑钩子名(如 commit-msg),必须 shift 再执行 sh "$_f" "$@",否则子脚本会把 commit-msg 当成 $1(误以为是提交说明文件路径),导致 commitlint --edit 等报错。


二、从零搭建流程

1. 定目录

约定全局钩子目录为(可改,下文用 $HOOKS_HOME 表示):

export HOOKS_HOME="${GIT_HOOKS_HOME:-$HOME/.config/git/hooks}"
mkdir -p "$HOOKS_HOME"

也可长期用环境变量 GIT_HOOKS_HOME 指向别的路径(与 package.jsonprepare 一致)。

2. 写入脚本 global-git-hooks

$HOOKS_HOME/global-git-hooks 保存下文「四、完整脚本 global-git-hooks」中的全部内容。

必须修改的一处:脚本内 PREFIX=...,改成「允许追加 [AI]」的仓库根路径前缀(规范化路径,例如你所有公司代码都在 /data/work 下,则写 PREFIX="/data/work")。不匹配该前缀的仓库仍会执行链式钩子,但不会改提交说明。

然后:

chmod 700 "$HOOKS_HOME/global-git-hooks"

3. 建立符号链接(Git 要求的多个入口文件名)

cd "$HOOKS_HOME"
for h in pre-commit commit-msg prepare-commit-msg pre-push post-merge post-commit pre-rebase; do
  ln -sf global-git-hooks "$h"
done

4. 让 Git 使用本目录(二选一或都用)

方式 A — 本机所有仓库默认走全局钩子(最简单)

git config --global core.hooksPath "$HOOKS_HOME"

方式 B — 仅部分仓库、且与 Husky 合并(推荐团队)

  • 不设全局 hooksPath,或接受全局仍指向 $HOOKS_HOME
  • 在具体仓库的 package.json 里配置 prepare(见下文「五、仓库 package.json(Husky)」)。
  • 这样: $HOOKS_HOME/global-git-hooks 的机器在 pnpm install 后会把该仓库的 core.hooksPath 指到 $HOOKS_HOME没有该文件的机器保持 Husky 写入的 .husky/_

5. 自检

git config --get core.hooksPath
test -e "$HOOKS_HOME/global-git-hooks" && echo "global-git-hooks ok"

在「应追加 AI」的仓库根下:

printf 'feat: test\n' > /tmp/cm.txt
"$HOOKS_HOME/prepare-commit-msg" /tmp/cm.txt message
head -1 /tmp/cm.txt
# 期望:feat: test [AI]

三、环境变量与开关

变量 作用
NO_AI 仅当变量已设置(值可为空)时不追加 [AI]。须用 VAR= command 形式,不能写 NO_AI git …(shell 会把 NO_AI 当成命令名)。示例:NO_AI= git commit -m "..."NO_AI=1 git commit -m "...";多次提交可先 export NO_AI,用完 unset NO_AI
HUSKY=0 与 Husky 文档一致,为 0 时跳过执行 .husky/<钩子>
GIT_HOOKS_HOME 全局钩子目录不是默认路径时,与 package.jsonprepareGH=... 一致,指向含 global-git-hooks 的目录

四、完整脚本 global-git-hooks

保存为 $HOOKS_HOME/global-git-hooks(与下面内容一致;请改其中 PREFIX= 为你的工作区路径前缀)。

#!/usr/bin/env sh
# 单入口:各钩子名为指向本文件的 symlink,basename "$0" 区分钩子名。
# prepare-commit-msg:.git/hooks → .husky/prepare-commit-msg → [AI]
# 其它:.husky/<同名> → .git/hooks/<同名>

HOOK=$(basename "$0")
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || exit 0
ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
ROOT_CANON=$(cd "$ROOT" 2>/dev/null && pwd -P) || ROOT_CANON="$ROOT"
PREFIX="/Users/macadmin/Work/code"

_run_husky() {
	_hook=$1
	shift
	_f="$ROOT/.husky/$_hook"
	[ -f "$_f" ] || return 0
	export PATH="$ROOT/node_modules/.bin:$PATH"
	_i="${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh"
	[ -f "$_i" ] && . "$_i"
	[ "${HUSKY-}" = "0" ] && return 0
	sh "$_f" "$@" || exit $?
}

case "$HOOK" in
prepare-commit-msg)
	COMMIT_MSG_FILE=$1
	LEGACY="$GIT_DIR/hooks/prepare-commit-msg"
	if [ -x "$LEGACY" ]; then
		case "$LEGACY" in "$0") ;; *) sh "$LEGACY" "$@" || exit $? ;; esac
	fi
	_run_husky prepare-commit-msg "$@"
	[ -z "$COMMIT_MSG_FILE" ] || [ ! -f "$COMMIT_MSG_FILE" ] && exit 0
	case "$ROOT_CANON" in "$PREFIX"|"$PREFIX"/*) ;; *) exit 0 ;; esac
	# 跳过追加:须 export NO_AI 或 NO_AI= git commit …(勿写 NO_AI git,会被当作命令)
	[ "${NO_AI+x}" = "x" ] && exit 0
	_fl=$(head -n 1 "$COMMIT_MSG_FILE")
	_tr=$(printf '%s' "$_fl" | tr -d ' \t\r\n')
	[ -z "$_tr" ] && exit 0
	printf '%s' "$_fl" | grep -qE ' \[AI\]$' && exit 0
	sed -i.bak '1s/$/ [AI]/' "$COMMIT_MSG_FILE"
	rm -f "${COMMIT_MSG_FILE}.bak"
	exit 0
	;;
pre-commit|commit-msg|pre-push|post-merge|post-commit|pre-rebase)
	_run_husky "$HOOK" "$@"
	LEG="$GIT_DIR/hooks/$HOOK"
	[ -x "$LEG" ] && exec sh "$LEG" "$@"
	exit 0
	;;
*)
	exit 0
	;;
esac

移植说明

  • PREFIX:改成你环境的工作区根,例如 Linux PREFIX="/home/you/projects"。判断用的是 ROOT_CANONpwd -P),需与实际磁盘路径前缀一致。
  • sed -i.bak:适配 macOS BSD sed;Linux GNU sed 同样可用。
  • 未列出的钩子名:若自行增加 ln -sf global-git-hooks my-hook,需在脚本末尾 case 中增加分支,否则落入 *) 直接退出。

五、仓库 package.json(与 Husky 合并,可选)

在已有 husky 的前提下,让仅当本机存在全局脚本时,把当前仓库core.hooksPath 指到 $HOOKS_HOMEchmod 失败不阻断后续配置。

"prepare": "husky && (chmod 700 .husky/* 2>/dev/null || true) && sh -c 'GH=\"${GIT_HOOKS_HOME:-$HOME/.config/git/hooks}\"; [ -e \"$GH/global-git-hooks\" ] && git config core.hooksPath \"$GH\" || true'"

含义简述:

  1. husky:安装/更新 .husky/_,并可能把 core.hooksPath 写成 .husky/_
  2. chmod … || true:避免无文件匹配等导致整条 prepare 失败,从而跳过后面的 git config
  3. [ -e "$GH/global-git-hooks" ] && git config core.hooksPath "$GH":只有本机已部署全局脚本时才覆盖为全局目录;否则保持 Husky 默认。
  4. GIT_HOOKS_HOME:全局目录非默认路径时,在 shell 或 CI 里导出该变量与脚本内 GH 一致。

六、commitlint[AI]

prepare-commit-msgcommit-msg 之前执行,标题会变成 type: subject [AI]。若规则不允许尾部标记,需在 commitlint.config.js 中放宽 subject 等规则。


七、卸载 / 排错

去掉全局默认钩子目录

git config --global --unset core.hooksPath

某个仓库曾写入本地 core.hooksPath

git config --unset core.hooksPath

未追加 [AI]

检查 说明
git config --get core.hooksPath 合并生效时应为 $HOOKS_HOME,不是 .husky/_
PREFIX 仓库根路径(pwd -P)是否以 PREFIX 为前缀
NO_AI 是否误设
首行已以 [AI] 结尾 脚本会跳过,避免 commit --amend 重复追加

提示 NO_AI: command not found

  • 说明写成了 NO_AI git commit;应改为 NO_AI= git commitNO_AI=1 git commit(等号与命令之间要有空格)。

commit-msg / commitlint 报找不到文件

  • 多为旧版转发把钩子名当成 $1 传给 commitlint;请确认 global-git-hooks_run_husky 已含 shift(与上文「一、6」一致)。

八、文件清单(部署完成后)

$HOOKS_HOME/
  global-git-hooks          # 唯一实现(可执行)
  pre-commit            -> global-git-hooks
  commit-msg            -> global-git-hooks
  prepare-commit-msg    -> global-git-hooks
  pre-push              -> global-git-hooks
  post-merge            -> global-git-hooks
  post-commit           -> global-git-hooks
  pre-rebase            -> global-git-hooks