Appearance
首先要明白 git merge
和 git rebase
的共同目标:都是为了将一个(本地)分支的更改合并到另一个(远程)分支中。它们的主要区别在于实现这一目标的方式和最终产生的 Git 历史记录。
场景设定
为了方便讲解,我们先设定一个共同的初始场景。
假设我们有一个主分支 main
,并且基于 main
分支的某个点,我们创建了一个新的功能分支 feature/123456
来开发新功能。
在此期间,main
分支上也有其他同事提交了新的更新。
初始状态图示:
C1
和C2
是main
和feature/123456
分支的共同提交历史。feature/123456
分支在C2
的基础上,新增了提交C3
。main
分支在C2
的基础上,新增了提交C4
。
现在,我们的任务是:将 feature/123456
分支上的更改(C3
)合并回 main
分支。下面我们分别用 merge
和 rebase
两种方式来操作。
git merge
(合并)
git merge
是一个"忠于历史"的操作。它会保留分支的完整历史,包括分叉和合并的过程。
操作命令:
bash
# 1. 切换到接收更改的分支(这里是 main)
git checkout main
# 2. 执行 merge 命令,将 feature/123456 分支合并进来
git merge feature/123456
背后发生的事情:
- Git 会找到
main
和feature/123456
两个分支的共同祖先(在此例中是C2
)。 - Git 会将两个分支从共同祖先之后的所有更改(
C3
和C4
)进行合并。 - 然后,Git 会创建一个全新的提交,我们称之为"合并提交"(Merge Commit),在这个例子里是
C5
。 - 这个
C5
很特殊,它有两个父提交:C4
(来自main
) 和C3
(来自feature/123456
)。 - 最后,
main
分支的指针会移动到这个新的合并提交C5
上。
git merge
之后的结果图示:
merge
的特点总结:
- 优点:
- 保留真实历史:它忠实地记录了发生过的一切,包括分支的创建、开发和合并,分支历史不会被修改。
- 可追溯性强:通过合并提交,可以清晰地看到某个功能分支是何时被合并进主干的。
- 缺点:
- 历史记录复杂:如果项目频繁地创建和合并分支,Git 历史图会变得非常杂乱,像一张意大利面条,难以阅读。
- 产生额外的合并提交:每次合并都会产生一个(或多个)
Merge branch 'feature/123456' into 'main'
这样的无意义提交,污染了提交日志。
git rebase
(变基)
git rebase
顾名思义,是"改变基底"(re-base)。它会拿走你的分支上的所有提交,然后将它们在目标分支的最新提交之后"重放"一遍,从而创造出一条线性的历史记录。
操作命令:
bash
# 1. 切换到需要变基的分支(这里是 feature/123456)
git checkout feature/123456
# 2. 执行 rebase 命令,将 feature/123456 的基底变更为 main
git rebase main
背后发生的事情:
- Git 会找到
main
和feature/123456
的共同祖先C2
。 - 它会"暂存"
feature/123456
分支上从C2
之后的所有提交(这里是C3
)。 - 然后,它将
feature/123456
分支的指针重置到main
分支的最新提交C4
上。 - 最后,它将刚刚"暂存"的提交
C3
在C4
之后重新应用一遍。这里会创建一个内容相同但哈希值不同的新提交C3'
。 - 这个过程重写了
feature/123456
分支的历史。
git rebase
之后的结果图示(第一步):
feature/123456
分支的历史被改写,现在它看起来像是直接在 main
的最新提交之后开发的。
此时 feature/123456
分支已经包含了 main
的最新更改。接下来,我们再把它合并回 main
分支。
bash
# 3. 切换回 main 分支
git checkout main
# 4. 将 rebase 后的 feature/123456 分支合并进来
git merge feature/123456
因为 feature/123456
分支的所有历史现在都在 main
分支的前面,所以这次合并是一个快进式合并(Fast-forward),不会产生新的合并提交。main
分支的指针直接移动到 C3'
即可。
git rebase
+ merge
之后的最终结果图示:
rebase
的特点总结:
- 优点:
- 历史记录清晰、线性:提交历史是一条直线,非常整洁,易于阅读和理解。
- 没有无意义的合并提交:使 commit log 保持干净。
- 缺点:
- 重写了历史:它改变了提交的哈希值、时间和顺序。这会丢失分支的真实开发背景(比如,你无法知道
C3
是在C4
之前开发的)。 - 协作风险:这是最重要的一点! 如果你对一个已经推送到远程仓库并被团队其他人使用的公共分支执行
rebase
,会给所有协作者带来巨大的麻烦。因为你本地的历史和远程的历史产生了分歧。
- 重写了历史:它改变了提交的哈希值、时间和顺序。这会丢失分支的真实开发背景(比如,你无法知道
核心区别总结
特性 | git merge | git rebase |
---|---|---|
历史记录 | 非线性,保留分支的真实结构 | 线性,将提交历史整理成一条直线 |
分支历史 | 保留原始的分支历史,不修改 | 重写历史,创建新的提交 |
合并提交 | 会创建一个新的"合并提交" | 不会产生额外的合并提交 |
协作安全性 | 安全,可以对任何分支使用 | 危险,绝对不要在公共/共享分支上使用 |
易读性 | 历史图复杂,但真实反映了过程 | 历史图简洁,但"粉饰"了历史 |
使用场景建议
何时使用
git merge
?- 当你希望保留完整、精确的分支历史时。
- 当合并公共分支(如
main
,develop
)时,这是标准且安全的操作。例如,将一个已经完成并审核通过的 Pull Request (PR) 合并到主干。
何时使用
git rebase
?- 在将你的个人功能分支推送到远程之前,用它来同步
main
分支的最新更改。这可以让你在本地解决冲突,然后向团队提交一个干净、线性的提交历史,方便 code review。 - 黄金法则:只对尚未推送到远程的、只有你自己使用的本地分支执行
rebase
操作。
- 在将你的个人功能分支推送到远程之前,用它来同步
一个常见的工作流:
- 在你的
feature/123456
分支上开发。 - 开发过程中,主分支
main
有了更新。 - 在提交 Pull Request (PR) 之前,执行
git rebase main
,将你的feature/123456
分支变基到最新的main
之上,保持历史整洁。 - 将变基后的
feature/123456
分支推送到远程,并创建 PR。 - 项目维护者审核通过后,使用
git merge
(通常是 PR 界面上的 "Merge" 按钮)将你的feature/123456
分支安全地合并到main
分支。