2

我试图从https://stackoverflow.com/a/37151159git revert了解如何使用三路合并

假设当前分支是B,command 是否git revert C创建了一个提交D,那么这是和的B三向合并的结果?CDC~

4

2 回答 2

3

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:cherry-pickC2revertC2。您当前的提交是L,通过HEAD. Git 现在继续进行三向合并操作。不寻常的是,下面称为B的合并基础是or ,而下面称为R的另一个提交也是or ——无论哪个不是合并基础。对于, B =和R = 。对于, B =和R = 。C1C2C1C2git cherry-pickC1C2git revertC2C1

三路合并如何工作,简而言之但相当完整的形式

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

  • 有一个合并基础提交B
  • 有一个左侧或本地或--ours提交L。代码将其git mergetool称为“本地”,但大多数 Git 命令只是将其称为HEAD--ours.
  • 有一个右手边或远程或--theirs提交Rgit mergetool代码称它为“远程”,而它git merge本身使用MERGE_HEAD.

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

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

对于挑选或恢复,提交BR被迫进行某些特定的提交。例如,如果你运行git revert <hash>B是你确定的提交,R是它的父提交:

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

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

  • git diff --find-renames B L
  • git diff --find-renames B R

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

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

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

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

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

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


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

2git merge -s recursive对于由or调用的递归合并git merge,Git 首先为您找到合并基础。这可能会出现不止一个提交。如果确实发生了这种情况,Git 会使用(的内部版本)合并合并基础git merge。这是“递归合并”的递归部分:合并合并基础以便提出单个提交。那个单一的提交就是最外层的合并基础git merge

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

3对于在合并两个合并基础时发生的递归(内部)合并期间的合并冲突,Git 只需提交冲突的文本。结果不是很漂亮。

于 2019-01-23T22:35:44.830 回答
1

我不知道您是如何将其可视化的……但这就是我的看法:

git revert C当您站在 B 上时,要求 git 在 B 和 C 之间进行 3 路合并〜假设(迫使 git 思考)两个修订版的分支点都是 C。

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

于 2019-01-23T21:06:34.733 回答