【问题标题】:What's the difference between 'git merge' and 'git rebase'?“git merge”和“git rebase”有什么区别?
【发布时间】:2013-05-15 23:44:22
【问题描述】:

git mergegit rebase 有什么区别?

【问题讨论】:

标签: git merge git-merge rebase git-rebase


【解决方案1】:

mergerebase 有什么区别?

阅读官方 Git 手册,它指出 “rebase 在另一个基础分支上重新应用提交”,而 “merge 将两个或多个开发历史连接在一起”。换句话说,merge 和 rebase 之间的主要区别在于,merge 保留发生的历史,rebase 重写它。

让我们用一个并排的例子来说明这些陈述的背景!

如上图所示,merge 操作通过创建一个新的单一合并提交 (C7) 将分支交织在一起,从而导致菱形非线性历史 - 基本上保留了发生的历史。通过将此结果与 rebase 操作的结果进行比较,我们看到没有创建合并提交,而是两个提交 C5C6 只是被倒带并直接重新应用C4 的顶部,保持历史线性。

如果我们进一步检查这两个重新应用的提交,我们可以看到哈希值发生了变化,这表明 rebase 确实重写了历史记录。

值得注意

每当你rebase 一个分支时,新的提交总是会生成,即使内容可能仍然相同!也就是说,如果没有其他指针(分支/标签)引用它们,任何以前的提交最终都会(垃圾回收后)从历史记录中删除。

权力越大责任越大

我们已经看到 rebase 如何重写历史,而 merge 保留它。但这在更广泛的意义上意味着什么?这两种操作会带来哪些可能性和潜在的弊端?

冲突的更改

例如,假设您在尝试整合更改时遇到了一些令人讨厌的冲突。在合并场景中,您只需要直接在 C7 提交中解决冲突一次。另一方面,使用 rebase 时,您可能不得不在每次提交(C5C6)中解决类似的冲突,因为它们被重新应用。

已发布的分支

另一个与 rebase 相关的潜在问题发生在您正在 rebase 的分支已经远程发布,并且其他人已经基于它进行了工作。然后,您的重新定位分支可能会给所有相关方造成严重的混乱和头痛,因为 Git 会告诉您您的分支同时领先和落后。如果发生这种情况,使用 rebase 标志 (git pull --rebase) 拉取远程更改通常可以解决问题。

此外,每当您对已发布的分支进行变基时,无论其他人是否基于其工作,您仍然需要强制推送它以获取远程服务器的更新 — 完全覆盖现有的远程引用.

数据丢失(对您有利)

最后,由于 rebase 会重写历史,而 merge 会保留它,因此在 rebase 时实际上可能会丢失数据。当重新应用新的提交时,旧的提交(最终在垃圾回收后)被删除。实际上,正是这一特性使 rebase 如此强大——它允许您在公开之前整理您的开发历史!

结论

虽然从潜在数据丢失的角度来看,merge 可以安全使用,并且使用起来感觉更直接。以下是一些可以帮助您避免与rebase 相关的最常见问题的提示。

  • 不要对已远程发布的分支进行变基...
  • ……除非你知道你是唯一一个在做这件事的人(并且感到安全的力量推动)
  • 在变基之前,从您要变基的分支的顶端创建一个备份分支,因为它可以让您轻松比较结果(一旦完成)并在必要时跳回变基前的状态。

来源:以上摘录摘自这篇关于该主题的完整帖子:Differences Between Git Merge and Rebase — and Why You Should Care

