(与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命令序列,它们将:
为什么默认情况下git不这样做?
答案 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 -p
或rebase --rebase-merges
可以保存它们。