包括我在内,我想很多人第一次接触 Git 时,大多只使用 git merge
合并分支,对 git rebase
既感陌生又好奇。
即使偶尔用过,往往也只是照着教程执行命令,对其工作机制和适用场景并不真正理解。
因此,本文不打算泛泛而谈 rebase
的所有参数,而是聚焦于它最核心的本质——重新应用提交,重写分支的基础。
我们将通过一个典型场景,深入剖析 git rebase
背后的逻辑,理解它为何能让 Git 历史变得更加简洁有序。
场景再现
初始场景
起初情况是这样的,你从主分支创建了新的特性分支,心里期待着开发完成后将自己开发的新功能合并到主分支上。
此时的分支结构像下面这样:
C---D (feature)
/
A---B (master)
突发意外
正当你全神贯注的进行开发时,有人告诉你主分支上修复了一个紧急BUG。
听了这个消息,你眉头一紧,因为此时的分支结构变成了这样:
C---D (feature)
/
A---B---E (master)
你发现你现在正基于旧版本B进行开发,如果继续开发,以后合并的时候可能会有很多冲突,最后积重难返就麻烦了,所以要尽快解决这个问题。
解决方案
方案一
拉取最新的主分支代码,然后先与主分支进行一次合并,再在合并的基础上重新创建分支进行开发,分支结构如下:
C---D (feature [old])
/ \
A---B---E---M (master)
\
F (feature [new])
方案二
方案一可以解决你的问题,但显然这会让分支结构变得复杂,显得不够优雅。
此时,git rebase
就是你的方案二,它不仅可以让你实现与主分支进行同步的目的,还会让你的分支结构保持简洁清晰。
使用git rebase
后你的分支结构将变成下面的样子:
C'---D' (feature)
/
A---B---E (master)
这个样子是不是看起来就像是直接基于最新的 master
开发的?
Rebase的本质:时间线重构
当我们在 feature
分支执行 git rebase master
时发生了什么呢?
- Git 会找到
feature
和master
的共同祖先(这里是提交B
) - 临时保存
feature
的差异(C
和D
的改动) - 将
feature
分支的**起点(base)**从B
迁移到E
,这正是“变基”(rebase
)的核心含义。 - 在
E
之后重新应用C
和D
的改动,生成新提交C'
和D'
这样操作后,分支结构就变成了方案二中的结构:
C'---D' (feature)
/
A---B---E (master)
C和D被复制为C’和D’,并且基于主分支最新的提交E进行重新应用
关键变化
- 提交历史被重写:原来的
C
和D
会被遗弃(最终被 Git 垃圾回收),但会生成新的C'
和D'
- 线性历史:现在
feature
分支的历史看起来像是直接基于最新的master
开发的 - 提交哈希值改变:因为父提交变了,所以
C'
和D'
的哈希值与原来的C
、D
不同
与Merge的对比
操作 | 命令 | 历史结构 | 特点 |
---|---|---|---|
Merge | git merge master | 保留分叉,生成合并节点 | 历史完整,适合公共分支 |
Rebase | git rebase master | 线性历史,无合并提交 | 整洁清晰,适合本地整理 |
实操演示:变基工作流
- 创建特性分支
git checkout -b feature
- 开发过程中主分支更新
# 在主分支获取更新
git checkout master
git pull
- 变基同步最新代码
git checkout feature
git rebase master
- 处理可能出现的冲突
# 手动解决冲突文件
git add resolved_file
git rebase --continue
git会逐个处理feature上的每个分支(例子中的C和D),所以第4步可能需要重复多次。
写在最后
git rebase
不是一个神秘高深的命令,它的本质就是将你写过的提交,转移到一个新的基础之上。
掌握它,能帮你构建出更加清晰、可读性更强的提交历史。
当然,我们需要注意,对于已经共享到远程的分支,随意使用 rebase
可能会带来混乱。
希望这篇文章能让你对 git rebase
有更深的理解。
如果它帮你理清了思路,欢迎点赞或转发给还在纠结 merge
和 rebase
的朋友们 😊
本文首发于微信公众号《Linux在秋名山》,欢迎大家关注~