git rebase和git merge --ff-only之间是否有区别


78

从我的阅读中,他们两个都有助于我们获得线性历史。

根据我的实验,变基一直都在起作用。但是merge --ff-only仅在可以快速转发的情况下有效。

我还注意到,git merge创建了一个合并提交,但是如果我们仅使用--ff,它会给出一个线性历史记录,基本上等于git rebasing。所以--ff-only杀死了git merge的目的,对吗?

那么它们之间的实际区别是什么?

Answers:


154

请注意,git rebase它的工作不同于git merge(带有或不带有--ff-only)。要做的rebase是获取现有提交并复制它们。举例来说,假设您在进行branch1两次提交,A并且B

...-o--o--A--B   <-- HEAD=branch1
        \
         o--C    <-- branch2

然后您决定宁愿启用这两个提交branch2。您可以:

  • 获取您所做的更改的列表AA与父项的区别)
  • 获取您所做的更改的列表BB与相比A
  • 切换到 branch2
  • 进行与您所做的相同的更改A并提交,从复制您的提交消息A;我们称这个提交A'
  • 然后进行与您所做的相同的更改B并提交,从复制您的提交消息B;让我们称之为B'

有一个git命令可以为您完成diff-then-copy-commit git cherry-pick。所以:

git checkout branch2      # switch HEAD to branch2 (commit C)
git cherry-pick branch1^  # this copies A to A'
git cherry-pick branch1   # and this copies B to B'

现在您有了这个:

...-o--o--A--B         <-- branch1
        \
         o--C--A'-B'   <-- HEAD=branch2

现在,您可以使用切换回branch1原来的AB,并将其删除git reset(我将--hard在这里使用它,因为它也可以清理工作树,因此更加方便):

git checkout branch1
git reset --hard HEAD~2

这消除了原有的AB1所以现在您有:

...-o--o               <-- HEAD=branch1
        \
         o--C--A'-B'   <-- branch2

现在,您只需要重新签出branch2即可继续在那里工作。

这是做什么的git rebase:它“移动”提交(尽管不能通过实际上移动它们,因为它不能这样做:在git中,一个提交永远都不能更改,因此即使仅更改父ID,也需要将其复制到新的并且稍有不同的地方承诺)。

换句话说,虽然git cherry-pick一次提交的自动差异化和重做,但git rebase是一种重做多次提交的自动化过程,最后还可以移动标签以“忘记”或隐藏原始文档。

上面的示例说明了将提交从一个本地分支移动branch1到另一个本地分支的过程branch2,但是git使用完全相同的过程来移动提交,前提是您拥有一个远程跟踪分支,当您进行远程处理时,该分支会获取一些新提交git fetch(包括的fetch第一步)git pull)。您可能会先处理feature上游的分支,origin/feature然后自己进行几次提交:

...-o        <-- origin/feature
     \
      A--B   <-- HEAD=feature

但你决定你应该看到发生了什么事上游,让你跑git fetch2和,啊哈,有人上游写了一个承诺C

...-o--C     <-- origin/feature
     \
      A--B   <-- HEAD=feature

在这一点上,你可以简单地衍合featureA,并BC,赠送:

...-o--C     <-- origin/feature
        \
         A'-B'  <-- HEAD=feature

这些是原件AB的副本,副本完成后将其丢弃(但请参阅脚注1)。


有时,没有什么可以改变的,即您自己没有做过的工作。也就是说,之前的图形fetch看起来像这样:

...-o      <-- origin/feature
           `-- HEAD=feature

但是,如果您随后git fetch又提交了,则在前进的同时C您的 feature分支仍指向旧的提交origin/feature

...-o--C   <-- origin/feature
     `---- <-- HEAD=feature

这就是git merge --ff-only来自于:如果你问合并当前分支featureorigin/feature,Git把它可能只是滑动箭头前进,因为它是,这样feature直接点提交C。不需要实际的合并。

如果你有自己的提交AB,虽然,你问合并那些C,混帐会做一个真正的合并,做一个新的合并提交M

...-o--C        <-- origin/feature
     \   `-_
      A--B--M   <-- feature

在这里,--ff-only将停止并给您一个错误。底垫中,而另一方面,可以复制ABA'B',然后躲起来原来的AB

因此,总而言之(可以,为时已晚:-)),他们只是在做不同的事情。有时结果是相同的,有时则不同。如果它的确定以复制AB,你可以使用git rebase; 但是如果有很好的理由复制它们,则可以根据需要使用git merge,也许将其与--ff-only合并或失败。


1 Git实际上将原件保留了一段时间(在这种情况下通常为一个月),但将它们隐藏了起来。找到它们的最简单方法是使用git的“ reflogs”,它保留每个分支指向的位置以及在HEAD更新分支和/或的每次更改之前所指向的位置的历史记录HEAD

最终reflog历史记录条目到期,这时这些提交才有资格进行垃圾回收

2或者再次使用git pull,它是一个便捷脚本,其开始于运行git fetch。提取完成后,便捷脚本将运行git mergegit rebase,具体取决于您如何配置和运行它。


3
好答案。谢谢。
凤凰城

5
我喜欢你git cherry-pick用来解释的方式git rebase。看起来很棒!
开化

当您将PR从开发分支合并到master(prod)时,建议的策略是什么?
Shanika Ediriweera,

@ShanikaEdiriweera:被谁推荐,目的是什么?(我去过的各个地方都有不同的目标,因此使用了不同的机制。)在不了解您的情况的情况下,我无法提供有用的建议。
torek '19

1
这听起来像是进行标准合并的地方,也许也可以与--no-ff此处合并。
托雷克

10

是,有一点不同。git merge --ff-only如果无法快进,将中止操作,并接受一个提交(通常是一个分支)进行合并。只有无法快进(即永远不会使用--ff-only),它才会创建一个合并提交。

git rebase重写当前分支上的历史记录,或可用于将现有分支重新建立到现有分支上。在那种情况下,它不会创建合并提交,因为它是基于基础而不是合并。


也就是说,-ff-only会检查并且如果不重写历史记录就不可能合并失败。rebase通过重写历史记录来做到这一点。是吗?
凤凰城

2
不完全的。合并不会重写历史记录,因此git merge也永远不会重写历史记录(带有或不带有--ff-only)。--ff-only防止除快速合并之外的任何合并(没有合并重写历史记录)。除快速合并外,其他合并通常只是简单地按顺序应用变更集(这是一种简化,但足以配合使用),并记录已合并变更集的元数据;不涉及历史重写。
2015年

7

是的,--ff-only在平原git merge将失败的地方总是失败,在平原git merge将成功的地方可能失败。这就是要点-如果您试图保持线性历史记录,并且合并无法以这种方式完成,则您希望它失败。

向命令中添加失败案例的选项并不是没有用的。这是一种验证前提条件的方法,因此,如果系统的当前状态不是您期望的状态,则不会使问题变得更糟。


1
就像--ff-only用来验证方案并在可能的情况下创建线性历史记录;而变基则更像是创建线性历史记录,而不管情况如何。是吗?
凤凰城

4
是的,如果您重新整理了一些不够干净的内容,merge --ff-only则会从历史树中的某个位置删除提交,然后将它们(修改后)重新插入到另一个位置。这称为“重写历史记录”,如果您在未发布的分支上也可以,但是在发布分支并且其他人克隆了它之后,重写历史记录会给他们带来麻烦。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.