Skip to content

Git 分支合并操作入门:merge、squash、rebase 一文学懂 ​

每天都要 git merge、git rebase,但你真的知道它们的区别吗?同样的需求,为什么有时候历史是一条直线,有时候长出一堆"分叉"?为什么有人说"rebase 危险"? 本教程由浅入深讲透 Git 的三种合并方式——merge / squash / rebase——以及 cherry-pick、fast-forward、冲突处理等周边操作。核心目标:看清三种方式对提交历史的"改写"程度,从此合并不再纠结。

📑 目录 ​

全篇共 9 章 · 总阅读约 50 分钟 · 难度从 ⭐ 到 ⭐⭐⭐ 递进 阅读路线:🟢 新手 → 第 1、2、3、4、5、9 章 | 🟠 进阶 → 全篇通读(第 6、7、8 章)

章节难度时长简介
一、三个核心概念:提交、分支、分叉(入门篇)⭐ 入门5 分钟看懂提交图,才能看懂三种合并的本质区别
二、merge:保留完整历史的"合并提交"(核心篇)⭐⭐ 核心7 分钟非线性历史的由来,fast-forward 与 merge commit
三、squash merge:把一堆提交压成一个(核心篇)⭐⭐ 核心7 分钟GitHub PR 默认操作,主干干净但细节尽失
四、rebase:改写历史,线性重放(核心篇)⭐⭐⭐ 核心10 分钟最强大也最危险,交互式 rebase 与黄金法则
五、三者终极对比与选型(实战篇)⭐⭐⭐ 实战5 分钟一张全对比表 + 决策流程图,照着选不会错
六、补充操作:fast-forward、cherry-pick、revert(进阶篇)⭐⭐ 进阶6 分钟merge 家族的其他成员,各有各的用途
七、冲突处理:合并绕不开的坎(实战篇)⭐⭐⭐ 实战8 分钟冲突长什么样、怎么解决,merge 与 rebase 的区别
八、常见坑与避坑(避坑篇)⭐⭐ 避坑4 分钟rebase 已推送分支等 6 个大坑
九、总结与练习⭐ 收尾2 分钟记忆口诀 + 4 道自测题 + 速查表

一、三个核心概念:提交、分支、分叉(入门篇) ​

想看懂三种合并的区别,先补三个基础概念。

1.1 提交(commit):历史的"快照链" ​

每个提交记录一次代码变更,并指向它的父提交,形成一条链:

A ← B ← C ← (HEAD → main)

提交唯一标识

每个提交有唯一的哈希值(如 3f8a2c1)。历史一旦被改写,哈希就会变——这是理解 rebase"危险"的关键。

1.2 分支(branch):可以移动的指针 ​

分支本质上就是指向某个提交的指针。main 和 feature 各自指向不同的提交:

main:      A ← B ← C
feature:   A ← B ← C ← D ← E

1.3 分叉(divergence):需要合并的根本原因 ​

当两个分支从同一祖先各自前进时,就分叉了:

main:      A ← B ← C
                \
feature:         D ← E

现在的问题是

C 和 E 都基于 B 各自发展,谁也不知道对方改了什么。合并就是把分叉的历史"重新接上"。merge、squash、rebase 就是三种"接法",区别在于——接完之后,历史长什么样。


二、merge:保留完整历史的"合并提交"(核心篇) ​

git merge feature(在 main 上执行)把 feature 的改动并入 main,产生一个合并提交(merge commit),它有两个父提交:

main:      A ← B ← C ─────── F (merge commit,双亲:C 和 E)
                \         /
feature:         D ← E ──

什么时候没有 merge commit?

如果 feature 直接从 main 顶端长出、main 期间没有新提交(没有分叉),Git 直接移动指针 → fast-forward(快进合并),不产生合并提交:

main:      A ← B ← C ← D ← E   (指针直接前进,C 后面就接 D、E)

想让分叉不存在时也保留合并记录,用 git merge --no-ff feature。

命令速记 ​

bash
git checkout main          # 先切到目标分支
git merge feature          # 合并 feature(默认 fast-forward 可用则快进)
git merge --no-ff feature  # 强制生成 merge commit

优点与缺点 ​

说明
✅ 优点历史完整可追溯:每个提交、每次合并都记录在案;不改写任何提交,最安全
❌ 缺点历史非线性:多人协作后提交图变成"毛线球",git log 很难读

什么时候用 merge?

长期分支(如 release、develop)之间的合并、需要完整审计历史的团队项目。GitHub 上"Create a merge commit"就是这种。


三、squash merge:把一堆提交压成一个(核心篇) ​

git merge --squash feature 把 feature 的所有提交压缩成一个新提交,挂在 main 顶端;feature 自身的提交历史不进入 main:

