合并提交的共同祖先位于 Git 中(当存在交叉合并时)?
Where does the common ancestor of merged commits reside in Git (when there's criss-cross merge)?
我是如何从@torek 的彻底回答(例如,)中得知的,当 Git 在两个分支(提交)之间进行合并并且一些冲突阻碍了它时,有三个索引中的非空“插槽”:第一个存储每个文件的通用(基本)版本,第二个存储本地(我们的)版本,第三个存储远程(他们的)版本。
设想起来非常简单,因为所有这些版本实际上都作为 blob 对象存储在 objects 目录中。所以 Git 可以从这些 blob 对象中提取需要的版本到它的索引中。
但是,如果出现纵横交错的合并怎么办?在这种情况下,我们有多个合并基础,并且 Git 将它们全部合并到一个提交中,考虑到新的合并基础是单个基础(参见 )。
我发现 Git 没有为此新提交创建任何 blob 对象。这是否意味着基本版本只存在于工作内存中(Git 索引的第一个插槽)?因此,如果我的计算机关闭并且我有一个未解决的冲突,生成的基本提交是否会丢失?
根据评论更新(现在没有时间研究这个,所以我假设 ET 笔记是准确的...)
首先快速说明一下,这一切都以递归合并策略为前提,并且在相当具体的情况下,它将尝试从多个候选者计算合并基础。
有了这个,如果我们设法让所有这些合并影响单个文件,就很容易看到发生了什么。 “最简单”是一个相对术语,但我们开始...
O -- A - M2 <--(master)
\ X
---B - M1 <--(dev)
在 O
中,我们有一个名为 foo
的文件。 A
和 B
各自以不冲突的方式更改此文件。 (它不冲突可能不是绝对必要的 - 显然即使是冲突的结果也可以作为递归策略中的计算合并基础 - 但如果你想重现这些步骤并查看回购协议,它可以更容易地看到发生了什么.)
M1
是 A
和 B
的“邪恶合并”;虽然 A
和 B
会完全合并,但在创建 M1
.
时对 foo
进行了额外更改
同样,M2
是 A
和 B
的“邪恶合并”; M2
中对 foo
的添加更改与 M1
中对 foo
的添加更改冲突。
现在,如果我们尝试将 dev
与 master
合并,那么 foo
就会发生冲突;果然
git cat-file -p :1:foo
将向我们展示一个我们之前从未存储过的文件 - A
和 B
.
中的 foo
版本之间完全合并的结果
现在回答你的问题:
I found Git does not create any blob object for this new commit
这个说法有点令人困惑;提交不由 BLOB
个对象表示。确实没有 COMMIT
对象表示计算出的合并基础。
但是 是 :1:foo
的新 BLOB
- 就像任何索引条目 [1] 一样。还有(在我的测试中)一个包含 BLOB
的 TREE
对象。换句话说,计算出的合并基础的整个 content 实际上存储在 .git/objects
.
中
Does it mean the base versions exist nowhere but in the working memory (the 1st slot of the index for Git)?
基础版本存在于索引的第一个位置,但那是绝对不是“工作内存”。在您解决冲突期间,通常甚至没有 git 进程 运行 - 因此没有进程的工作内存可用于包含它。
如上所述,索引由磁盘上的 BLOB
个对象组成。
And so, if my computer shuts down and I have an unresolved conflict, would the generated base commit be lost?
没有
但如果是的话会有关系吗?如果不知何故自动生成的合并基丢失了,你必须重新开始,git会自动重新生成它。
[1] - 好的... 叹息... 不是“任何”索引条目。当您声明添加文件的意图时,有“content-less”索引条目。但这与这一切并没有真正的关系,它只是混淆了事情。提它只是为了领先于学究和巨魔。
出于好奇,我一度在 git merge-recursive
中深入研究了这段代码。这是可怕的细节(每个 的“可怕”)。
首先,共享大部分代码的 git merge-recursive
或 git merge-resolve
收集所有合并基础提交哈希 ID。这形成了一个简单的列表(当我查看时存储为链接列表,但重要的是它是一个有序列表)。如果列表的长度为 1,则我们有一个合并基础,我们就完成了。否则我们有多个合并基础:
如果我们git merge-resolve
,我们“随机”选择一个(最方便的,可能是列表的头部)并使用它,我们就完成了。
否则——当我们git merge-recursive
——我们使用以下算法:
而列表中至少有两个条目:
- 用递归调用合并前两个合并基。这会产生合并结果(在索引中)。如果有合并冲突,冲突会出现在索引中。1
- 强制将所有结果推入索引槽零。从此树进行新提交。
- 用步骤 2 中的单个提交哈希 ID 替换提交哈希 ID 对。
本次循环结束时,列表中只有一个哈希ID。这是合并基地。
注意这个算法在合并碱基的数量上是线性的,当我们可以使用对数策略时:如果有,比如说,16 个合并碱基,我们可以合并每一对得到 8 个合并碱基,然后合并每个pair 得到 4,然后合并每对得到 2,然后合并 pair。相反,我们合并 2 得到 1,留下 15 合并;合并2得到1,剩下14;等等。
这可能是完全合理的。 Criss-cross 合并可为您提供 two-merge-base 设置,但这种情况很少见。我不确定您将如何设法设置 16-merge-base 情况。 编辑: 而且,,我们无论如何都不会保存任何主要工作。
1这解释了为什么冲突的对象出现在存储库中。然而,如果我们正在执行最外层的合并——递归代码总是知道这一点——就没有必要将这个对象放入存储库中。请注意,此处用于低级合并的 merge.driver
是执行最外层合并时定义的驱动程序,以及执行任何内部合并时的递归驱动程序;这就是为什么我们知道这是否是最外层的合并。如果它是最外层的合并,则无需在 Git 存储库数据库中创建 blob。
我是如何从@torek 的彻底回答(例如,
设想起来非常简单,因为所有这些版本实际上都作为 blob 对象存储在 objects 目录中。所以 Git 可以从这些 blob 对象中提取需要的版本到它的索引中。
但是,如果出现纵横交错的合并怎么办?在这种情况下,我们有多个合并基础,并且 Git 将它们全部合并到一个提交中,考虑到新的合并基础是单个基础(参见
我发现 Git 没有为此新提交创建任何 blob 对象。这是否意味着基本版本只存在于工作内存中(Git 索引的第一个插槽)?因此,如果我的计算机关闭并且我有一个未解决的冲突,生成的基本提交是否会丢失?
根据评论更新(现在没有时间研究这个,所以我假设 ET 笔记是准确的...)
首先快速说明一下,这一切都以递归合并策略为前提,并且在相当具体的情况下,它将尝试从多个候选者计算合并基础。
有了这个,如果我们设法让所有这些合并影响单个文件,就很容易看到发生了什么。 “最简单”是一个相对术语,但我们开始...
O -- A - M2 <--(master)
\ X
---B - M1 <--(dev)
在 O
中,我们有一个名为 foo
的文件。 A
和 B
各自以不冲突的方式更改此文件。 (它不冲突可能不是绝对必要的 - 显然即使是冲突的结果也可以作为递归策略中的计算合并基础 - 但如果你想重现这些步骤并查看回购协议,它可以更容易地看到发生了什么.)
M1
是 A
和 B
的“邪恶合并”;虽然 A
和 B
会完全合并,但在创建 M1
.
foo
进行了额外更改
同样,M2
是 A
和 B
的“邪恶合并”; M2
中对 foo
的添加更改与 M1
中对 foo
的添加更改冲突。
现在,如果我们尝试将 dev
与 master
合并,那么 foo
就会发生冲突;果然
git cat-file -p :1:foo
将向我们展示一个我们之前从未存储过的文件 - A
和 B
.
foo
版本之间完全合并的结果
现在回答你的问题:
I found Git does not create any blob object for this new commit
这个说法有点令人困惑;提交不由 BLOB
个对象表示。确实没有 COMMIT
对象表示计算出的合并基础。
但是 是 :1:foo
的新 BLOB
- 就像任何索引条目 [1] 一样。还有(在我的测试中)一个包含 BLOB
的 TREE
对象。换句话说,计算出的合并基础的整个 content 实际上存储在 .git/objects
.
Does it mean the base versions exist nowhere but in the working memory (the 1st slot of the index for Git)?
基础版本存在于索引的第一个位置,但那是绝对不是“工作内存”。在您解决冲突期间,通常甚至没有 git 进程 运行 - 因此没有进程的工作内存可用于包含它。
如上所述,索引由磁盘上的 BLOB
个对象组成。
And so, if my computer shuts down and I have an unresolved conflict, would the generated base commit be lost?
没有
但如果是的话会有关系吗?如果不知何故自动生成的合并基丢失了,你必须重新开始,git会自动重新生成它。
[1] - 好的... 叹息... 不是“任何”索引条目。当您声明添加文件的意图时,有“content-less”索引条目。但这与这一切并没有真正的关系,它只是混淆了事情。提它只是为了领先于学究和巨魔。
出于好奇,我一度在 git merge-recursive
中深入研究了这段代码。这是可怕的细节(每个
首先,共享大部分代码的 git merge-recursive
或 git merge-resolve
收集所有合并基础提交哈希 ID。这形成了一个简单的列表(当我查看时存储为链接列表,但重要的是它是一个有序列表)。如果列表的长度为 1,则我们有一个合并基础,我们就完成了。否则我们有多个合并基础:
如果我们
git merge-resolve
,我们“随机”选择一个(最方便的,可能是列表的头部)并使用它,我们就完成了。否则——当我们
git merge-recursive
——我们使用以下算法:而列表中至少有两个条目:
- 用递归调用合并前两个合并基。这会产生合并结果(在索引中)。如果有合并冲突,冲突会出现在索引中。1
- 强制将所有结果推入索引槽零。从此树进行新提交。
- 用步骤 2 中的单个提交哈希 ID 替换提交哈希 ID 对。
本次循环结束时,列表中只有一个哈希ID。这是合并基地。
注意这个算法在合并碱基的数量上是线性的,当我们可以使用对数策略时:如果有,比如说,16 个合并碱基,我们可以合并每一对得到 8 个合并碱基,然后合并每个pair 得到 4,然后合并每对得到 2,然后合并 pair。相反,我们合并 2 得到 1,留下 15 合并;合并2得到1,剩下14;等等。
这可能是完全合理的。 Criss-cross 合并可为您提供 two-merge-base 设置,但这种情况很少见。我不确定您将如何设法设置 16-merge-base 情况。 编辑: 而且,
1这解释了为什么冲突的对象出现在存储库中。然而,如果我们正在执行最外层的合并——递归代码总是知道这一点——就没有必要将这个对象放入存储库中。请注意,此处用于低级合并的 merge.driver
是执行最外层合并时定义的驱动程序,以及执行任何内部合并时的递归驱动程序;这就是为什么我们知道这是否是最外层的合并。如果它是最外层的合并,则无需在 Git 存储库数据库中创建 blob。