Skip to content

合并(Merge)

合并(Merge)是将一个分支的改动整合到另一个分支的操作。Git 提供了多种合并策略,理解它们的区别是高效使用 Git 的关键。

快进合并(Fast-forward)

当目标分支没有新提交,且源分支是直接在目标分支基础上延伸时,Git 只需移动分支指针,无需创建新的合并提交。

合并前:
main:    A ← B ← C
                   ↑ HEAD
feature:           C ← D ← E
                             ↑ feature

合并后(Fast-forward):
main:    A ← B ← C ← D ← E
                           ↑ HEAD(main 指针移动)
bash
git switch main
git merge feature/login

# 输出:
# Updating abc123..def456
# Fast-forward
#  src/login.js | 45 ++++++++++++++++++++++++++
#  1 file changed, 45 insertions(+)

特点:

  • 历史是线性的,没有合并提交
  • 无法从历史中区分"哪些提交来自哪个分支"

三方合并(Three-way Merge)

当两个分支都有新提交(有分叉)时,Git 会找到它们的共同祖先,进行三方合并,并创建一个新的合并提交(Merge Commit)

合并前:
          C ← D (feature)
         /
A ← B ← C  ← main(main 也有新提交 D、E,假设写错了)

实际场景:
A ← B ← C ← D ← E  (main)
              \
               F ← G  (feature)

合并后:
A ← B ← C ← D ← E ← M  (main,M 是合并提交,有两个父提交)
              \       /
               F ← G
bash
git switch main
git merge feature/login

# 输出:
# Merge made by the 'ort' strategy.
#  src/login.js | 45 +++++++++++
#  1 file changed, 45 insertions(+)

特点:

  • 保留了完整的分支历史
  • 产生一个合并提交(有两个父提交)
  • 历史图是非线性的(有分叉)

git merge --no-ff 强制创建合并提交

即使可以 Fast-forward,也强制创建合并提交,保留分支合并的记录:

bash
git merge --no-ff feature/login -m "Merge branch 'feature/login'"
不使用 --no-ff(Fast-forward):
A ← B ← C ← D ← E(没有合并提交,看不出哪些来自 feature)

使用 --no-ff:
A ← B ← C ← M(合并提交)
          \  /
           D ← E(feature 的提交清晰可见)

适用场景:

  • 使用 Git Flow 工作流时
  • 需要清晰记录"哪个功能分支在何时合并"
  • 团队合作中,保持可追溯的合并历史

git merge --squash 压缩合并

将源分支的所有提交压缩成一个暂存区改动,然后手动提交,不创建合并提交:

bash
git merge --squash feature/login
git commit -m "feat: 添加用户登录功能"
合并前:
A ← B ← C  (main)
          \
           D ← E ← F  (feature,3个提交)

合并后(squash):
A ← B ← C ← S  (main,S 包含了 D+E+F 的所有改动)
(feature 分支的历史对 main 不可见)

适用场景:

  • 功能分支上的提交混乱(大量 WIP、fix typo 等),不想污染主线历史
  • 只关心"这个功能完成了",不关心开发过程中的细节提交

注意: squash 后,feature 分支本身没有被删除,需要手动删除:

bash
git branch -D feature/login  # 用 -D 因为 Git 认为它未被合并

合并提交的父提交(-m 1 / -m 2)

合并提交有两个父提交:

  • -m 1:第一父提交(合并时所在的分支,通常是 main)
  • -m 2:第二父提交(被合并的分支,通常是 feature)

这在 git revert 合并提交时用到:

bash
# 撤销合并提交时,指定要保留哪一侧
git revert -m 1 <merge-commit-hash>
bash
# 查看合并提交的父提交
git log --merges --oneline
git log M^1 -1    # 第一父提交
git log M^2 -1    # 第二父提交

git merge --abort 取消合并

如果合并过程中遇到冲突,想放弃合并,恢复到合并前的状态:

bash
git merge --abort

这会撤销合并操作,恢复到合并前的干净状态。

合并选项对比

选项是否创建合并提交历史形状适用场景
merge(默认)有分叉时创建可能非线性通用
--no-ff始终创建非线性Git Flow
--ff-only不创建,不能则失败线性保证线性历史
--squash不创建(需手动提交)线性清理临时提交
bash
# 只允许 fast-forward(不能则报错,不执行合并)
git merge --ff-only feature/login

合并策略(-s)

bash
# 递归策略(默认)
git merge -s recursive feature

# Ours 策略(完全忽略对方改动,只是合并提交的标记)
git merge -s ours old-feature

# Octopus 策略(同时合并多个分支,不能有冲突)
git merge feature1 feature2 feature3

实用技巧

预览合并效果

bash
# 不实际合并,查看哪些提交会被引入
git log HEAD..feature/login --oneline

# 查看合并后会产生的 diff(通过 merge-base)
git diff HEAD...feature/login

仅合并某个分支的最新状态

bash
# 如果不需要分支历史,只需要最终代码状态
git merge --squash feature/long-lived-feature
git commit -m "feat: 集成长期特性分支的最终结果"

合并冲突时的完整流程

bash
# 1. 尝试合并
git merge feature/login

# 2. 遇到冲突
# Auto-merging src/app.js
# CONFLICT (content): Merge conflict in src/app.js

# 3. 查看冲突状态
git status

# 4. 手动解决冲突(编辑冲突文件)
# 详见「冲突处理」章节

# 5. 标记冲突已解决
git add src/app.js

# 6. 完成合并
git commit

# 或者放弃合并
git merge --abort

总结

合并方式命令结果
默认合并git merge <branch>自动选择 FF 或三方合并
强制合并提交git merge --no-ff <branch>始终产生合并提交
压缩合并git merge --squash <branch>压缩为一次改动
取消合并git merge --abort恢复合并前状态

合并是 Git 中整合代码的核心操作,选择合适的合并策略取决于团队的工作流规范和对历史清晰度的要求。