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

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

(类似于 this question ,但有一些上下文和演示为什么 rerere 不是答案。)

对于给定的历史:

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

我有一个已 merge 到 master 的主题分支,并进行了一次额外的提交。同时,上游有人在 origin/master 上再次提交,所以我不能再按原样推送我的 master。

我想将我的 master 重新定位到 origin/master 而不改变主题上的提交 SHA 并且不丢失已经在 master 上执行的冲突解决。 (这是迄今为止我想要保留 merge 提交的最常见情况,所以我很惊讶这显然如此困难。)

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

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

为什么它不重新应用原始 merge 提交中的变更集?这将使 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 有 parent B 和 T 以及一个独特的变更集 Mc。创建 M' 时,git 在父 N 和 T 之间执行新的 merge ,并丢弃 Mc。为什么 git 不能只重新应用 Mc 而不是丢弃它?

最后,我希望历史看起来像这样:
o...o...o...o...B...N...M'...A'  master
    \...T............../

其中 M' 和 A' 从 rebase 更改 SHA1,但 M' 包括 Mc 变更集,而 T 没有更改 SHA1 或父级。现在我可以将 origin/master 快进到 A'。

我还注意到有一个新选项 --rebase-merges起初听起来不错,后来确实产生了正确的图表——但就像 --preserve-merges仍然会因 M' 上的冲突而停止,并丢失 Mc 中未由 rerere 保存的任何唯一更改。

该问题的另一种表述可能更有用:

给定上面的初始状态,并且刚刚开始一个交互式 rebase ,现在处于 HEAD1 或 HEAD2 状态:
        /...........(T)
       /               \
      /             /...M'  HEAD2
     /              /...    HEAD1
    /           /...N       origin/master
o...o...o...o...B...M...A   master
    \...T........../        topic

(HEAD1 已 checkout N,但尚未执行任何其他操作;HEAD2 创建了一个新的 merge ,其中 N 作为父级 1,T 作为父级 2,但由于 Unresolved 冲突尚未提交)

是否有一些 rebase 命令和/或 git 命令序列将:
  • 计算 M 和 B 之间的差异 Mc(选择 B,因为另一个父 T 没有变化)
  • 将此应用于冲突树 M'(应该完全解决所有冲突,除非 N 引入新的冲突)只需在 N 之上应用它(无需先进行任何 merge )——这些应该是等价的;第二个可能更容易
  • 暂停让人类解决由 N 引入的任何剩余冲突(如果有)。
  • 提交 M' 作为 N 和 T 之间的 merge
  • 像往常一样继续(在这种情况下,将 A 重新设置为 M' 之上的 A')

  • 为什么git默认不这样做?

    最佳答案

    git rerere 无法记录非冲突的根本原因是 git rerere 以廉价和肮脏的方式实现:Git 获取每个初始冲突,剥离一些数据以使其更适用(就像 git patch-id 剥离行号和一些white-space),然后将冲突保存为数据库中的 blob 对象,获得它存储在 rerere 目录中的哈希 ID。稍后,当您 git commit 结果时,Git 将那个特定的冲突更改 blob 与其分辨率配对。所以它只“知道”冲突,而不是任何其他变化。

    稍后的 merge (及其冲突)尝试再次保存冲突,再次获取哈希 ID,并找到配对,因此它使用保存的第二个 blob 作为解决方案。由于非冲突更改未保存在此处,因此它们永远不会作为此过程的一部分出现。

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

    In my (perhaps flawed) understanding of merge commits, they consist of two (or more) parents and a unique changeset (used to store the conflict resolutions, plus any other changes made before committing the merge or later amended to the merge commit).



    这是不正确的。所有提交都只是状态的快照。 merge 在这里并不特别——就像非 merge 提交一样,它们有一个完整的源代码树。他们的特别之处在于他们有两个(或更多) parent 。

    复制非 merge ,如 git cherry-pick 所做的(并且 git rebase 通过反复调用 git cherry-pick 或做一些不太好的事情,但类似),通过使用提交的(唯一的)父作为 merge 的 merge 基础来工作 -作为动词的操作。复制 merge 通常是不可能的,并且 rebase 不会尝试:它只是重新执行 merge 。

    (另一方面, git cherry-pick 会让你选择一个 merge ,使用它的 -m 选项来选择一个特定的父级。Git 只是在三向 merge 操作期间假装它是唯一的父级。理论上, rebase 代码也可以这样做:-m 1 几乎总是正确的父级,并且总是可以使用低级别的 git commit-tree 进行实际提交,从而使其成为 merge 提交。但 git rebase 不这样做。)

    ... if there were any other changes to files that were also fixed in the merge commit (eg. lines purely added in the merge without conflicts, but still needed to be corrected due to changes elsewhere), these are lost.



    是的(出于上述原因)。这也许是人们将此类事情称为“邪恶 merge ”的一个原因(尽管该短语的另一个可能原因是,至少对于 future 可用的所有证据而言,此类更改实际上并未被任何人要求)。虽然它对现有 merge 没有帮助您的目标,但我建议不要进行此类更改:相反,在 merge 之前或之后进行这些更改,在输入或输出 merge 的普通非 merge 提交中,以便稍后rebase -prebase --rebase-merges 可以保留它们。

    关于git - 为什么 git 在执行 rebase-merges rebase 时不会自动重新应用冲突解决方案?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54359393/

    相关文章:

    git - 获取特定用户修改特定文件的提交列表

    git - Git checkout 的幕后发生了什么?

    git - 如何将功能分支 merge 到主分支中,以便新的更改也反射(reflect)出来

    git - Cygwin 上的 Intellij 差异和 merge 工具

    git - git 能否找出要 merge 的分支有过时的更改?

    git - 我如何在 git 中知道一个分支是否已经重新基于 master?

    git - 我们应该(git/版本控制)跟踪 Firebase 托管的 .firebase 文件夹吗?

    git - VSCode 无法识别不在根目录中的 Git 文件夹

    git - 撤消 git 自动 merge

    大重构后的 git rebase