Git 中 merge 和 rebase 的区别。它们都是用来整合不同分支的代码变更,但实现方式和最终的提交历史(commit history)有显著不同。

核心区别一句话总结:

  • merge:创建一个新的“合并提交”(merge commit),将两个分支的历史合并在一起,保留了完整的分支历史。
  • rebase:将一个分支的提交重新应用(replay)到另一个分支的最新提交之上,使历史看起来像是线性发展。

一、 前提:创建示例场景

假设我们有一个项目,主分支是 main。开发新功能时,我们从 main 分出一个新分支 feature。

  1. 初始状态 (main 分支)

    A --- B --- C     (main)
  2. 开发新功能 (feature 分支) 你在 feature 分支上进行了两次提交:

              D --- E     (feature)
             /
    A --- B --- C         (main)
  3. 同时,main 分支也有新提交 在你开发 feature 时,其他人向 main 分支推送了一个修复提交 F:

              D --- E     (feature)
             /
    A --- B --- C --- F   (main)

现在,你完成了 feature 分支的开发,需要将你的更改整合到 main 分支中。这时,merge 和 rebase 就派上用场了。

二、 使用 merge

操作:

# 切换到 main 分支
git checkout main
# 将 feature 分支合并进来
git merge feature

结果: Git 会创建一个新的“合并提交”(Merge Commit),通常标记为 M。这个提交有两个父提交:F (main 的最新提交) 和 E (feature 的最新提交)。

          D --- E
         /       \
    A --- B --- C --- F --- M   (main)

特点:

  1. 保留历史:完全保留了 feature 分支的存在和开发历史。你可以清晰地看到 feature 分支是从 C 点分叉出去的,并在 M 点合并回来。
  2. 简单直接:操作简单,不易出错。
  3. 历史记录真实:反映了实际的开发流程,即两个分支是并行开发的。
  4. 提交历史可能复杂:如果项目有很多分支频繁合并,提交历史会变成一个“网状结构”,对于只想看主干线性发展的开发者来说,可能显得杂乱。

三、 使用 rebase

操作:

# 切换到 feature 分支
git checkout feature
# 将 feature 分支变基到 main 分支的最新提交上
git rebase main
# 切换回 main 分支
git checkout main
# 将已经变基好的 feature 分支快进合并(fast-forward merge)
git merge feature

详细过程:

  1. git rebase main:

    • Git 会找到 feature 和 main 的共同祖先(这里是 C)。
    • 它会暂时移除 feature 分支上的提交 D 和 E。
    • 然后,它将 main 分支的最新提交 F 应用到 feature 分支。
    • 最后,它将之前移除的提交 D 和 E 重新应用到 F 之后。注意,D 和 E 会变成新的提交(内容相同,但哈希值不同),我们用 D' 和 E' 表示。

    此时的状态:

    A --- B --- C --- F --- D' --- E'   (feature)
                   \
                    (main)
  2. git merge feature: 因为 feature 现在是 main 的直接后续,所以这次合并只是一个简单的“快进”(fast-forward),不需要创建合并提交。

    最终状态:

    A --- B --- C --- F --- D' --- E'   (main, feature)

特点:

  1. 线性历史:提交历史看起来非常干净、线性,没有多余的合并提交。
  2. 历史记录被重写:D 和 E 被“重写”为 D' 和 E'。虽然内容一样,但它们的提交哈希值、作者时间和父提交都变了。
  3. 可能丢失上下文:feature 分支曾经存在过的事实被“抹去”了,看起来像是 D' 和 E' 是在 F 之后直接开发的。
  4. 需要谨慎:永远不要对已经推送到公共仓库的提交进行 rebase。因为这会重写历史,如果你的同事基于旧的 D 和 E 进行了工作,会产生严重的冲突。

四、 如何选择?

场景推荐操作原因
合并公共分支(如 develop 合并到 main)merge保留完整的分支历史,清晰地记录了发布周期。
整合来自其他团队的分支merge尊重他人的工作流程,保留其分支历史。
清理本地功能分支,准备合并rebase在将 feature 分支推送到远程前,用 rebase 更新到最新的 main,然后进行 merge 或 rebase 后的 fast-forward merge。
希望提交历史干净、线性rebase适用于个人项目或团队约定使用 rebase 的情况。
协作开发,分支已共享避免 rebase重写已共享的历史会导致协作混乱。

五、 总结

  • merge 是“包容”的,它忠实地记录了所有分支的并行开发过程,适合团队协作和保留历史上下文。
  • rebase 是“重塑”的,它通过重写提交历史来创造一个看似线性的、更简洁的提交记录,适合在合并前清理自己的本地工作。
  • 还是不知道该怎么用,那就闭眼睛用merge,永远不会错 !

最佳实践建议:

  1. 在本地开发功能时,可以频繁使用 git rebase main 来保持 feature 分支与 main 同步,避免后期合并时出现大量冲突。
  2. 当功能开发完成,准备合并时,如果团队允许,可以先 rebase 再进行 fast-forward merge,以保持主分支的线性。
  3. 如果团队要求保留所有分支历史,则直接使用 merge。

更多推荐