前置知识: Git

GitHub 合并与变基

6 min入门

以对比驱动方式讲解 git merge 与 git rebase 两种分支整合路线的原理、适用场景与选择原则,覆盖快进合并、三方合并与交互式变基,适合零基础学习者。

开篇:像双人写作合并一样整合分支

假设你和同事合写一本书:你负责”第 1-3 章”,他负责”第 4-6 章”。你俩在各自的文档副本上写作,最后要把两份稿子合成一本完整的书。此时有两条路线:

  • 路线一(merge,合并):把两份稿子原样装订在一起,加上一个”合并页”记录”本书由两份稿件合成”。书的正文里,你的章节和同事的章节完整保留各自历史,合订本能清楚看到每个章节各自的发展过程,缺点是目录结构有分叉,读起来略乱。
  • 路线二(rebase,变基):把同事写完的第 4-6 章作为”基准”,把你的第 1-3 章拆散重写,按顺序重新誊抄到基准之上。最终书稿是一份连续、直线的完整稿子,历史干净,但你的原始草稿(原提交)被”重写”了——誊抄稿和原稿不是同一份。

对应到 Git:git merge 保留双方完整历史并生成合并提交;git rebase 把你的提交”重放”到对方最新提交之后,历史呈一条直线。本篇采用对比驱动方式,把这两条路线掰开揉碎,讲清原理、场景和选择原则。


一、先看结论:merge 与 rebase 速览表

对比维度git mergegit rebase
本质三方合并,生成合并提交提取提交补丁,在目标基底上重放
历史形态有分叉(能看到分支合并痕迹)直线(历史整洁)
是否改写已有提交否,原提交不变是,生成全新提交(ID 改变)
对共享分支的风险无,安全高,禁止对已推送的共享分支使用
可追溯性强,合并提交记录两个父提交较弱,分支独立开发过程被抹平
适用场景长期分支、公共分支、保留合并记录个人功能分支、push 前整理历史
命令形态git merge <分支>git rebase <基底>

官方结论(Pro Git):两种方式整合的最终代码快照完全一样,区别只在提交历史。变基让历史更整洁,合并让历史更完整。


二、原理讲解:从”共同祖先”说起

2.1 分支为什么会分叉

Git 的分支本质上是一个指向提交的可移动指针。当两个分支从同一个提交(共同祖先)各自前进时,历史就分叉了:

A --- B --- C   (main)
       \
        D --- E   (feature)

2.2 merge 的三方合并原理

git merge feature(在 main 上执行)时,Git 取三个点做三方合并:

  1. 我方(ours):当前分支 main 的最新提交 C;
  2. 对方(theirs):要合并进来的分支 feature 的最新提交 E;
  3. 共同祖先(base):两个分支最近共同祖先 B。

Git 逐文件比较三个版本:只有一方改的,自动采用;双方改了同一处且不一致的,标记为冲突等你裁决。合并成功后生成合并提交 M(有两个父提交),历史变成:

A --- B --- C --- M   (main)
       \         /
        D --- E   (feature)

2.3 快进合并(fast-forward)的特殊情况

如果被合并的分支是当前分支的直接后代(没分叉),Git 不需要创建合并提交,直接把指针往前移即可:

# 前提:main 在 B,feature 在 C,且 C 直接继承 B
git merge feature
# 输出:Updating a1b2c3d..e4f5g6h (Fast-forward)

2.4 rebase 的”重放”原理

git rebase main(在 feature 上执行)分四步走(官方文档描述):

  1. 找分叉点:定位 feature 与 main 的共同祖先 B;
  2. 提取补丁:把 feature 自 B 以来的提交(D、E)的修改内容存为临时补丁;
  3. 移动基底:把 feature 指针指向 main 的最新提交 C;
  4. 依次重放:把补丁按原顺序应用到 C 之上,生成新提交 D’、E’。

结果:

A --- B --- C           (main)
             \
              D' --- E'   (feature)

关键区别再强调:merge 后 D、E 原封不动;rebase 后 D’、E’ 是全新的提交(哈希 ID 变了,作者信息保留)。所以 rebase 等于”重写了自己这一侧的历史”。


三、git merge 命令全解

3.1 基本用法

# 把 feature 分支合并到当前分支
git switch main
git merge feature

# 强制创建合并提交(即使可以快进也创建一个,保留"分支曾存在"的记录)
git merge --no-ff feature

# 仅当可以快进时才合并,否则报错退出(适合不想产生合并提交的场景)
git merge --ff-only feature

# 压缩合并:把 feature 的所有提交压成一个改动,暂存到暂存区(需再 commit)
git merge --squash feature
git commit -m "feat: 用户登录功能"

