gpt4 book ai didi

java - 了解冲突 merge 算法

转载 作者:行者123 更新时间:2023-12-04 20:51:05 29 4
gpt4 key购买 nike

我看着一个合并标记,看起来都搞砸了。为了给您带来这种情况,让我们这样做:

public void methodA() {
prepare();
try {
doSomething();
}
catch(Exception e) {
doSomethingElse();
}
}

现在进行合并(我使用SourceTree进行拉取)。
标记看起来像这样:
<<<<<<<<< HEAD
try {
doSomething();
}
catch(Exception e) {
doSomethingElse();
}
============================
private void methodB() {
doOtherStuff();
>>>>>>>> 9832432984384398949873ab
}

因此,拉出的提交所做的是完全删除methodA并添加methodB。

但是您注意到有些行完全丢失了。

据我了解的过程,Git正在尝试一种所谓的自动合并,如果失败并在检测到冲突时发生冲突,则完全合并将由标有'<<< * HEAD'+ + +'====的部分表示。 '+后+'>>> * CommitID'并准备手动冲突解决方案。

那么为什么它省略了一些行。在我看来,这更像是个虫子。

我使用Windows7,并且安装的git版本是 2.6.2.windows.1 。虽然最新版本是2.9,但我想知道关于git版本有这么大合并问题的信息吗?这不是我第一次遇到这样的事情。

最佳答案

您的担心是对的:Git不懂任何语言,它的内置合并算法严格基于时线比较。 您不必使用此内置的合并算法,但是大多数人都这样做,因为(a)它大部分都可以工作,并且(b)没有太多选择。

