Git 中的 merge 可以理解为:把另一个分支上的修改合并到当前分支。不同合并模式的区别,是合并后提交历史如何呈现、是否保留分支痕迹,以及是否重写提交记录。
下面以 main 分支和 feature 分支为例,对常见合并方式进行说明。
main: A---B---C
feature: \---D---E
其中,D、E 表示在 feature 分支上完成的两个提交。
1. Fast-forward merge:快进合并
基本含义
Fast-forward merge 是最简单的一类合并。它发生在当前分支从创建 feature 分支之后没有新的提交时,Git 不需要额外创建合并提交,只需要把当前分支指针直接移动到 feature 分支的最新提交。
合并前:
合并后:
常用命令
git switch main git merge feature如果 Git 判断可以快进,就会自动执行 fast-forward merge。也可以使用下面的命令强制只允许快进合并:
git merge --ff-only feature这条命令的意思是:如果能够快进就合并;如果不能快进,就直接报错,不创建额外的 merge commit。
特点
此时 feature 分支上包含:
A---B---C---D
main 也包含:
A---B---C---D
也就是说,feature 上的内容没有消失,只是 main 也走到了同一个位置。可以理解成:原来 feature 比 main 领先两个提交;FF merge 后,main 追上了 feature;两者现在站在同一个提交点上。
优点是提交历史非常干净,最终看起来是一条直线;缺点是分支开发的痕迹不明显,后续不容易看出某几个提交原本属于同一个功能分支。
2. No-fast-forward merge:非快进合并
基本含义
No-fast-forward merge 表示即使当前场景可以直接快进,也强制创建一个新的合并提交。这个新的提交通常被称为 merge commit,用来记录某个功能分支被合并进主分支。
合并后:
其中,M 就是新增的 merge commit。
常用命令
git switch main git merge --no-ff feature特点
优点是可以保留分支结构,能够清楚看出 C、D 是作为一个完整功能分支开发后合并进来的;缺点是提交历史中会多出 merge commit,历史线条不如纯直线简洁。
这种方式适合比较正式的功能开发,例如登录模块、支付模块、实验性功能等。因为这些内容本身就是一个相对完整的开发单元,保留分支痕迹反而更利于后续回溯。
3. Three-way merge:三方合并
基本含义
Three-way merge 严格来说不是一个需要单独手动选择的模式,而是 Git 在无法快进合并时自动采用的合并方式。当 main 和 feature 在共同祖先之后各自都有新提交时,Git 不能简单移动分支指针,只能根据三个版本进行合并。
合并前:
合并后:
三方合并中的“三方”分别是:共同祖先 B、当前分支最新提交 C,以及被合并分支最新提交 E。Git 会比较 C 和 E 相对于 B 的变化,然后尝试把两边的修改合在一起。
常用命令
git switch main git merge feature特点
如果两边修改的内容不冲突,Git 可以自动合并;如果两边修改了同一文件的同一位置,就可能出现 conflict,需要手动解决冲突后再提交。
4. Squash merge:压缩合并
基本含义
Squash merge 会把 feature 分支上的多个提交压缩成一个新的提交,再放到 main 分支上。它不会把 feature 分支上的原始提交 C、D、E 原样带入 main,而是生成一个包含全部修改的新提交 S。
合并前:
合并后:
常用命令
git switch main git merge --squash feature git commit -m "add feature"git merge --squash feature 之后,Git 只是把 feature 分支上的改动放入暂存区,并不会自动提交,因此还需要手动执行 git commit。
特点
优点是主分支历史非常简洁,通常可以做到一个功能对应一个提交;缺点是 feature 分支上原来的多个提交记录不会原样进入 main。
这种方式适合 feature 分支提交比较杂乱的情况,例如提交信息中有 fix、debug、try、test 等临时提交。将这些过程性提交压缩成一个清晰提交,可以提高主分支历史的可读性。
5. Rebase merge:变基后合并
基本含义
Rebase 本身不是 git merge 命令的一种参数,但在实际开发和 GitHub Pull Request 中经常与 merge 放在一起讨论。它的核心思想是:把 feature 分支上的提交搬到 main 分支的最新提交后面。
原始状态:
rebase 后:
这里的 D'、E' 不是原来的 D、E,而是 Git 根据新的基础重新生成的提交,因此 rebase 会重写提交历史。
常用命令
git switch feature git fetch origin git rebase origin/main完成 rebase 后,如果要把 feature 合并回 main,通常可以执行 fast-forward 合并:
git switch main git merge --ff-only feature最终主分支历史会变成一条直线:
特点
优点是主分支历史保持线性,看起来非常清楚;缺点是会改写 feature 分支的提交历史。如果该分支已经被多人共享,不建议随意 rebase。
rebase 后 push
如果这个 feature 分支之前没 push 过,
git push origin feature远程 GitHub 上还没有这个 feature 分支,不存在历史冲突。直接push
如果这个 feature 分支之前已经 push 过
原来可能是:
rebase 之后变成:
普通 push 很可能会失败,这时候应该用:
git push --force-with-lease origin feature它的意思是:如果远程 feature 没有被更新过,就允许覆盖;如果别人已经推了新提交,就拒绝覆盖。
6. 几种模式的对比
模式 | 常用命令 | 历史形状 | 是否保留分支痕迹 | 适合场景 |
Fast-forward | git merge feature | 直线 | 不明显 | main 没有新提交时 |
No-ff merge | git merge --no-ff feature | 有分叉和合并点 | 保留 | 正式功能分支 |
Three-way merge | git merge feature | 有 merge commit | 保留 | main 和 feature 都有新提交 |
Squash merge | git merge --squash feature | 直线 | 不保留原提交 | feature 提交较乱 |
Rebase + merge | git rebase + merge --ff-only | 直线 | 不保留原分叉 | 追求干净线性历史 |
fast-forward:直接把 main 指针往前挪
--no-ff:保留分支痕迹,生成 merge commit
three-way:两边都改了,Git 找共同祖先合并
squash:把一整个分支压成一个提交
rebase:把自己的提交搬到 main 最新提交后面
7.GitHub Merge
GitHub Pull Request 中常见的三个按钮,通常对应 Merge commit、Squash merge 和 Rebase merge。
情况一:本地合并进 main,然后 push main
git switch main git merge feature git push origin main等价于在本地直接做了最终合并。不需要 GitHub PR merge
但团队开发通常是另一种流程:本地只 push feature,然后在 GitHub 点 PR
git switch feature git push origin feature然后 GitHub 上点:
Squash and merge
或者:
Create merge commit
或:
Rebase and merge
这时最终合并由 GitHub 完成
一般的PR 流程
git switch feature git add . git commit -m "add login page" git push origin feature然后到 GitHub 上创建 PR:feature → main
审核通过后,在 GitHub 上选择:
Merge commit / Squash merge / Rebase merge
实现最终合并。