Mir*_*ral 5 git git-merge git-rebase git-rerere
(类似于这个问题,但有一些上下文和演示为什么rerere不是答案。)
对于给定的历史:
/...o origin/master
o...o...o...o...o...o...o master
\...o........../ topic
Run Code Online (Sandbox Code Playgroud)
我有一个主题分支,我已将其合并到 master 中,并进行了一次额外的提交。同时,上游有人对 origin/master 进行了另一次提交,所以我不能再按原样推送我的 master。
我想将我的 master 重新绑定到 origin/master 而不更改主题上的提交 SHA,并且不会丢失已经在 master 上执行的冲突解决方案。(这是迄今为止我想要保留合并提交的最常见情况,所以我很惊讶这显然如此困难。)
与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
Run Code Online (Sandbox Code Playgroud)
M 有父节点 B 和 T 以及一个独特的变更集 Mc。创建 M' 时,git 在父 N 和 T 之间执行新的合并,并丢弃 Mc。为什么 git 不能只是重新应用 Mc 而不是丢弃它?
最后,我希望历史看起来像这样:
o...o...o...o...B...N...M'...A' master
\...T............../
Run Code Online (Sandbox Code Playgroud)
其中 M' 和 A' 从 rebase 更改 SHA1,但 M' 包含 Mc 变更集,而 T 未更改 SHA1 或父项。现在我可以将 origin/master 快进到 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
Run Code Online (Sandbox Code Playgroud)
(HEAD1 已经检出 N 但还没有做其他事情;HEAD2 已经创建了一个新的合并,其中 N 作为父级 1,T 作为父级 2 但由于未解决的冲突尚未提交)
是否有一些 rebase 命令和/或 git 命令序列,它们将:
为什么 git 默认不这样做?
git rerere无法记录非冲突的根本原因是它以一种廉价而肮脏的方式实现:Git 获取每个初始冲突,剥离一些数据以使其更适用(与剥离行号和一些空格的git rerere方式相同)git patch-id),然后将冲突作为 blob 对象保存在数据库中,并获取其存储在rerere目录中的哈希 ID。后来,当你git commit获得结果时,Git 会将一个特定的冲突更改 blob 与其解决方案配对。所以它只“知道”冲突,而不知道任何其他变化。
稍后的合并(及其冲突)尝试再次保存冲突,再次获取哈希 ID,并找到配对,因此它使用保存的第二个 blob 作为解决方案。由于不冲突的更改不会保存在此处,因此它们永远不会作为此过程的一部分出现。
\n\nGit 也许可以节省更多,但事实并非如此。
\n\n\n\n\n在我对合并提交的理解(可能有缺陷)中,它们由两个(或更多)父级和一个唯一的变更集(用于存储冲突解决方案,以及提交合并之前所做的任何其他更改或稍后修改为合并提交)组成。
\n
这是不正确的。 所有提交都只是状态的快照。合并在这里并不特殊\xe2\x80\x94就像非合并提交一样,它们有一个完整的源树。他们的特别之处在于他们有两个(或更多)父母。
\n\n复制非合并git cherry-pick(并且git rebase通过重复调用来重复执行git cherry-pick,或者做一些不太好但类似的事情),通过使用提交的(一个且唯一的)父级作为合并的合并基础来工作。作为动词的操作。复制合并通常是不可能的,并且 rebase 不会尝试:它只是重新执行合并。
(另一方面,git cherry-pick将让您选择合并,使用其-m选项选择一个特定的父级。Git 只是假装这是三向合并操作期间的唯一父级。理论上,变基代码可以做同样的事情:-m 1几乎总是正确的父级,并且总是可以使用低级别git commit-tree进行实际提交,从而使其成为合并提交。但git rebase不这样做。)
\n\n\n...如果对合并提交中也已修复的文件有任何其他更改(例如,纯粹在合并中添加的行没有冲突,但由于其他地方的更改仍然需要更正),这些更改都会丢失。
\n
是的(出于上面讨论的原因)。这也许是人们将此类事情称为“邪恶合并”的原因之一(尽管使用该短语的另一个可能原因是,至少从未来掌握的所有证据来看,这种变化实际上并不是任何人所要求的)。虽然它对现有合并的目标没有帮助,但我建议不要进行此类更改:相反,在合并之前或之后,在馈入或出合并的普通非合并提交中进行这些更改,以便稍后rebase -p或者rebase --rebase-merges 可以保存它们。
| 归档时间: |
|
| 查看次数: |
460 次 |
| 最近记录: |