Git:为什么在fetch + rebase之后,先前的fetch + merge提交会消失

时间:2018-02-01 21:10:59

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

TL; DR:如果我将远程更改提取到本地git仓库,然后进行合并,一段时间后我会获取一些新的更改,但这次我做了rebase而不是merge,然后先前创建的合并提交消失。为什么呢?

示例

考虑以下起点,由命令git log --all --graph --decorate --oneline创建:

* 28992d3 (repo1/master) hello4
* 3610bdf hello3
| * f113d63 (HEAD -> master) bye-bye
| * cabc896 bye
|/  
* 75f7ca9 hello2
* 525cb4a hello1

即,有一个git repo,其中master分支包含一些本地的未刷新更改。刚刚从遥控器获取了一些其他更改(在本例中为repo1)。

下一个命令:git merge repo1/master。结果:

*   b94aa29 (HEAD -> master) Merge remote-tracking branch 'repo1/master'
|\  
| * 28992d3 (repo1/master) hello4
| * 3610bdf hello3
* | f113d63 bye-bye
* | cabc896 bye
|/  
* 75f7ca9 hello2
* 525cb4a hello1

现在假设本地以及远程repo1都有一些新的提交,然后再次通过repo1git fetch repo1 master获取远程内容。结果如下:

* 2e3d749 (repo1/master) hello6
* b17983d hello5
| * 2e49819 (HEAD -> master) see ya
| * c2f2d5a good-bye
| *   b94aa29 Merge remote-tracking branch 'repo1/master'
| |\  
| |/  
|/|   
* | 28992d3 hello4
* | 3610bdf hello3
| * f113d63 bye-bye
| * cabc896 bye
|/  
* 75f7ca9 hello2
* 525cb4a hello1

到目前为止一切顺利。 现在让我们做git rebase repo1/master,结果是一个很好的线性提交日志:

* 101e524 (HEAD -> master) see ya
* 3ce7543 good-bye
* 849cbd4 bye-bye
* 483bab8 bye
* 2e3d749 (repo1/master) hello6
* b17983d hello5
* 28992d3 hello4
* 3610bdf hello3
* 75f7ca9 hello2
* 525cb4a hello1

问题:提交b94aa29 Merge remote-tracking branch 'repo1/master'去了哪里?(据我所知,它甚至没有被保留为“死”提交,例如在独立头部进行提交。)

说明:

  • 我想答案必须是“git通知,我们不再需要b94aa29,”因为我们将无论如何都会拥有所有内容“,但是,请你详细解释一下它的内容上?而且,这总是如此,在先前合并的分支上的变基将丢弃所有合并提交吗?
  • 如果你能以某种方式强制合并提交保留,那就太好了。
  • 如果示例可以简化,我愿意编辑问题。

1 个答案:

答案 0 :(得分:1)

TL;答案的DR版本

git rebase在功能上意味着:

  • 选择要复制的一些提交;
  • 一次一个地复制这些提交,就像通过git cherry-pick;
  • 一样
  • 完成后,将当前分支名称更改为指向最终复制的提交。

复制字面意思无法复制合并,所以它通常不会尝试。

更长(但看到我的其他答案更多)

这里的一般想法是进行一系列提交:

             A--B--C--D   <-- topic (HEAD)
            /
...--o--o--*--o--o   <-- mainline

并将它们移植到一系列新的改进提交中:

             A--B--C--D   [abandoned]
            /
...--o--o--*--o--o   <-- mainline
                  \
                   A'-B'-C'-D'  <-- topic (HEAD)

&#34;改进&#34;是将新链基于其他分支的顶端,例如mainline。为了实现这一点,Git确实必须将原始提交 - A-B-C-D复制到具有不同哈希ID的不同提交,因为每次提交都是永久性的 1 并陷入困境;即使只有一位不同的提交也会为您提供一个新的不同的提交哈希ID,即使唯一的区别是存储在新提交中的父ID。因此,即使快照A'中的源树与快照A中的源树匹配 - 并且它可能没有 - A'的提交ID与提交ID不同A

(当然,这也会通过其余的提交进行。)

您提供给git rebase的参数选择:

  • 承诺复制,
  • 从哪里开始复制(在哪里放置第一次复制的提交)。

通常情况下,你可以使用这两个名称。例如,git rebase mainline表示将提交后的副本放入mainline个点,并复制那些可以从topic(当前分支名称)指向的提交中获取的提交-ie,D - 排除从mainline提示可以访问的任何提交。 >

}的第一个提交是提交*,其中两个分支重新加入(在这种情况下永远)。

在某些情况下,您可能需要使用git rebase --onto来区分这两个概念。使用--onto,您可以告诉rebase 放置副本的位置,释放剩余的参数以表示不要复制的内容。这不是必需的。

有一堆种类/风格的rebase:git rebase没有参数使用git format-patch | git am来复制提交,而不是实际运行git cherry-pick,而git rebase -i实际使用git cherry-pick。 (在早期版本的Git中,git rebase -i是一个shell脚本,它实际上运行git cherry-pick。为了使Windows更快,git rebase被修改,以便内置-i Git的音序器,这是实现cherry-pick和revert的代码。)

请注意,所有这一次复制(一次一个)最终会构建提交的线性链即使输入可能包含合并,也会发生这种情况,如:

          A--B--M--C--D   <-- master
         /     /
...--o--*--o--S------o--T   <-- repo1/master

你现在要求Git重新设置(即复制)一些提交 - 在这种情况下,一些提交在master上 - --onto目标为T,并且限制是* T / origin/master可以从master开始的第一个提交,即提交*

此类提交的完整列表为A,然后是B,然后是M,然后是C,然后是D。但Git应该如何复制M?如果尝试,结果可能看起来很像:

          A--B--M--C--D   [abandoned]
         /     /
...--o--*--o--S------o--T   <-- repo1/master
                         \
                          A'-B'-M'-C'-D   <-- master (HEAD)
                               /
                  ???----------
M'之外的

要合并,需要两个父项。其他父母应该有什么?如果其他父母是S,那么可能,但它带来了什么价值?

(合并的目的是合并两个不同开发线的变化。由于A'基于T,基于SA'已经包括S中的任何内容,并且不需要合并它。)

一般来说,Git只是省略合并提交,因此最终只复制A-B-C-D。请注意,如果您重新定义包含内部合并的内容,则会发生同样的事情:Git只是复制&#34; side&#34;合并,线性化结果:

                 C--D
                /    \
            A--B      M--G   <-- topic (HEAD)
           /    \    /
          /      E--F
         /
...--o--*--o--o   <-- mainline

此处git rebase将复制A-B-C-D-E-F-GA-B-E-F-C-D-G,删除M并展平拓扑。

-p有一个git rebase -i标记,其拼写时间--preserve-merges较长,但实际上并没有保留合并(也不是樱桃挑选他们,这是不可能的)。相反,它进行新的合并(通过运行git merge)。这非常棘手,但可用于修改上述A-B-(C-D, E-F)-M-G拓扑。请注意,如果您在M中解决了合并冲突,那么当Git进行合并M'D' F'的新合并git rerere时,您必须再次解决它们这里可能很有用。)

1 永久,也就是说,直到整个提交被放弃足够长的时间让Git确定没有人想要它;然后它被git gc清除掉。