Git 中的 fast-forward merge

说明 Git 中 fast-forward merge 的本质、发生条件、与普通 merge 的区别,以及 --ff-only 等选项的行为边界。

#type / concept #status / evergreen #tech / ops #resource / git

[!info] related notes

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 会把 mainC 直接移动到 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-forwardrebase 只是常常把历史整理成“之后可以 fast-forward”的状态。
  • 即使前面已经 rebase 过,如果 main 在你切回去之后又新增提交,git merge --ff-only 仍然会失败。
  • 想保留功能分支作为一个明确集成节点时,不应该追求 fast-forward,而应该显式使用普通 merge 或 --no-ff
创建于 2026/4/30 更新于 2026/5/27