main:      A ← B ← C ← S (单个新提交,内容 = D+E 的全部改动)
# 实际执行(squash 不会自动提交,需要手动 commit)
git merge --squash feature   # 把改动暂存(staged)
git commit -m "feat: 完成 X 功能"   # 手动提交成一个

这就是 GitHub PR 的默认行为

在 GitHub 合并 PR 时选 "Squash and merge" 按钮,就是这个操作:一个 PR = 一个提交。

优点与缺点 ​

说明
✅ 优点main 历史干净线性:一个功能一个提交,好读、好回滚(git revert 一次撤销整个功能)
❌ 缺点丢失过程细节:中间"改了两版、修了 5 次"的过程没了;想单独回退某个中间提交也不行

什么时候用 squash?

功能分支 → main 的标准姿势(团队 PR 合并首选)。前提:这个功能已经成熟,中间过程不需要保留。


四、rebase:改写历史,线性重放(核心篇) ​

git rebase main(在 feature 上执行):把 feature 的提交摘下来,在 main 顶端重新"播放"一遍,生成一批全新的提交(哈希全变):

之前:      main:  A ← B ← C
            feature:    D ← E

之后:      main:  A ← B ← C
            feature:        D' ← E'   (D、E 的"重制版")

结果:历史变成一条直线,没有 merge commit。但注意——D' 不是 D,E' 不是 E,它们是内容相同、哈希不同的克隆。

交互式 rebase:整理自己的历史 ​

bash
git rebase -i HEAD~3   # 交互式整理最近 3 个提交
命令作用
pick保留该提交
reword保留提交,但修改提交信息
squash把该提交合并进上一个提交
edit停下来编辑该提交
drop删除该提交

黄金法则(必须记住)

绝不要 rebase 已经推送到远程、且别人可能基于它的提交。 rebase 改写历史哈希,别人 rebase 过的分支一 pull 就"炸"(历史对不上、冲突满天飞)。只对自己的、未推送的提交使用 rebase。

优点与缺点 ​

说明
✅ 优点历史完全线性,像"一个人从头写到尾",最好读;合并冲突可以逐个提交解决
❌ 缺点改写历史,共享分支上用就是灾难;每个重放的提交都可能触发冲突

什么时候用 rebase?

  • 本地把个人分支"跟上"最新 main(git rebase main)后在看板上会很干净
  • 用 -i 整理自己还没推送的提交(合并碎提交、改提交信息)

五、三者终极对比与选型(实战篇) ​

全维度对比表 ​

维度mergesquashrebase
历史形态非线性(有合并分叉)线性(压缩成 1 个提交)线性(重放全部提交)
提交粒度保留全部提交压缩为 1 个保留全部(哈希改变)
过程可追溯性⭐⭐⭐ 最强⭐ 无(只看结果)⭐⭐ 中间(提交还在但哈希变了)
冲突解决一次解决所有一次解决所有每个提交都可能要解决
改写历史?❌ 不改写🟡 只加 1 个新提交✅ 改写原提交
共享分支安全性最安全安全❌ 危险
典型场景长期分支合并功能分支 PR 合并本地个人分支整理

决策流程图 ​

要合并的分支情况?
│
├─ 别人也要基于它继续开发(共享分支)→ 绝对用 merge(不要 rebase)
│
├─ 功能已完成,合并进 main,想要干净主干 → squash merge
│
├─ 个人开发分支,想跟上 main 且历史整洁 → rebase main
│
└─ 只要某个分支的"一个提交" → cherry-pick(见第六章)

团队约定比个人偏好重要

具体用哪种,先看团队和远端仓库的约定(GitHub 仓库可强制只允许 squash)。个人偏好服从项目惯例。


六、补充操作:fast-forward、cherry-pick、revert(进阶篇) ​

6.1 fast-forward(快进合并) ​

没有分叉时 merge 的默认行为——指针直接前移,不产生合并提交:

main:      A ← B ← C ← D ← E   (feature 的提交直接"接"在 main 后面)

想要"明明可以快进也记录一次合并"?用 --no-ff。

6.2 cherry-pick(摘取单个提交) ​

把某一个提交的改动复制到当前分支,常用于修复版分支:

bash
git cherry-pick 3f8a2c1   # 把提交 3f8a2c1 的改动应用到当前分支

典型场景

功能在 main 已经合并了,但 release 分支紧急要其中一个 bug 修复——cherry-pick 单个提交过去,不用整条分支合并。

6.3 revert vs reset(撤销操作) ​

操作原理历史使用时机
git revert <hash>生成一个反向提交保留原提交,安全已推送的提交(团队协作)
git reset --hard <hash>直接把指针移回去删除后续提交本地未推送的提交

