Skip to content

首先要明白 git mergegit rebase共同目标:都是为了将一个(本地)分支的更改合并到另一个(远程)分支中。它们的主要区别在于实现这一目标的方式最终产生的 Git 历史记录

场景设定

为了方便讲解,我们先设定一个共同的初始场景。

假设我们有一个主分支 main,并且基于 main 分支的某个点,我们创建了一个新的功能分支 feature/123456 来开发新功能。

在此期间,main 分支上也有其他同事提交了新的更新。

初始状态图示:

  • C1C2mainfeature/123456 分支的共同提交历史。
  • feature/123456 分支在 C2 的基础上,新增了提交 C3
  • main 分支在 C2 的基础上,新增了提交 C4

现在,我们的任务是:feature/123456 分支上的更改(C3)合并回 main 分支。下面我们分别用 mergerebase 两种方式来操作。

git merge (合并)

git merge 是一个"忠于历史"的操作。它会保留分支的完整历史,包括分叉和合并的过程。

操作命令:

bash
# 1. 切换到接收更改的分支(这里是 main)
git checkout main

# 2. 执行 merge 命令,将 feature/123456 分支合并进来
git merge feature/123456

背后发生的事情:

  1. Git 会找到 mainfeature/123456 两个分支的共同祖先(在此例中是 C2)。
  2. Git 会将两个分支从共同祖先之后的所有更改(C3C4)进行合并。
  3. 然后,Git 会创建一个全新的提交,我们称之为"合并提交"(Merge Commit),在这个例子里是 C5
  4. 这个 C5 很特殊,它有两个父提交:C4 (来自 main) 和 C3 (来自 feature/123456)。
  5. 最后,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

背后发生的事情:

  1. Git 会找到 mainfeature/123456 的共同祖先 C2
  2. 它会"暂存"feature/123456 分支上从 C2 之后的所有提交(这里是 C3)。
  3. 然后,它将 feature/123456 分支的指针重置到 main 分支的最新提交 C4 上。
  4. 最后,它将刚刚"暂存"的提交 C3C4 之后重新应用一遍。这里会创建一个内容相同但哈希值不同的新提交 C3'
  5. 这个过程重写了 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 mergegit rebase
历史记录非线性,保留分支的真实结构线性,将提交历史整理成一条直线
分支历史保留原始的分支历史,不修改重写历史,创建新的提交
合并提交会创建一个新的"合并提交"不会产生额外的合并提交
协作安全性安全,可以对任何分支使用危险,绝对不要在公共/共享分支上使用
易读性历史图复杂,但真实反映了过程历史图简洁,但"粉饰"了历史

使用场景建议

  1. 何时使用 git merge

    • 当你希望保留完整、精确的分支历史时。
    • 当合并公共分支(如 main, develop)时,这是标准且安全的操作。例如,将一个已经完成并审核通过的 Pull Request (PR) 合并到主干。
  2. 何时使用 git rebase

    • 将你的个人功能分支推送到远程之前,用它来同步 main 分支的最新更改。这可以让你在本地解决冲突,然后向团队提交一个干净、线性的提交历史,方便 code review。
    • 黄金法则:只对尚未推送到远程的、只有你自己使用的本地分支执行 rebase 操作。

一个常见的工作流:

  1. 在你的 feature/123456 分支上开发。
  2. 开发过程中,主分支 main 有了更新。
  3. 在提交 Pull Request (PR) 之前,执行 git rebase main,将你的 feature/123456 分支变基到最新的 main 之上,保持历史整洁。
  4. 将变基后的 feature/123456 分支推送到远程,并创建 PR。
  5. 项目维护者审核通过后,使用 git merge(通常是 PR 界面上的 "Merge" 按钮)将你的 feature/123456 分支安全地合并到 main 分支。