唯一可靠方式是服务端pre-receive钩子拦截+ci校验+分支保护规则强制require merge commits,因git无原生per-branch合并策略,--no-ff需通过命令参数或pr策略显式触发。

如何让某个分支拒绝 fast-forward 合并
Git 本身不支持「对某一分支强制只允许 --no-ff」的原生配置,它没有 per-branch 的 merge 策略开关。但你可以通过预设的本地钩子或团队协作规范来逼近这个效果。
真正起作用的是 git merge 命令执行时是否带 --no-ff 参数,而不是分支本身的属性。所以关键不是“设置分支”,而是“控制谁、在什么条件下怎么合并”。
pre-merge 钩子能拦截 fast-forward 吗
不能直接用 pre-merge 钩子(Git 没有这个钩子),但可以用 pre-commit 钩子配合脚本检测上一次提交是否为合并提交、是否由 fast-forward 产生——这太绕且不可靠。
更实用的做法是:在团队约定中,把 master 或 main 分支的合并操作统一收口到 CI 流水线,由 CI 脚本检查本次合并是否生成了合并提交:
- CI 中运行
git log -1 --pretty=%s HEAD,确认 commit message 是否含Merge branch - 检查
git log --merges -n 1是否命中当前 HEAD - 若不是合并提交,直接 exit 1 并报错:
Refusing non-merge commit on main: use 'git merge --no-ff' instead
注意:这个检查必须在 push 后、CI 执行前完成,否则无法拦截。因此更适合放在 pre-receive 钩子(服务端)里,而非本地钩子。
为什么不能靠 git config merge.ff false
这个配置项全局生效,影响所有本地分支的所有合并行为,不是按分支粒度控制的:
-
git config --global merge.ff false→ 所有git merge默认加--no-ff -
git config --local merge.ff false→ 当前仓库所有分支都受影响 - 它无法区分「我要合进
main」和「我在feature/x上做临时合并」
如果你只希望 main 分支被合并时强制 --no-ff,而其他开发分支仍可快进,这个配置就不够精准,反而会干扰日常开发节奏。
最可行的落地方式:文档 + CI + 权限约束
真正能稳定落地的方案,是组合三层控制:
- 在仓库根目录放
.github/workflows/protect-main.yml(或其他 CI 配置),要求所有推送到main的提交必须来自合并提交(git merge --no-ff生成) - 在 CONTRIBUTING.md 里明确写:“向
main合并必须使用git merge --no-ff -m 'merge: xxx',否则 CI 将拒绝” - 在远程仓库(如 GitHub/GitLab)开启 branch protection:禁止直接 push 到
main,只允许通过 PR 合并,并在 PR 设置中勾选 “Require merge commits”
PR 界面里的 “Require merge commits” 选项,本质就是强制走非 fast-forward 路径——哪怕两个分支其实是线性的,也会生成一个合并提交。这才是目前最可靠、无需自研脚本、且对开发者透明的方式。
别指望 Git 自己记住“这个分支要特殊对待”,它只认命令参数和服务器策略。真正的约束不在客户端,而在入口关卡。











