Neu*_*nas 3 git merge plasticscm
我一直在研究试图了解 GIT 合并是如何工作的。我知道有几种合并类型,如递归、章鱼等。我发现解析/递归是最常用的。并且只有在有几个共同的祖先/基础时,递归合并才有用。
但是,我无法找到使用哪种算法(或应该如何计算祖先)并从分支重复合并到主节点。
一个简单的例子。让我们创建一个包含 1 个文件“A”的空项目:
A
Run Code Online (Sandbox Code Playgroud)
然后创建另一个文件“B”并提交给master
A
B
Run Code Online (Sandbox Code Playgroud)
然后我从只有一个文件“A”的第一个版本创建一个分支并创建另一个文件“C”。所以我的分支看起来像这样:
A
C
Run Code Online (Sandbox Code Playgroud)
然后我决定将我的分支更改合并到 master,我得到:
A
B
C
Run Code Online (Sandbox Code Playgroud)
然后我决定回到我的分支并从那里继续我的工作。我创建了另一个文件“D”
A
C
D
Run Code Online (Sandbox Code Playgroud)
现在我想将我的更改从分支合并回主干。祖先是如何计算的?
如果我拿祖先“AC”来说,应该说“B”也是一个新增的,因为它不存在于两个版本中:分支和祖先。
如果我拿祖先“ABC”来说,应该说“B”被删除了,因为B存在于两个版本中:主人和祖先。
这两个选项看起来都不正确。我试图通过使用具有合并解释功能的“Plastic SCM”来弄清楚它。正如它所显示的,祖先/基础被用作版本“AC”,但是它仍然正确地计算了添加了多少文件(只有 1 而不是 2)。
既要总结评论,又要解决所问的问题......
Git 使用用于查找有向无环图的最低公共祖先的算法来计算一对提交的合并基数。精确的算法没有在任何地方描述并且可能会改变,只要新算法产生正确的结果。另请参阅在有向无环图中找到最低共同祖先的算法?
可能有多个 LCA。在这种情况下,-s resolve合并策略会选择其中之一。你无法控制它选择哪一个。该-s recursive合并策略运行git merge他们在同一时间,二,仿佛通过以下:
commits=$(git merge-base --all $left $right)
if len($commits) > 1
a=$commits[0]
for i in range(1, len(commits))
b=$commits[i]
a=$(git-merge-recursively-inner $a $b)
rof
commits=($a)
fi
Run Code Online (Sandbox Code Playgroud)
(在伪代码中)。请注意,内部递归合并本身可能会找到多个合并基;如果是这样,它使用这个算法来合并它们。
最终结果是一次提交,$commits[0]. 这是合并基础。
无论如何,现在我们有了一个单一的合并基提交——来自只找到一个 LCA 的 LCA 查找算法,或者通过合并递归合并来自 LCA 查找算法的多个合并基,或者通过合并——解决只是从列表中选择一个提交——我们可以看看git merge-(recursive|resolve)实际是如何合并文件的。它必须运行两个内部git diff操作,每个操作都打开重命名检测器。
一个文件差异引擎比较两个文件。我们把一个文件放在左边,另一个文件放在右边。在两个文件匹配的地方,差异没有说明。在这两个文件不同的地方,差异引擎(取决于它的好坏程度)会提出一些我们可以应用的更改,以使左侧的内容与右侧文件的内容相匹配。
为了区分一对提交,Git 将一个放在左侧,一个放在右侧。然后它必须在这两个提交中配对文件。Git 可以在启用或不启用重命名检测器的情况下执行此操作。
当没有重命名检测器时,图片非常清晰。左侧和右侧的文件是“同一个文件”当且仅当它们具有相同的名称。添加重命名检测器会标识(标记为“相同”)差异左侧和右侧的某些文件,即使名称已更改。
Git 现有的重命名检测器正在进行一些更改以使其更好。这里不需要确切的细节:我们只需要知道它会说一些文件被重命名,所以是“相同”的文件,即使它们有不同的名称。其他文件自动成为“相同”文件,因为它们具有相同的名称。
对于每个配对的文件,差异引擎会产生一组更改,使左侧文件变成右侧文件。重命名检测器生成需要首先执行的重命名操作。右侧的新文件称为added,左侧提交中存在但右侧提交中不存在的文件将被删除。
因此,提交对的差异导致:
根据需要,对两个提交中都存在的文件进行一些更改。
给定一个合并基础提交,解析和递归都以相同的方式进行:
HEAD,并启用重命名检测。这些是我们的改变。“合并”需要处理单个文件中的高级更改(例如重命名、添加和删除)以及低级更改。将应用合并更改的文件是来自合并库的文件。这保证了结果在所有情况下都有效。
例如,假设我们重命名了一个文件,而他们修改了我们重命名的文件。合并后的更改实际上说,最后,将文件 base.ext 重命名为 head.ext;同时,更改base.ext 的第17 行。 所以我们将更改第 17 行,并重命名文件,捕获这两个操作。
高级操作可能会发生冲突!例如,如果我们重命名一个文件,然后他们将其删除,这就是高级别的冲突。如果我们和他们都重命名文件,除非我们都选择了相同的最终名称,否则就会发生冲突。如果我们和他们都删除了一个文件,这与明显的结果结合得很好。
低级更改也可能发生冲突。如果我们和他们都以不同的方式修改同一行,或者如果我们的更改和它们的更改在任一边缘“接触”,就会发生冲突。例如,如果我们替换第 9 行和第 10 行(删除第 8 行之后的 2 行并在第 8 行之后插入 2 行)并且它们替换第 11 行和第 12 行,我们的更改就会发生。出于一般谨慎,将其称为冲突。
当然,如果我们和他们对相同的原始台词进行相同的更改,那不是冲突。Git 只是获取这些更改的一份副本。
扩展选项-Xours或-Xtheirs通过选择一方(我们的或他们的)来解决低级冲突,而忽略另一方。这仅适用于低级别冲突。从逻辑上讲,它也可以应用于高级冲突,但事实并非如此。
合并所有我们和他们的更改后,Git 会将合并的更改应用于在合并基础提交中找到的快照。如果没有冲突,生成的文件可以自动提交。这是这些合并的默认操作;用于--no-commit抑制此默认提交。
当合并递归使用内部合并进行合并基础提交时,即使存在合并冲突,它也会强制提交结果。您无法看到它对这些冲突做了什么,除非当您的(外部)合并也有冲突时合并基础中出现的任何内容。(在这种情况下,文件的合并基础副本在索引槽 1 中可用。此外,如果您设置merge.conflictStyle为diff3,则冲突文件的每个工作树副本将显示来自合并基础的文本,并带有冲突标记。 )