Git 中的 fast-forward merge
说明 Git 中 fast-forward merge 的本质、发生条件、与普通 merge 的区别,以及 --ff-only 等选项的行为边界。
#type / concept
#status / evergreen
#tech / ops
#resource / git
[!info] related notes
- 所属 MOC: Git MOC
- 相关概念: Git 中的 merge 和 rebase
- 相关 howto: 用 Rebase 把功能分支线性合并回 main
Git 中的 fast-forward merge
一句话定义
fast-forward merge 的核心不是创建一个新的 merge commit,而是把当前分支指针直接前移到目标分支的最新提交上。
核心内容
什么情况下会发生 fast-forward
当当前分支是目标分支的祖先时,Git 不需要真的“合并”两条历史,只需要把当前分支指针往前移动。
例如:
A --- B --- C main
\
F1 --- F2 feature
如果 main 仍然停在 C,而 feature 只是从 C 之后继续提交,那么在 main 上执行:
git merge feature
Git 会把 main 从 C 直接移动到 F2:
A --- B --- C --- F1 --- F2
main
feature
这里没有新的 merge commit,只有分支指针前移。
为什么 rebase 后通常可以 fast-forward
如果一开始两边已经分叉:
F1 --- F2 feature
/
A --- B --- C main
直接执行 git merge feature 时,Git 会发现双方都不是对方的祖先,因此必须创建 merge commit。
但如果先在功能分支上执行:
git rebase origin/main
Git 会把功能分支的提交重新播放到最新的 main 之后:
A --- B --- C --- F1' --- F2' feature
main
这时 main 已经成为 feature 的祖先,所以再回到 main 执行合并时,通常就可以直接 fast-forward。
fast-forward 和普通 merge 的区别
| 方式 | 历史形态 | 是否产生 merge commit | 适合场景 |
|---|---|---|---|
| fast-forward merge | 线性 | 否 | 个人项目、小团队、短生命周期功能分支 |
| 普通 merge | 分叉后再汇合 | 是 | 多人协作、长期分支、需要保留集成节点 |
怎么确认这次 merge 能不能 fast-forward
可以先检查当前分支是否是目标分支的祖先:
git merge-base --is-ancestor main <feature-branch>
- 退出码是
0:说明main是<feature-branch>的祖先,可以 fast-forward - 退出码不是
0:说明不能直接 fast-forward
如果想更直观看历史形态,可以看:
git log --oneline --graph --decorate --all
--ff、--ff-only、--no-ff 的区别
| 命令 | 行为 |
|---|---|
git merge feature | 默认允许 fast-forward;不能快进时会创建 merge commit |
git merge --ff feature | 基本等同默认行为 |
git merge --ff-only feature | 只允许 fast-forward;不能快进就直接失败 |
git merge --no-ff feature | 即使能快进,也强制创建 merge commit |
如果你的目标是保持 main 历史线性,git merge --ff-only <feature-branch> 比裸 git merge <feature-branch> 更安全。
边界与易混淆点
fast-forward不是一种“更聪明的 merge 算法”,而是一种根本不需要新 merge commit 的历史形态。rebase不等于fast-forward;rebase只是常常把历史整理成“之后可以 fast-forward”的状态。- 即使前面已经
rebase过,如果main在你切回去之后又新增提交,git merge --ff-only仍然会失败。 - 想保留功能分支作为一个明确集成节点时,不应该追求 fast-forward,而应该显式使用普通 merge 或
--no-ff。