从概念上讲,"git revert"如何与三向合并相关



我正在尝试了解git revert如何使用来自https://stackoverflow.com/a/37151159

假设当前分支是B,命令git revert C是否创建了一个提交D,使得BCD相对于C~的三元合并的结果?

git revert所做的是使用异常选择的合并基进行三方合并。这与git cherry-pick的操作相同,只是所选的合并基数不同。

在所有情况下,我们都可以绘制提交图:

...--o--o--C1--C2--...--o   <-- somebranch

o--o--L   <-- our-branch (HEAD)

或:

...--o--C1--C2--o--...--L   <-- our-branch (HEAD)

或类似的(绘制您的图的任何样子)。

您告诉Git:樱桃采摘C2还原C2。您的当前提交是L,可通过HEAD找到。Git现在进行三方合并操作。不同寻常的是,合并基(下面称为B)是C1C2,另一个提交(下面称之为R,也是C1C2——以不是合并基的为准。对于git cherry-pickB=C1R=C2。对于git revertB=C2R=C1

三方合并的工作方式,以简短但合理完整的形式

所有Git合并都以相同的方式实现1我们从2开始有三个提交:

  • 有一个合并基提交B
  • 存在左侧或本地或--ours提交Lgit mergetool代码称其为"local",但大多数Git命令只称其为HEAD--ours
  • 存在右侧或远程或--theirs提交Rgit mergetool代码称其为"远程",而git merge本身使用MERGE_HEAD

许多实际合并的合并基础从图中可以明显看出:

o--...--L   <-- our-branch (HEAD)
/
...--o--B

o--...--R   <-- their-branch

对于cherry-pick或revert,提交BR将被强制执行某些特定的提交。例如,如果运行git revert <hash>B是您标识的提交,R则是其父级:

...--o--R--B--o--...--L   <-- our-branch (HEAD) 

现在,有了BLR这三个提交(或者更确切地说,它们的哈希ID),Git实际上将运行两个git diff操作:

  • git diff --find-renamesBL
  • git diff --find-renamesBR

第一个diff查找底部和左侧之间不同的文件(包括跨越该间隙的任何重命名文件)。第二个diff查找基础和右手边不同的文件,同样包括任何重命名的文件。

任何一方上未更改的任何文件在所有三次提交中都是相同的。合并结果是所有三个提交共享的文件的(单个)版本。

任何只在一侧更改的文件,Git都会从该一侧获取文件的版本。

任何在两侧更改的文件,但内容相同,Git可以复制LR。这两个副本在定义上是相同的,所以Git选择了一个(实际上总是L,因为它更方便——Git直接在索引中完成所有这些工作,这可以避免将L文件从零位移出!)。

最后,对于两侧更改的任何文件,Git都会尝试合并这两组更改,可能成功,也可能失败。组合的更改将应用于来自基本提交B的文件副本。如果合并成功,那就是合并的结果。否则,Git会在工作树中尽最大努力进行合并,并因合并冲突而停止3添加-X ours-X theirs,会告诉Git:不要停止冲突,而是通过从diff中选择我们的或他们的hunk来解决冲突。请注意,这是只有情况,实际上必须填充文件的三个索引槽,然后调用低级别合并代码(或.gitattributes中的合并驱动程序,如果您设置了一个)。

成功的结果由git merge自动提交为合并提交,或者由git cherry-pickgit revert自动提交为普通提交,除非您告诉Git不要提交结果。失败的(由于冲突)合并将停止,在索引和工作树中留下一团混乱,必须进行清理。


1Git所谓的章鱼合并仍然是这样工作的,但它是迭代的,重复地将多个分支提示合并到索引中,而不提交结果。这使得它有点特别,因为ours状态在索引中仅,而不是实际的提交。其他Git命令通常会检查索引和HEAD提交是否匹配,除了git cherry-pick -ngit revert -n只是使用索引,就好像是提交一样,就像octopus merge一样。在上面的主答案文本中,您可以将索引的内容视为ours提交:在内部,Git只是将所有阶段0的条目转移到阶段2,以实现这一点。

2对于git merge -s recursivegit merge调用的递归合并,Git首先为您找到合并基础。这可能会出现多个提交。如果真的发生了这种情况,Git会使用(内部版本的)git merge合并合并库。这是"递归合并"的递归部分:合并合并基,以便产生单个提交。那么,该单个提交就是最外层git merge的合并基础。

如果你使用git merge -s resolve,并且Git找到了多个合并库,Git会选择一种更简单的方法:它以(看起来)随机的方式选择其中一个合并库(这并不是真正的随机——它只是从其合并库查找算法中最容易找到的一个——但它没有经过仔细控制;没有内在的理由更喜欢任何一个候选合并库而不是任何其他合并库)。

3对于合并两个合并基时发生的递归(内部)合并过程中的合并冲突,Git只需提交冲突的文本。结果不是很好。

我不知道你是如何将其可视化的……但我就是这样看待它的:

当你站在B上时,git revert C要求git在B和C之间进行3路合并~假设(迫使git思考)两个版本的分支点是C.

定义:在正常情况下,分支点将是两个分支历史上的最后一个修订。在那次修订之后,这两个分支的历史不再共享另一个共同的祖先。

最新更新