注意,这取决于您的合并策略(-s参数);以下文字是默认的recursive策略。 resolve策略与recursive非常相似; octopus策略不仅仅适用于两次提交;而ours策略则完全不同(与-X ours完全不同)。您还可以使用.gitattributes和“合并驱动程序”为特定文件选择替代策略或算法。而且,这都不适用于Git决定认为是“二进制”的文件:对于这些文件,它甚至不尝试合并。 (我不会在这里讨论任何内容,只是默认recursive策略如何处理文件。)
git merge的工作方式(使用默认-s recursive时)

  • 合并以两次提交开始:当前一次(也称为“我们的”,“本地”和HEAD),以及一些“其他”一次(也称为“其”和“远程”)的
  • Merge查找这些提交之间的合并基础
  • 通常,这只是另一个提交:隐式branch1加入
  • 的第一点的提交
  • 在某些特殊情况下(多个合并基础候选对象),Git必须发明一个“虚拟合并基础”(但在这里我们将忽略这些情况)
  • 合并运行两个差异:git diff base localgit diff base other
  • 这些已重命名检测已打开
  • 您可以自己运行这些相同的差异,以查看
  • 会发生什么合并

    您可以将这两个差异视为“我们做了什么”和“他们做了什么”。合并的目的是将“我们所做的”和“他们所做的”结合起来。 差异是基于行的,来自最小编辑距离算法2,实际上只是Git对我们做了什么以及他们做了什么的猜测。

    第一个diff(base-vs-local)的输出告诉Git哪些基础文件与哪些本地文件相对应,即如何从当前提交将名称跟随到基础上。然后,Git可以使用基本名称在其他提交中发现重命名或删除。在大多数情况下,我们可以忽略重命名和删除问题以及新文件创建问题。请注意,默认情况下,Git 2.9版会为所有差异(不仅是合并差异)打开重命名检测。 (您可以在早期的Git版本中通过将 diff.renames配置为 true来启用此功能;另请参见 git configdiff.renameLimit设置。)

    如果仅在一侧(基础到本地或基础到另一方)更改了文件,则Git会简单地进行这些更改。当两面都更改文件时,Git只需要进行三向合并。

    为了执行三向合并,Git本质上遍历了两个差异(基本到本地和基本到另一差异),一次比较一个“差异块”,比较了变化的区域。如果每个块都影响原始基础文件的不同部分,则Git会接受该块。如果某些块影响基本文件的同一部分,则Git会尝试获取该更改的一份副本。

    例如,如果本地更改显示为“添加一条右括号”,而远程更改显示为“添加(在同一位置,相同的缩进)封闭括号”,则Git将仅获取该副本的一个副本。如果两个都说“删除右括号”,Git只会删除该行一次。

    仅当两个差异发生冲突时,例如,一个说“添加一个缩进的括号内缩进12个空格”,另一个说“添加一个闭合的括号内缩进11个空格”,Git才会声明冲突。默认情况下,Git将冲突写入文件中,显示两组更改-并且,如果将 merge.conflictstyle设置为 diff3,还显示文件的基于合并的版本中的代码。

    任何不冲突的差异大块,Git均适用。如果存在冲突,Git通常会使文件处于“冲突合并”状态。但是,两个 -X参数( -X ours-X theirs)对此进行了修改:使用 -X ours Git在冲突中选择“我们的” diff大块,然后将该更改放入,而忽略“其”更改。使用 -X theirs,Git选择“他们的” diff块并将其放入,而不考虑“我们的”改变。这两个 -X参数保证Git最终不会声明冲突。

    如果Git能够自行解决此文件的所有问题,它就可以做到:您将在工作树和索引/临时区域中获取基本文件,以及本地更改以及其他更改。

    如果Git不能自行解决所有问题,它将使用三个特殊的非零索引插槽将文件的基础版本,其他版本和本地版本放入索引/临时区域。工作树版本始终是“Git能够解决的问题,以及各种可配置项指示的冲突标记”。

    每个索引条目都有四个插槽

    诸如 foo.java之类的文件通常在插槽0中暂存。这意味着现在就可以进行新提交了。根据定义,其他三个插槽为空,因为存在零插槽条目。

    在发生冲突的合并期间,插槽零保留为空,并且插槽1-3用于保存合并的基本版本,“本地”或 --ours版本以及另一个或 --theirs版本。工作树保存正在进行的合并。

    您可以使用 git checkout提取任何这些版本,或使用 git checkout -m重新创建合并冲突。所有成功的 git checkout命令都会更新文件的工作树版本。

    一些 git checkout命令使各个插槽不受干扰。一些 git checkout命令写入插槽0,清除插槽1-3中的条目,以便文件可以提交。 (要知道哪些人在做什么,您只需要记住它们即可。在很长一段时间内,我就把它们弄错了。)

    您必须先清除所有未合并的插槽,然后才能运行 git commit。您可以使用 git ls-files --unmerged查看未合并的插槽,或使用 git status查看更人性化的版本。 (提示:请使用 git status。请经常使用!)

    成功合并并不意味着好的代码

    即使git merge成功自动合并了所有内容,这并不意味着结果正确! 当然,当它因冲突而停止时,这也意味着Git无法自动合并所有内容,而不是它自己自动合并的内容是正确的。我喜欢将 merge.conflictstyle设置为 diff3,以便在将Git的“基本”代码替换为合并的两侧之前,可以了解Git认为的基本情况。经常发生冲突是因为diff选择了错误的基数(例如某些匹配的大括号和/或空行),而不是因为必须存在实际的冲突。

    至少在理论上,使用“耐心”差异可能会导致基本选择不佳。我自己还没有尝试过。 The new "compaction heuristic" in Git 2.9很有前途,但我也没有尝试过。

    您必须始终检查和/或测试合并结果。 如果已提交合并,则可以编辑文件,构建和测试,用 git add纠正版本,并使用 git commit --amend推开先前的(不正确的)合并提交,并使用相同的父项进行其他提交。 ( --amendgit commit --amend部分是虚假广告。它不会更改当前提交本身,因为它不能这样做;相反,它使用与当前提交相同的父ID进行新的提交,而不是使用当前提交的常规方法提交的ID作为新提交的父对象。)

    您还可以禁止使用 --no-commit进行合并的自动提交。在实践中,我发现对此几乎没有必要:大多数合并大多数情况下都只是工作而已,而快速查看 git show -m和/或“它可以编译并通过单元测试”就可以发现问题。但是,在发生合并冲突或 --no-commit的过程中,简单的 git diff将为您提供组合的差异(提交合并后,使用 git show而没有 -m的同类),这可能会有所帮助,或者可能会更加令人困惑。您可以运行更特定的 git diff命令和/或检查三个(基本,本地,其他)插槽条目,作为 Gregg noted in a comment

    看看Git会看到什么

    除了使用 diff3作为 merge.conflictstyle之外,您还可以看到 git merge将看到的差异。您需要做的就是运行两个 git diff命令,即 git merge将运行的两个命令。

    为此,您必须找到(或至少告诉 git diff找到)合并库。您可以使用 git merge-base,从字面上查找(或所有)合并库并将其打印出来:
    $ git merge-base --all HEAD foo
    4fb3b9e0570d2fb875a24a037e39bdb2df6c1114

    这表示在当前分支和分支 foo之间,合并基础是commit 4fb3b9e...(并且只有一个这样的合并基础)。然后,我可以运行 git diff 4fb3b9e HEADgit diff 4fb3b9e foo。但是有一种更简单的方法,只要我可以假设只有一个合并基础:
    $ git diff foo...HEAD   # note: three dots

    这告诉 git diff(并且只有 git diff)找到 fooHEAD之间的合并基础,然后比较该合并基础的提交以提交 HEAD。和:
    $ git diff HEAD...foo   # again, three dots

    做同样的事情,找到 HEADfoo之间的合并基数-“合并基数”是可交换的,因此它们应该与其他方法相同,例如7 + 2和2 + 7均为9,但这一次比较合并以提交 foo .1为基础

    (对于其他命令-并非 git diff的事物-三点语法会产生对称差异:位于两个分支但不在两个分支上的所有提交的集合。对于具有单个合并基础提交的分支,这是“合并基础之后的每个分支上的每个提交”,换句话说,两个分支的并集,不包括合并基础本身和任何更早的提交。对于具有多个合并基础的分支,这将减去所有合并基础。对于 git diff我们只是假设只有一个合并基础,而不是将其及其祖先相减,而是将其用作差异的左侧或“之前”。)

    1在Git中,分支名称标识一个特定的提交,即分支的尖端。实际上,这就是分支的实际工作方式:一个分支名称命名一个特定的提交,并为了向该分支添加另一个提交(这里的分支意味着提交链),Git进行了一个新的提交,其父作为当前的分支提示。 ,然后将分支名称指向新提交。 “分支”一词可以指分支名称,也可以指整个提交链。我们应该根据上下文找出哪一个。

    在任何时候,我们都可以命名一个特定的提交,并通过将该提交及其所有祖先(其父项,其父项的父项,等等)作为分支来对待。在此过程中,当我们执行合并提交时(与两个或多个父项进行的提交),我们将接受所有父项提交,以及他们父母的父母,依此类推。

    2此算法实际上是可选的。默认的 myers基于 Eugene Myers的算法,但是Git还有其他一些选项。

    关于java - 了解冲突 merge 算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38012397/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com