git rebase和git merge --ff-only之间有区别吗?

时间:2015-01-25 19:20:15

标签: git git-merge git-rebase

根据我的阅读,它们都有助于我们获得线性历史。

根据我的实验,rebase一直在运作。但是merge -ff-only仅适用于可以快速转发的场景。

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

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

3 个答案:

答案 0 :(得分:76)

请注意,git rebase作业git merge不同(有或没有--ff-only)。 rebase做的是接受现有提交并复制它们。例如,假设您已经branch1并且进行了两次提交AB

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

并且您决定将这两个提交改为branch2。你可以:

  • 获取您在A(差异A与其父级相比)中所做的更改列表
  • 获取您在B(差异BA
  • 所做的更改列表
  • 切换到branch2
  • A中进行相同的更改并提交它们,从A复制您的提交消息;让我们调用此提交A'
  • 然后在B中进行相同的更改并提交它们,从B复制您的提交消息;我们称之为B'

有一个git命令可以为你执行diff-and-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

现在你有了这个:

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

现在,您可以使用branch1切换回A并删除原始Bgit reset(我在这里使用--hard,它也更方便,因为它也清理了工作树):

git checkout branch1
git reset --hard HEAD~2

这会移除原始的AB 1 ,所以现在你有:

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

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

这是git rebase的作用:它&#34;移动&#34;提交(虽然不是通过实际移动它们,因为它不能:在git中,提交永远不会被更改,所以即使只更改父ID也需要将其复制到新的略有不同的提交中。)

换句话说,虽然git cherry-pick一个提交的自动差异和重做,但git rebase是重做多个的自动过程>提交,最后,将标签移动到&#34;忘记&#34;或隐藏原件。

上面说明了将提交从一个本地分支branch1移动到另一个本地分支branch2,但是当您拥有一个远程时,git使用完全相同的进程来移动提交跟踪分支,当您执行git fetch(包括fetch的第一步git pull)时,会获取一些新提交。您可以从处于feature上游的分支origin/feature开始,并进行一些自己的提交:

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

但是你决定你应该看看上游发生了什么,所以你运行git fetch 2 ,而且,上游有人写了一个提交C

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

此时,您可以简单地将feature AB改为C,并提供:

...-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合并,那么git会进行真正的合并,从而进行新的合并提交{{ 1}}:

M

在这里,...-o--C <-- origin/feature \ `-_ A--B--M <-- feature 会停止并给您一个错误。另一方面,Rebase可以将--ff-onlyA复制到BA',然后隐藏原始B'A

所以,简而言之(好吧,为时已晚:-)),他们只是做了不同的事情。有时结果是一样的,有时候不是。如果可以复制BA,则可以使用B;但是如果有一些很好的理由来复制它们,您可以使用git rebase(可能与git merge)合并或合并。


1 Git实际上保留原件一段时间 - 通常是一个月 - 在这种情况下 - 但它隐藏了它们。找到它们的最简单方法是使用git&#34; reflogs&#34;,它保存每个分支所指向的历史记录,以及--ff-only指向的每个更新分支之前的位置和/或HEAD

最终,reflog历史记录条目到期,此时这些提交符合garbage collection的条件。

2 或者,您可以再次使用HEAD,这是一个以运行git pull开头的便捷脚本。完成提取后,便捷脚本将运行git fetchgit merge,具体取决于您配置和运行它的方式。

答案 1 :(得分:5)

是的,有区别。 git merge --ff-only如果不能快进就会中止,并且提交(通常是一个分支)进行合并。如果它不能快进,它将只创建一个合并提交(即永远不会这样做{ {1}})。

--ff-only重写当前分支上的历史记录,或者可用于将现有分支重新绑定到现有分支上。在那种情况下,它不会创建合并提交,因为它的变基,而不是合并。

答案 2 :(得分:3)

是的,--ff-only将始终在普通git merge失败的地方失败,并且可能在普通git merge成功的地方失败。这就是重点 - 如果您尝试保留线性历史记录,并且合并无法以这种方式完成,那么您希望它失败。

将失败案例添加到命令的选项并不是无用的;它是验证前提条件的一种方式,因此,如果系统的当前状态不是您所期望的,那么您就不会使问题变得更糟。

相关问题