远程分支合并不会触发本地钩子;仅本地执行 git merge 时才可能触发,其中 fast-forward 仅触发 post-merge,--no-ff 触发 pre-merge-commit 等完整流程,冲突后手动提交则只触发 prepare-commit-msg 和 commit-msg。

远程分支合并不会直接触发本地 pre-merge-commit 或 post-merge 钩子——除非你在本地执行了 git merge 操作。 这是绝大多数人踩坑的起点:误以为 push 到远程、或别人在 GitLab 上点 Merge Request 就会跑你本地写的钩子脚本。实际上,那些操作完全不经过你的机器。
哪些合并场景会触发本地 hooks?
只有你亲自在本地运行 git merge 时,才可能触发客户端钩子。但具体触发哪个,取决于合并类型:
-
fast-forward merge(比如git checkout main && git merge feature,且feature是从当前main直接衍生的)→ 只触发post-merge,pre-merge-commit完全不执行 -
--no-ff合并(显式加参数)→ 触发pre-merge-commit→prepare-commit-msg→commit-msg→post-merge - 解决冲突后手动
git add+git commit→ 不走pre-merge-commit,只走prepare-commit-msg和commit-msg(因为此时 Git 认为这是一次普通提交)
想拦截「把 feature 分支合并进 main」这类操作?别依赖 pre-merge-commit
它不可靠:fast-forward 场景下完全绕过,而很多人默认就让它 fast-forward。更稳妥的方式是用 pre-commit + 分支名判断:
#!/bin/sh # .git/hooks/pre-commit current_branch=$(git rev-parse --abbrev-ref HEAD) if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then echo "❌ Refusing to commit directly on $current_branch" exit 1 fi
再配合保护分支(如 GitLab 的 protected branches 设置),才能真正阻止非法合并源头。
远程合并(MR/PR)想自动检查?必须用 server-side hooks 或 CI
本地钩子对远程操作无效。可行路径只有两条:
- Git 服务器端 hook:
update钩子可拦截推送,检查 refname 是否为refs/heads/main,再用git merge-base判断来源分支是否合规(例如禁止直接推feature/*到main) - CI 流水线(推荐):在 MR 触发时运行脚本,用
git merge-base origin/main HEAD算出基线,再校验提交历史是否只含允许的分支前缀(如feature/、hotfix/)
注意:post-receive 钩子虽然能拿到推送信息,但它在合并完成之后才执行,无法阻止非法合并——只能告警或回滚,代价更高。
真正难的不是写钩子,而是厘清「谁在什么环节拥有控制权」:本地钩子管不了别人 push,server hook 管不了别人本地 merge,CI 才是唯一能统一约束所有入口的层。漏掉这个前提,所有脚本都可能在某次 fast-forward 或 MR 点击中失效。