【讨论】:

    【解决方案2】:

    我发现一篇关于 git rebase vs merge 的非常有趣的文章,想分享一下here

    • 如果您想查看完全一样的历史,您可以 应该使用合并。 Merge 保留历史,而 rebase 重写它。
    • 合并会在您的历史记录中添加一个新的提交
    • 变基更好地简化复杂的历史记录,您可以通过交互式变基更改提交历史记录。

    【讨论】:

      【解决方案3】:

      为了方便理解可以看我的图。

      变基会改变提交哈希,所以如果你想避免很多冲突,只需在该分支完成/稳定时使用变基。

      【讨论】:

        【解决方案4】:

        Git rebase 更接近于合并。 rebase 的区别在于:

        • 本地提交会暂时从分支中删除。
        • 运行 git pull
        • 再次插入所有本地提交。

        这意味着在所有远程提交之后,您的所有本地提交都会移到最后。如果你有合并冲突,你也必须解决它。

        【讨论】:

        • “更接近合并”比什么?
        【解决方案5】:

        虽然被接受且最受好评的答案很棒,但我还发现尝试仅用文字来解释差异很有用:

        合并

        • “好的,我们的存储库有两种不同的开发状态。让我们将它们合并在一起。两个父母,一个孩子。”

        变基

        • “将主分支(无论其名称)的更改提供给我的功能分支。假装我的功能工作是稍后开始的,实际上是在主分支的当前状态上。”
        • “重写我的更改历史以反映这一点。” (需要强制推送它们,因为通常版本控制都是关于篡改给定的历史记录)
        • “可能——如果我所做的更改与我的工作无关——历史实际上不会有太大变化,如果我逐个查看我的提交差异(您可能还会想到“补丁”)。“

        总结:如果可能,rebase 几乎总是更好。使重新集成到主分支更容易。

        因为? ➝ 您的功能工作可以呈现为 一个 相对于主分支的大“补丁文件”(又名 diff),而不必“解释”多个父级:至少两个,来自一个合并,但可能更多,如果有几个合并。 与合并不同,多个变基不会累加。(另一个大优点)

        【讨论】:

          【解决方案6】:

          就我个人而言,我不认为标准的图表技术很有帮助 - 箭头似乎总是指向错误的方向。 (它们通常指向每个提交的“父”,最终会在时间上倒退,这很奇怪)。

          用文字来解释:

          • 当您将您的分支rebase 到他们的分支上时,您告诉 Git 让它看起来好像您干净地签出了他们的分支,然后从那里开始执行所有工作。这样就形成了一个干净的、概念上简单的更改包,可供某人查看。当他们的分支上有新的更改时,您可以再次重复此过程,您将始终在其分支的“尖端”得到一组干净的更改。
          • 当您将他们的分支合并到您的分支中时,此时您将两个分支历史联系在一起。如果您稍后再进行更多更改,您将开始创建一个交错的历史线索:他们的一些更改,我的一些更改,他们的一些更改。有些人觉得这很混乱或不受欢迎。

          由于我不明白的原因,Git 的 GUI 工具从未付出太多努力来更清晰地呈现合并历史,抽象出单个合并。所以如果你想要一个“干净的历史”,你需要使用 rebase。

          我似乎记得读过使用rebase的程序员和其他从不使用rebase的程序员的博客文章。

          示例

          我将尝试用一个简单的例子来解释这一点。假设您项目中的其他人正在处理用户界面,而您正在编写文档。如果没有变基,你的历史可能看起来像:

          Write tutorial
          Merge remote-tracking branch 'origin/master' into fixdocs
          Bigger buttons
          Drop down list
          Extend README
          Merge remote-tracking branch 'origin/master' into fixdocs
          Make window larger
          Fix a mistake in howto.md
          

          也就是说,在文档提交中间进行合并和 UI 提交。

          如果您将代码重新定位到 master 而不是合并它,它将如下所示:

          Write tutorial
          Extend README
          Fix a mistake in howto.md
          Bigger buttons
          Drop down list
          Make window larger
          

          您的所有提交都在顶部(最新),然后是 master 分支的其余部分。

          免责声明:我是另一个答案中提到的“我讨厌 Git 的 10 件事”帖子的作者

          【讨论】:

          • 提交 digram 有指向父母而不是孩子的指针,因为那是 git 存储的内容。理解这一点——以及分支是在其“提示”处指向单个提交的指针这一事实——对于理解 git 所做的许多事情非常有用。我也不知道您所说的“Git 的 GUI 工具从未付出太多努力来更清晰地呈现合并历史,抽象出单个合并”。不过,您对变基和合并的实际描述很棒。
          【解决方案7】:

          假设最初有 3 个提交,A,B,C:

          然后开发者 Dan 创建了提交 D,开发者 Ed 创建了提交 E

          显然,这种冲突应该以某种方式解决。为此,有两种方法:

          合并

          DE 仍然存在,但我们创建了合并提交 M,它继承了 DE 的更改。但是,这会产生 菱形 形状,许多人对此感到非常困惑。

          REBASE

          我们创建commit R,其实际文件内容与上面的merge commit M相同。但是,我们摆脱了提交E,就像它从未存在过一样(用点表示 - 消失线)。由于这种删除,E 应该是开发人员 Ed 本地的,并且不应该被推送到任何其他存储库。 rebase 的优点是避免了 diamond 形状,并且历史保持良好的直线 - 大多数开发人员都喜欢它!

          【讨论】:

          • 精美插图。但是,我并不完全同意处理 rebase 的积极语气。在合并和变基中都可能发生需要手动解决的冲突。与往常一样,当涉及到程序员时,出现错误(即错误)的可能性是不可忽视的。如果发生合并错误,整个团队或社区都可以看到合并并验证那里是否引入了错误。 rebase 的历史保留在 1 个开发人员的仓库中,即使在那里,它在 reflog 中也只有有限的生命周期。它可能看起来更好,但没有其他人可以轻易地看出哪里出了问题。
          • > “但是,这会产生菱形,很多人觉得这很混乱。”嗯……你能详细说明一下吗?
          • @GregMaletic:钻石形状是非线性的历史。我不了解你,但我一般不喜欢非线性的东西。也就是说,如果您真的喜欢,欢迎您使用与钻石合并 - 没有人强迫您。
          • 虽然这个答案非常有帮助,但如果您使用简单的 foo.txt 文件添加实际的 git 命令以在本地重现它会更好。就像上一个用户说的那样,谁在做 rebase 并不明显。
          • @pferrel:我认为您没有正确理解。 git merge 不会交错提交(但通过查看 git log 可能会出现这种情况)。取而代之的是,git merge 保留了 Dan 和 Ed 的开发历史原封不动地保存下来,就像一次从每个角度看到的那样。 git rebase 看起来是 Dan 先做的,Ed 跟着他。在这两种情况下(合并和变基),实际生成的文件树是完全相同的。
          猜你喜欢
          • 2018-03-05
          • 2023-04-03
          • 2018-04-28
          • 2012-02-22
          • 1970-01-01
          • 2011-09-11
          • 2011-03-22
          相关资源
          最近更新 更多