在 video 分支上开发时,master 主干又有了新提交,怎么把它们同步到当前分支?这是日常开发中非常高频的操作,但 git rebase 和 git merge 的区别、以及 rebase 背后到底发生了什么,很多人其实并没有真正搞清楚。

这篇文章把整个问题一次讲透。


一、先看场景

假设从 master 的 C1 拉出 video 分支,之后两边各自有了新提交:

master:   C1 ── C2 ── C3
                \
video:           V1 ── V2
  • master 新增了 C2、C3
  • video 新增了 V1、V2
  • 现在要把 master 的改动同步到 video

有两种方式:git merge 和 git rebase。


二、操作前的准备

无论用哪种方式,先确认状态:

git status          # 确认工作区干净
git branch          # 确认当前在 video 分支

如果有未提交的改动,先 git stash 或 commit,否则 rebase 会中断。


三、方式一:git merge

git checkout video
git merge master

结果:

master:   C1 ── C2 ── C3
                \         \
video:           V1 ── V2 ── M
  • 产生一个新的合并提交 M(merge commit)
  • video 的历史保留了原来的分叉结构
  • 提交顺序、时间线是真实的
  • 历史图呈“菱形”,可读性稍差,但信息完整

特点:

  • ✅ 不改写已有提交,安全
  • ✅ 适合公共 / 共享分支
  • ❌ 历史有分叉,git log 看起来较乱

四、方式二:git rebase

git checkout video
git rebase master

结果:

变基前:
master:   C1 ── C2 ── C3
                \
video:           V1 ── V2

变基后:
master:   C1 ── C2 ── C3
                          \
video:                     V1' ── V2'
  • 把 V1、V2 “搬”到 C3 之后,生成新的提交 V1'、V2'
  • 没有合并提交,历史是一条直线
  • 注意:V1'、V2' 是全新的 commit(hash 变了),原来的 V1、V2 被丢弃

特点:

  • ✅ 历史线性、干净,git log 一目了然
  • ✅ 适合个人分支
  • ❌ 改写了历史,公共分支上慎用(需 force push)

五、并排对比

merge:                                rebase:

master:  C1 ── C2 ── C3               master:  C1 ── C2 ── C3
               \         \                              \
video:          V1 ── V2 ── M         video:            V1' ── V2'

         └─ 有分叉 + 合并提交                   └─ 一条直线,无合并提交
         └─ 提交 hash 不变                        └─ 提交 hash 被重写
         └─ 保留真实时间线                        └─ 时间线被“整理”过

一句话记忆:

操作本质结果
merge把两条线汇合在一起多一个合并提交,历史分叉
rebase把自己的提交挪到别人后面历史变直线,提交被重写

口诀:

  • merge = 保留分叉,加一个“结”
  • rebase = 削掉分叉,把提交重新排成一条线

六、关键疑问:rebase 后,V2′ 里到底有没有 C3 的代码?

这是最容易混淆的地方。答案是:有,但机制和你想的可能不一样。

rebase 的本质是“重放提交”,不是“合并代码”

rebase 做的是:

  1. 找到 video 相对 master 的独有提交(V1、V2)
  2. 把 video 的基点移到 C3
  3. 依次把 V1、V2 的“改动(diff)”重新应用到新基点上

逐步推演

假设:

  • C3 新增了文件 foo.txt
  • V1 修改了 bar.txt
  • V2 修改了 baz.txt

第 1 步:把 V1 重放到 C3 上 → V1′

V1' 的内容 = C3 的内容 + (V1 对 bar.txt 的改动)

所以 V1′ 里已经包含了 C3 的代码(因为它的父提交就是 C3)。

第 2 步:把 V2 重放到 V1′ 上 → V2′

V2' 的内容 = V1' 的内容 + (V2 对 baz.txt 的改动)
           = C3 + V1改动 + V2改动

所以 V2′ 里也包含了 C3 的代码——但这是通过“继承父提交 V1’”间接带上的,不是把 C3 单独 merge 进来。

结论

V1 从 C2 rebase 到 C3 变成 V1′ 时,V2′ 会包含 C3 的代码吗?

会。 准确说法是:

  • V1' 的父提交变成 C3,所以 V1' 天然包含 C3
  • V2' 的父提交是 V1',所以 V2' 也间接包含 C3
  • 这不是“把 C3 merge 进 V2’”,而是整条 video 分支的基点移到了 C3 之上

和 merge 的区别

同样场景下用 merge:

master:   C1 ── C2 ── C3
                \         \
video:           V1 ── V2 ── M
  • V1、V2 内容完全不变,父提交还是 C2
  • C3 的代码只在合并提交 M 里才被引入
  • 所以 V2 不包含 C3,只有 M 包含
V1/V2 是否含 C3怎么含进去的
rebase含(V1’、V2′ 都含)基点整体移到 C3,靠父子继承
merge不含(V1、V2 保持原样)C3 只在合并提交 M 里引入

rebase 改的是分支的基点,所以基点上所有的历史代码都会被后续提交继承;merge 只是加了一个汇合点,原有提交不动。


七、冲突处理

rebase 冲突

# 手动解决冲突后
git add <冲突文件>
git rebase --continue

# 想放弃变基,回到操作前
git rebase --abort

完成后因为改写了历史,需要强推:

git push --force-with-lease origin video

--force-with-lease 比 --force 安全,如果远程有人推了新提交会拒绝,避免覆盖别人的工作。

merge 冲突

git add <冲突文件>
git commit
git push origin video

八、到底怎么选?

场景推荐
video 是个人开发分支,还没合并回 masterrebase,历史线性整洁
video 是共享分支,多人协作merge,避免改写公共历史
只想临时同步一下 master 的改动两者都可,看团队规范

rebase 的“黄金法则”

不要在公共分支(别人也在用的分支)上 rebase。

因为 rebase 会重写 commit hash,别人基于旧提交的工作会全部错乱。个人本地分支随便 rebase,共享分支用 merge。


九、几个实用小技巧

  • 拉取远程最新代码:git fetch origin,rebase 时用 git rebase origin/master
  • 查看 master 有哪些新提交:git log video..origin/master --oneline
  • 变基前先备份:git branch video-backup,出问题可随时回退
  • 推送用 --force-with-lease 而不是 --force

十、总结

  1. merge 保留分叉,加一个合并提交,安全但历史较乱
  2. rebase 把提交搬到新基点,历史成直线,但会重写 commit hash
  3. rebase 后 V2′ 会包含 C3 的代码,因为整条分支的基点移到了 C3 之上,靠父子继承而非 merge 引入
  4. merge 后 V1、V2 不含 C3,C3 只在合并提交 M 里
  5. 公共分支用 merge,个人分支用 rebase

理解这几点,Git 分支同步就不再是玄学了。