在 video 分支上开发时,master 主干又有了新提交,怎么把它们同步到当前分支?这是日常开发中非常高频的操作,但 git rebase 和 git merge 的区别、以及 rebase 背后到底发生了什么,很多人其实并没有真正搞清楚。
这篇文章把整个问题一次讲透。
一、先看场景
假设从 master 的 C1 拉出 video 分支,之后两边各自有了新提交:
master: C1 ── C2 ── C3
\
video: V1 ── V2
master新增了C2、C3video新增了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 做的是:
- 找到
video相对master的独有提交(V1、V2) - 把
video的基点移到C3 - 依次把
V1、V2的“改动(diff)”重新应用到新基点上
逐步推演
假设:
C3新增了文件foo.txtV1修改了bar.txtV2修改了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'天然包含C3V2'的父提交是V1',所以V2'也间接包含C3- 这不是“把 C3 merge 进 V2’”,而是整条 video 分支的基点移到了 C3 之上
和 merge 的区别
同样场景下用 merge:
master: C1 ── C2 ── C3
\ \
video: V1 ── V2 ── M
V1、V2内容完全不变,父提交还是C2C3的代码只在合并提交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 是个人开发分支,还没合并回 master | rebase,历史线性整洁 |
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
十、总结
- merge 保留分叉,加一个合并提交,安全但历史较乱
- rebase 把提交搬到新基点,历史成直线,但会重写 commit hash
- rebase 后 V2′ 会包含 C3 的代码,因为整条分支的基点移到了 C3 之上,靠父子继承而非 merge 引入
- merge 后 V1、V2 不含 C3,C3 只在合并提交 M 里
- 公共分支用 merge,个人分支用 rebase
理解这几点,Git 分支同步就不再是玄学了。