为什么在执行rebase-merger rebase时git不会自动重新应用冲突解决方案?

时间:2019-01-25 05:25:13

标签: git git-merge git-rebase git-rerere

(与this question类似,但有一些上下文说明了为什么rerere不是答案。)

对于给定的历史记录:

                /...o      origin/master
o...o...o...o...o...o...o  master
    \...o........../       topic

我有一个主题分支,我已将该分支合并到master中,并进行了另一次提交。同时,上游有人对原始服务器/主机进行了另一次提交,因此我不能再按原样推送主机。

我想在不更改主题上的提交SHA且不丢失已经在母版上执行的冲突解决方案的情况下,将我的母版重新设置为原始/母版。 (到目前为止,这是我最常见的想要保留合并提交的情况,因此令我惊讶的是,这显然如此困难。)

启用rerere后,git rebase -p 几乎可以工作-对于原始合并中的任何冲突,它都会记住我为修复它们所做的操作并重新应用了此操作(尽管它留下了该文件被标记为冲突文件,因此我必须记住将每个文件都标记为已解决,而无需重新启动该文件上的冲突解决程序,这对于TortoiseGit前端来说有点令人讨厌。但是,如果对文件的任何其他更改也已在合并提交中修复(例如,纯粹在合并中添加的行没有冲突,但由于其他地方的更改仍需要更正),则这些丢失。

这是东西。在我(可能有缺陷)对合并提交的理解中,它们由两个(或多个)父级和一个唯一的变更集(用于存储冲突解决方案,以及在合并之前或以后修改为合并提交所做的任何其他更改)组成。看来rebase -p重新创建了合并提交,但完全放弃了这个额外的变更集。

为什么它不重新应用原始合并提交中的变更集?这将使rerere变得多余,并避免丢失这些其他更改。如果需要人工确认,可能会将受影响的文件标记为冲突,但是在许多情况下,这种自动解决方案将是足够的。

换句话说,标记上面的某些提交:

                /...N      origin/master
o...o...o...o...B...M...A  master
    \...T........../       topic

T - the commit on topic
B - the merge-base of origin/master and master
N - the new commit on origin/master
M - the merge between B and T
A - the extra post-merge commit

M有父母B和T,还有一个独特的变更集Mc。创建M'时,git在父N和T之间执行新合并,并丢弃Mc。为什么git不能仅仅重新应用Mc而不是丢弃它?

最后,我希望历史记录如下:

o...o...o...o...B...N...M'...A'  master
    \...T............../

其中M'和A'从基准库中更改SHA1,但M'包括Mc更改集,而T没有更改SHA1或父级。现在我可以将原点/原点快速转发到A'。


我还注意到,有一个新的选项--rebase-merges听起来很不错,但之后确实得到了正确的图形-但就像--preserve-merges一样,由于M'上的冲突而停下来并失去了任何Mc的独特变化无法通过rerere保存。


问题的另一种表达方式可能更有用:

鉴于上面的初始状态,并且刚刚启动了一个交互式的变基,该变基现在处于HEAD1或HEAD2状态:

                     (T)
                       \
                    /...M'  HEAD2
                    /...    HEAD1
                /...N       origin/master
o...o...o...o...B...M...A   master
    \...T........../        topic

(HEAD1已签出N,但仍未执行任何其他操作; HEAD2已创建了一个新合并,其中N作为父级1,T作为父级2,但由于未解决的冲突而尚未提交)

是否存在一些rebase命令和/或git命令序列,它们将:

  1. 计算M和B之间的差异Mc(选择B,因为另一个父T不变)
  2. 将其应用于冲突树M'(除非N引入新的冲突,否则应完全解决所有冲突) OR 只需将其应用于N(无需先进行任何合并)即可-这些应该等效;第二个可能更容易
  3. 暂停以解决N所引起的任何剩余冲突。
  4. 将M'提交为N和T之间的合并
  5. 像往常一样继续操作(在这种情况下,将A重新定位为M'之上的A')

为什么默认情况下git不这样做?

1 个答案:

答案 0 :(得分:2)

git rerere无法记录非冲突的根本原因是git rerere是通过廉价而肮脏的方式实现的:Git接受每个初始冲突,剥离一些数据以使其更适用(与git patch-id删除行号和某些空格的方式相同),然后将冲突另存为blob对象在数据库中,从而获得存储在rerere目录中的哈希ID。稍后,当您git commit结果时,Git会将一个特定的冲突更改blob与它的分辨率配对。因此,它仅“知道”冲突,而没有其他任何更改。

后面的合并(带有冲突)尝试再次保存冲突,再次获取哈希ID,然后找到配对,因此它将保存的第二个blob用作分辨率。由于未冲突的更改未保存在此处,因此它们永远不会在此过程中显示。

Git也许可以节省更多,但事实并非如此。

  

在我(可能有缺陷的)对合并提交的理解中,它们由两个(或多个)父级和一个唯一的变更集(用于存储冲突解决方案,以及在提交合并或以后修改为合并之前进行的任何其他更改)组成提交)。

这是不正确的。 所有提交只是状态的快照。合并在这里并不特殊-就像非合并提交一样,它们具有完整的源代码树。 对他们的特殊之处在于他们有两个(或更多)父母。

git cherry-pick一样复制非合并(并且git rebase通过重复调用git cherry-pick或做一些不尽相同但相似的操作来重复复制),可以使用提交的(唯一的)父对象作为“作为一个动词的合并”操作的合并基础。通常,复制合并是不可能的,并且变基也不会尝试:它只是重新执行 合并。

(另一方面,git cherry-pick可以让您选择合并,使用其-m选项来选择一个特定的父代。Git只是假装那是在从理论上讲,rebase代码可以做到这一点:-m 1几乎总是正确的父级,而人们总是可以使用低级git commit-tree进行实际的提交,以使其成为合并提交。但是git rebase不会这样做。)

  

...如果对文件的任何其他更改也已在合并提交中修复(例如,纯粹在合并中添加的行没有冲突,但由于其他地方的更改仍需要更正),则这些丢失。

是(出于上述原因)。这也许是人们将这种事情称为“邪恶合并”的一个原因(尽管该短语的另一个可能原因是,至少就一个人将来可获得的所有证据而言,这种变化实际上并不是任何人的要求)。尽管它不能帮助您实现现有合并的目标,但我建议不要进行此类更改:相反,请在合并之前或之后进行这些更改,并在普通的非合并提交中进行合并(或合并),以便以后进行合并。 rebase -prebase --rebase-merges 可以保存它们。