git pull --rebase --preserve-merges使用未修改的父级重写合并提交

时间:2015-03-10 15:09:38

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

考虑历史

   D  origin/master
  /
 | C  master
 | |\
 | | B
  \|/|
   A :
   |
   :

git pull --rebase --preserve-merges之后,我希望历史是

   C'  master
   |\
   D |
   | B
   |/|
   A :
   |
   :

但事实证明是

   C'  master
   |\
   | B'
   |/|
   D :
   |
   A
   |
   :

换句话说,B提交被重写为B',即使不需要它,因为父母双方都是未经修改的。我知道我在D上重新定位,但这是有问题的,因为B合并中的所有冲突都必须再次解决。

目前的功能是否有好处?

有没有办法获得我想要的功能?

1 个答案:

答案 0 :(得分:2)

我无法真正回答“利益”问题,但我可以说明为什么rebase正在按原样行事以及如何获得所需的结果。

Rebase,无论是否具有交互性,都采用“三个类似分支或提交”的参数,它称之为 newbase upstream branch

  

git rebase [-i | --interactive] [ options ] [--exec cmd] [--onto newbase]
[ upstream [ branch ]]

如果你遗漏了部分或全部,rebase仍然使用它们,它只是自己找到它们:

  • branch 默认为当前分支,或HEAD(包括分离头)。
  • upstream 默认为当前分支的上游,由git branch --set-upstream-to或类似设置。无论您在此处指定的是什么,还是默认值,都会被赋予git rev-list以使其停止进行提交,即,将被重新定位的提交是由git rev-list upstream..HEAD打印的提交。 / LI>
  • newbase默认使用与 upstream 相同的提交ID。

(如果你在一个新的git中提供--fork-point以获得--fork-point,这些都会得到一些修改,但是这里的一般想法仍然适用:根据那些“之后的那些选择重新提交的提交” “默认情况下,上游,直至并包括HEAD。”

当你让git pull为你运行git rebase时,它会以upstream的形式提供当前分支的实际上游(再次通过较新的gits中的fork-point计算进行修改) ,但是如果您提取的实际上游尚未完成自己的变基,则这应该没有效果。 (--onto通常也会与上游相同。在这种情况下,它相当于git rebase [options] --onto origin/master origin/master master。)

正如您所看到的(通过运行git rev-list origin/master..master),这意味着“请将BC重新提交到D”。添加--preserve-merges只需使用交互式机制,并根据请求对两个提交进行重新定位后保留合并。

如果您只是运行git fetch,您将像往常一样获得提交D,并将您绘制的图表作为第一个:

   D  origin/master
  /
 | C  master
 | |\
 | | B
  \|/|
   A :
   |
   :

您现在可以尝试手动运行git rebase -p以重新提交提交C,将B指定为其“上游”,以便rebase不会复制提交B,并将D指定为--onto提交,以便将C重新定位到D

$ git rebase -p --onto origin/master master^2 master

不幸的是,这会产生一个新的合并提交,其中两个父项为origin/master(提交D)和原始master^(提交A)。 (这并不是很明显的原因,尽管它显然与交互式保留模式在内部的工作方式有关。)

因此,在这种情况下的诀窍是进行自己的合并:

$ git branch -m master old-master
$ git checkout master

(这使得新master指向提交D),然后:

$ git merge old-master^2

(这是未经修改的B)的新合并。

你还提到:

  

...这是有问题的,因为B合并中的所有冲突都必须再次解决。

不幸的是,您无法真正避免这种情况,因为D可能与A有足够的不同,实际的合并冲突解决方案 也不同。

如果您确定它们(分辨率)不应该(不同),您也可以通过从旧提交C获取合并结果来支持此问题。例如,假设冲突发生在dir1/file1dir2/file2,而AD之间的差异全部来自README.txt或某些此类冲突。然后,当您执行上述合并时,您可以非常简单地“解决”冲突:

$ git checkout old-master -- dir1/file1 dir2/file2

提取这些文件的提交C版本,更新索引和工作树以恢复以前的合并解析。您的冲突现在已经解决(与以前完全一样),您可以git commit生成的合并。