# 合并时打开编辑器修改合并信息
git merge -e feature

3.2 合并输出示例

git merge feature

输出示例:

Merge made by the 'ort' strategy.
 app.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

3.3 合并后清理

# 删除已合并的本地分支(-d 只允许删除已合并的分支,安全)
git branch -d feature

# 删除远程分支
git push origin --delete feature

# 查看已合并到 main 的分支(可用于批量清理)
git branch --merged main

# 查看尚未合并的分支(-d 会拒绝删除它们)
git branch --no-merged

3.4 cherry-pick:只摘取某个提交

不是整条分支合并,而是只把某一个提交”移植”过来:

# 把指定提交应用到当前分支
git cherry-pick abc1234

# 一次移植多个提交
git cherry-pick abc1234 def5678

# 遇到冲突时:解决后继续
git cherry-pick --continue

# 放弃整个 cherry-pick
git cherry-pick --abort

四、git rebase 命令全解

4.1 基本用法

# 把当前分支变基到 main 之上
git switch feature
git rebase main

# 简写形式:直接指定分支(等价于 switch + rebase)
git rebase main feature

# 把当前分支变基到远程分支
git rebase origin/main

# 变基时保留空提交
git rebase --keep-empty main

4.2 变基冲突处理三板斧

# 解决冲突后继续
git rebase --continue

# 跳过当前有问题的提交
git rebase --skip

# 中止变基,回到变基前状态
git rebase --abort

4.3 交互式变基:整理自己的历史

git rebase -i 是重写本地历史的利器,常用于 push 前把多个零碎提交合并成一个清晰提交:

# 交互式整理最近 5 次提交
git rebase -i HEAD~5

# 从指定提交开始整理
git rebase -i abc1234

执行后会打开编辑器,列出待办清单,常用命令:

pick abc1234 添加登录功能
reword def5678 修改提交说明
squash e9f0123 修复样式       # 合并到上一个提交,并合并说明
fixup f1a2b3c 小修复          # 合并到上一个提交,丢弃说明
drop g4h5i6j 废弃的实验代码    # 删除该提交
edit 7k8l9m0 需要暂停修改      # 在该提交处暂停

使用原则:交互式变基只允许用于还没推送到共享远程的本地提交。已 push 的提交被改写,会让拉取过它的队友陷入历史冲突。

4.4 常用变体

# --onto:把 A 分支上基于 X 的提交,改放到 Y 之上
git rebase --onto main server client

# 编辑变基待办列表(进行中时用)
git rebase --edit-todo

# 查看当前变基正在应用的补丁
git rebase --show-current-patch

五、merge vs rebase:怎么选

场景推荐理由
main 合并功能分支(发布)git merge --no-ff保留”功能曾独立开发”的合并记录
个人功能分支同步 maingit rebase main历史整洁,push 前顺手整理
多人协作的共享分支同步git merge不改写他人可能已拉取的提交
PR 合并到 mainGitHub 默认 Squash 或 Merge平台内配置,避免本地操作
本地提交太碎想合并git rebase -i交互式压缩提交
只想要别人的某一个提交git cherry-pick精准移植

黄金原则(Pro Git 原话):不要对已推送到远程、且可能被别人拉取的提交执行 rebase。变基是”重写历史”,只能用于自己还没共享的提交。


六、常见错误与对策表

错误现象报错信息(节选)原因分析解决办法
merge 想快进却被拒绝fatal: Not possible to fast-forward, aborting.用了 --ff-only 但两分支已分叉去掉 --ff-only,接受普通三方合并
rebase 后 push 被拒! [rejected] feature -> feature (non-fast-forward)改写历史后与远程分叉个人分支可用 --force-with-lease;共享分支禁止
merge 出现冲突Automatic merge failed; fix conflicts...双方改同一处按 041 篇解决:编辑 → git add → git merge --continue
rebase 到一半想反悔变基进行中不知道可以中止git rebase --abort 一键回到起点
删分支被拒error: The branch 'feature' is not fully mergedgit branch -d 只删已合并分支确认内容不要后改用 git branch -D 强删
交互式变基里填错命令编辑器里看到 pick 等命令不知道干嘛不熟悉 rebase -i 指令查阅 4.3 节命令表;drop 删提交、squash 合并、reword 改信息
对已推送提交 amend/rebase队友拉取后历史混乱改写公共历史只重写未推送提交;已推送的用新提交修正(如 revert)

八、一句话记忆

merge 保留双方历史、生成合并提交(安全、适合共享分支);rebase 把提交重放成直线(整洁、只用于未推送的本地提交)——快进是特权,公共历史禁改写,push 前整理用 rebase -i。