记住一个原则

已推送的历史,用 revert;未推送的历史,才用 reset。


七、冲突处理:合并绕不开的坎(实战篇) ​

7.1 冲突长什么样 ​

两个分支改了同一处代码,Git 不知道听谁的,就在文件里标记出来:

text
<<<<<<< HEAD
const version = '2.0.0';          // 当前分支的版本
=======
const version = '1.9.9';          // 被合并分支的版本
>>>>>>> feature

7.2 解决流程(三步走) ​

bash
# 1. 手动编辑冲突文件,保留想要的内容,删掉 <<<<<<< ======= >>>>>>> 标记
# 2. 标记为已解决
git add 冲突文件
# 3. 继续合并
git merge --continue      # merge 时
git rebase --continue     # rebase 时

7.3 merge 冲突 vs rebase 冲突 ​

mergerebase
冲突出现的次数一次性解决所有差异每个重放的提交都可能冲突
中途想放弃git merge --abortgit rebase --abort
冲突范围两个分支的全部差异每个提交点位的差异

缓解冲突的策略

  1. 小步提交、频繁合并——分叉越久,冲突越多
  2. 合并前先 git fetch 同步最新 main,减少分叉
  3. 解决冲突时先看对方为什么改,别盲目删

八、常见坑与避坑(避坑篇) ​

#坑后果避坑方法
1rebase 已推送的分支队友 pull 后历史错乱、冲突爆炸共享分支只用 merge;rebase 只用于本地未推送提交
2squash 后想单独回滚一个中间提交做不到(中间提交根本不存在了)需要细粒度回滚就不该 squash
3以为会 fast-forward,结果冒出 merge commit莫名出现分叉先 git status 看是否分叉,或约定 --no-ff
4解决冲突时误删对方的代码功能悄悄消失用 IDE 的冲突工具,逐块确认
5rebase 中断后不会恢复卡在"rebase in progress"记牢 git rebase --abort 一键回退
6团队未约定策略,混用三种方式历史一会线一会叉,无法管理先定团队规范(如 PR 统一 squash)

最强提醒

rebase 和 reset 都是改写历史,误用无法通过普通手段找回且会影响他人。拿不准时,merge 永远是最安全的选择。


九、总结与练习 ​

记忆口诀 ​

线下想清楚,线上别乱来:
merge 保历史,squash 压成堆,rebase 改历史只对本地;
共享分支只用 merge,已推历史只用 revert。

核心要点回顾 ​

  • ✅ merge:保留完整历史,产生 merge commit,最安全
  • ✅ squash:把分支提交压成 1 个,主干干净,丢失过程
  • ✅ rebase:重放提交到目标顶端,历史线性,改写哈希,只用于未推送提交
  • ✅ fast-forward:无分叉时的自动快进
  • ✅ cherry-pick:摘单个提交;revert 撤销已推送历史
  • ✅ 冲突解决:merge 一次解决,rebase 逐个解决;都有 --abort 兜底

自测题(先做再看答案) ​

  1. 下图是哪种合并的结果?
    A ← B ← C ← S (内容 = D+E)
  2. 你在 feature 分支上开发的提交已经 push 到远端、团队共用了,现在想回到 main 顶端,该用 merge 还是 rebase?
  3. git merge feature 什么时候不会产生 merge commit?
  4. release 分支需要 main 上的某一个 bug 修复提交,用什么命令?
点击查看答案
  1. squash merge——多个提交被压缩成单个提交 S(无法确认,因为丢了中间过程)
  2. merge。已推送的分支属于共享历史,rebase 会改写哈希导致团队混乱;merge 安全
  3. 当 feature 直接基于 main 顶端、main 期间没有新提交(没有分叉)时,Git 会 fast-forward 直接移动指针
  4. git cherry-pick <提交哈希>

Git 命令速查表 ​

想做什么命令
合并分支(保留历史)git merge feature
强制生成合并提交git merge --no-ff feature
压缩合并(PR 风格)git merge --squash feature + git commit
线性重放(改历史)git rebase main / git rebase -i HEAD~3
摘取单个提交git cherry-pick <hash>
撤销已推送提交git revert <hash>
撤销本地提交git reset --hard <hash>
中断合并/变基git merge --abort / git rebase --abort
看提交图git log --graph --oneline --all

相关笔记 ​

  • GitHub 安装包的命名规则 —— GitHub 相关技能(下载安装与分支协作同属 Git 生态)
  • 进阶方向:团队协作工作流(Git Flow / GitHub Flow)可继续深入
📖本文阅读--次|📊全站访问--次|👥访客--人