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

标签 git git-merge git-rebase

从我读到的内容来看,它们都帮助我们获得了线性历史。

根据我的实验,rebase 一直有效。但是merge --ff-only 只适用于可以快进的场景。

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

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

最佳答案

请注意 git rebasegit merge 的工作不同(有或没有 --ff-only )。什么 rebase所做的是获取现有的提交并复制它们。例如,假设您在 branch1 上并进行了两次提交 AB :

...-o--o--A--B   <-- HEAD=branch1
        \
         o--C    <-- branch2
并且您决定更愿意将这两个提交放在 branch2 上相反。你可以:
  • 获取您在 A 中所做的更改列表(diff A 与其父级的差异)
  • 获取您在 B 中所做的更改列表(差异 BA)
  • 切换到 branch2
  • 进行与您在 A 中所做的相同的更改并提交它们,从 A 复制您的提交消息;让我们称这个提交 A'
  • 然后进行与您在 B 中所做的相同的更改并提交它们,从 B 复制您的提交消息;让我们称之为 B' .

  • 有一个 git 命令可以为你做这个 diff-and-then-copy-and-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
    
    这删除了原来的 AB ,1 所以现在你有:
    ...-o--o               <-- HEAD=branch1
            \
             o--C--A'-B'   <-- branch2
    
    现在您只需要重新退房branch2继续在那里工作。
    这是什么git rebase确实:它“移动”提交(虽然不是通过实际移动它们,因为它不能:在 git 中,提交永远不能更改,因此即使只是更改父 ID 也需要将其复制到新的和略有不同的提交)。
    换句话说,虽然 git cherry-pick是一次提交的自动差异和重做,git rebase是一个重做多次提交的自动化过程,最后,移动标签以“忘记”或 stash 原始标签。
    上面说明了从一个本地分支 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
    
    此时您可以简单地重新设置您的 featureABC ,给:
    ...-o--C     <-- origin/feature
            \
             A'-B'  <-- HEAD=feature
    
    这些是您原件的副本 AB , 副本完成后将原件扔掉(但请参阅脚注 1)。

    有时没有什么可以 rebase 的,即没有你自己做过的工作。即fetch之前的图看起来像这样:
    ...-o      <-- origin/feature
               `-- HEAD=feature
    
    如果你那么 git fetch并提交 C进来,但是,你留下了你的feature指向旧提交的分支,而 origin/feature已经前进:
    ...-o--C   <-- origin/feature
         `---- <-- HEAD=feature
    
    这是哪里git merge --ff-only进来:如果您要求 merge 您当前的分支 featureorigin/feature , git 看到可以像以前一样向前滑动箭头,以便 feature直接指向提交 C .不需要实际 merge 。
    如果你有自己的提交 AB但是,您要求将它们与 C merge , git 会进行真正的 merge ,进行新的 merge 提交 M :
    ...-o--C        <-- origin/feature
         \   `-_
          A--B--M   <-- feature
    
    在这里,--ff-only将停止并给你一个错误。另一方面,rebase 可以复制 ABA'B'然后 stash 原来的AB .
    所以,简而言之(好吧,太晚了:-)),他们只是做不同的事情。有时结果是一样的,有时却不是。如果可以复制AB ,您可以使用 git rebase ;但如果有充分的理由不复制它们,您可以使用 git merge ,也许与 --ff-only , 酌情 merge 或失败。

    1Git 实际上会保留原件一段时间——在这种情况下通常是一个月——但它会将它们 stash 起来。找到它们的最简单方法是使用 git 的“reflogs”,它记录了每个分支指向的位置以及 HEAD 的位置的历史记录。指出,在更新分支和/或 HEAD 的每个更改之前.
    最终 reflog 历史条目过期,此时这些提交有资格获得 garbage collection .
    2或者,您可以再次使用 git pull ,这是一个从运行 git fetch 开始的便捷脚本.获取完成后,便利脚本运行 git mergegit rebase ,取决于您如何配置和运行它。

    关于git - git rebase 和 git merge --ff-only 之间有区别吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28140434/

    相关文章:

    git - 撤消 Git Rebase

    git rebase 在之前的 git merge 之后

    merge 后Git丢失代码

    Git:如何通过 SSH 将 SourceTree 连接到我的 Git-Enabled-Web-Hosting?

    git - 如何在不妨碍我的团队的情况下重命名根项目文件夹?

    git - 无法在 Gerrit 中 merge

    git - 将存储库 merge 到经过修改的克隆中

    git - 在 Windows 7 64 位上安装 Hg-Git

    git - 显示已经 "squashed & merged"进入master的本地分支

    php - Git 将 PHP 应用程序部署到多个 EC2 节点