git merge 的 --no-ff 参数有什么作用
--no-ff 禁止快进合并:即使可以直接快进,也创建合并提交;若目标历史已被包含,则不额外创建。作用是保留"这里曾经合并过一个功能分支"的历史痕迹,让分支拓扑清晰可见,回滚整组改动更容易。
默认情况下,如果目标分支没有新提交,merge 会"快进"(fast-forward)——分支指针直接前移,不产生合并提交,历史上看不出这里曾有一条功能分支。 --no-ff 在需要合并新历史时生成合并提交,把"这次合并"作为可见节点;若已经 up-to-date 则不生成提交。
图解
默认快进(--ff):
main: A---B---C---D (D 原属于 feature,看不出分支存在过)
--no-ff:
main: A---B-----------M (M 是合并提交,"Merge branch 'feature-x'")
\ /
feature-x: C---D
为什么团队喜欢 --no-ff
- 特性边界清晰:
git log --first-parent main沿第一父提交查看历史,会同时包含主线普通提交;是否每个合并点对应一个特性/PR 取决于团队流程; - 回滚整组改动容易:
git revert -m 1 <合并提交>相对第一父提交撤销该合并带来的树变化,但保留合并祖先关系;以后再合并同一历史不会自动恢复被撤销内容; - 配合分支删除:分支删了,拓扑仍在;分支名本身不是提交身份,只有提交信息等可能留下名称。
配置默认行为
git config --global merge.ff false # 本机所有合并默认 --no-ff
# 只对特定分支(如 main)
git config branch.main.mergeoptions "--no-ff"
常见问题(FAQ)
Q:--no-ff 和 squash merge 怎么选?
--no-ff 保留功能分支的每个提交+合并节点(历史详尽);git merge --squash 把合并结果放入索引和工作区,需要再执行 commit,且不记录第二父提交。要细节选前者,要简洁选后者。
Q:Git Flow 必须用 --no-ff 吗?
Git Flow 的分支模型依赖合并提交来标记特性边界,所以是它的惯例配置。GitHub Flow 下按团队喜好。
Q:--ff-only 又是什么?
相反方向:只允许快进,不能快进就报错。用于"我不想产生合并提交"的线性历史策略,常与 rebase 工作流搭配。