解决冲突融合算法

时间:2016-06-24 11:30:57

标签: java git merge conflict

我看一下看起来搞砸了的合并标记。为了给你这种情况,我们可以:

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正在尝试所谓的自动合并,如果失败并且检测到冲突,则完整合并由标有&#39;&lt;&lt;&lt;&lt; * HEAD& #39; +之前+&#39; ====&#39; + +&#39;&gt;&gt;&gt; *提交ID&#39;并准备手动解决冲突。

那为什么会遗漏一些线条呢。它看起来更像是我的错误。

我使用的是Windows7,安装的git版本是 2.6.2.windows.1 。虽然最新的版本是2.9,但我想知道是否有任何关于具有如此规模的合并问题的git版本的知识?这不是我第一次经历这样的事情......

2 个答案:

答案 0 :(得分:8)

你是正确的:Git对语言一无所知,它的内置合并算法严格基于时间线比较。 您不必使用此内置合并算法,但大多数人这样做是因为(a)它主要起作用,(b)没有那么多替代方案。

请注意,这取决于您的合并策略-s参数);以下文字适用于默认的recursive策略。 resolve策略与recursive非常相似; octopus策略适用于不止两次提交;并且ours策略完全不同(并且与-X ours完全不同)。您还可以使用.gitattributes和&#34;合并驱动程序&#34;为特定文件选择其他策略或算法。并且,这些都不适用于Git已经决定相信的文件&#34;二进制&#34;:对于这些文件,它甚至不会尝试合并。 (我不会在这里讨论任何内容,只是默认的recursive策略如何处理文件。)

git merge的工作原理(使用默认-s recursive时)

  • 合并从两个提交开始:当前的一个(也称为&#34;我们的&#34;,&#34;本地&#34;和HEAD),以及一些&#34;其他&#34 ;一个(也称为#34;他们的#34;和#34;远程&#34;)
  • Merge在这些提交之间找到合并基础
    • 通常只有另一个提交:隐含分支 1 的第一个点加入
    • 在某些特殊情况下(多个合并基础候选者),Git必须发明一个&#34;虚拟合并基础&#34; (但我们在这里忽略这些情况)
  • 合并运行两个差异:git diff base localgit diff base other
    • 这些已启用重命名检测
    • 您可以自己运行这些相同的差异,以查看合并将会看到的内容

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

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

如果仅在一侧(从基站到本地,或从基站到其他)更改文件,Git只会进行这些更改。当在两个侧更改文件时,Git只需要进行三向合并。

要执行三向合并,Git基本上会遍历两个差异(基础到本地和基础到其他),一个&#34;差异大块&#34;一次,比较变化的区域。如果每个hunk影响原始基本文件的不同部分,则Git只接受该块。如果某些hunk影响基本文件的相同部分,Git会尝试获取任何更改的副本。

例如,如果本地更改显示&#34;添加一个紧密支撑线&#34;并且远程更改说&#34;添加(相同的地方,相同的缩进)闭括号线&#34;,Git只需要一个闭括号的副本。如果他们都说&#34;删除一个紧密的支撑线&#34; Git只会删除该行一次。

只有当两个差异冲突 -e.g。时,才会说&#34;添加一个缩进的支撑线,缩进12个空格&#34;另一个说&#34;添加一个紧密的支撑线,缩进11个空格&#34;将Git宣布冲突。默认情况下,Git会将冲突写入文件,显示两组更改 - 如果您将diff.renameLimit设置为merge.conflictstyle显示代码文件的合并基础版本

任何非冲突的差异,Git适用。如果存在冲突,Git通常会将文件保留在&#34;冲突的合并中。州。但是,两个diff3参数(-X-X ours)会对此进行修改:使用-X theirs Git选择&#34;我们的&#34;冲突中的差异,并将这种变化放入,忽略&#34;他们的&#34;更改。随着-X ours Git选择&#34;他们的&#34;差异并将这种变化放入,忽略&#34;我们的&#34;更改。这两个-X theirs参数保证Git不会声明冲突。

如果Git能够自行解决此文件的所有内容,它会这样做:您在工作树和索引/暂存区域中获取基本文件,本地更改以及其他更改

如果Git无法自行解决所有问题,它会使用三个特殊的非零索引槽将文件的基本版本,其他版本和本地版本放入索引/登台区域。工作树版本总是&#34; Git能够解决的问题,以及各种可配置项目指示的冲突标记。&#34;

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

-X等文件通常在插槽0中暂存。这意味着它现在可以进入新的提交了。根据定义,其他三个插槽为空,因为有一个插槽零条目。

在冲突合并期间,插槽零保留为空,插槽1-3用于保存合并基本版本,&#34; local&#34;或foo.java版本,以及其他版本或--ours版本。工作树保存正在进行的合并。

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

一些git checkout命令会使各个插槽不受干扰。一些git checkout命令写入插槽0,消除插槽1-3中的条目,以便文件准备好提交。 (要知道哪些人做了什么,你只需要记住它们。我的错误,在我脑海里,已经有一段时间了。)

在清除所有未合并的广告位之前,您无法运行git checkout。您可以使用git commit查看未合并的广告位,或使用git ls-files --unmerged查看更适合人性化的版本。 (提示:使用git status。经常使用它!)

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

即使git status成功自动合并所有内容,这并不意味着结果是正确的!当然,当它因冲突而停止时,这也意味着Git无法使用自动合并所有内容,而不是它自己合并的内容是正确的。我想将git merge设置为merge.conflictstyle,这样我就可以看到Git认为 base 是什么,之后它取代了&#34; base&#34;代码与合并的两面。通常会发生冲突,因为差异选择了错误的基础 - 例如一些匹配的括号和/或空行 - 而不是因为必须存在实际的冲突。

使用&#34;耐心&#34;至少在理论上,差异可以保持不良的基础选择。我自己没有尝试过这个。 The new "compaction heuristic" in Git 2.9很有希望,但我也没有尝试过这个。

您必须始终检查和/或测试合并的结果。 如果合并已提交,您可以编辑文件,构建和测试{{1更正后的版本,并使用diff3将先前(不正确的)合并提交推出,并使用相同的父项进行不同的提交。 (git add的{​​{1}}部分是虚假广告。它不会更改当前提交本身,因为可以不会;相反,它会使用相同的< em>父ID 作为当前提交,而不是使用当前提交的ID作为新提交的父级的常规方法。)

您还可以禁止使用git commit --amend自动提交合并。在实践中,我发现没有必要这样做:大多数合并主要只是工作,快速注视--amend和/或&#34;它编译并通过单元测试&#34;遇到问题。但是,在冲突或git commit --amend合并期间,简单的--no-commit将为您提供合并后的差异(与git show -m无法--no-commit相同的排序,提交合并后),这可能会有所帮助,或者可能更令人困惑。您可以运行更具体的git diff命令和/或检查三个(基本,本地,其他)插槽条目,如Gregg noted in a comment

看看Git会看到什么

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

要执行这些操作,您必须找到 - 或者至少告诉git merge找到合并基础。你可以使用git diff,它真正找到(或所有)合并基础并打印出来:

git merge

这表示在当前分支和分支git diff之间,合并基础是提交git merge-base(并且只有一个这样的合并基础)。然后,我可以运行$ git merge-base --all HEAD foo 4fb3b9e0570d2fb875a24a037e39bdb2df6c1114 foo。但是有一种更简单的方法,只要我能假设只有一个合并基础:

4fb3b9e...

这会告诉git diff 4fb3b9e HEAD(以及 git diff 4fb3b9e foo)找到$ git diff foo...HEAD # note: three dots git diff之间的合并基础,然后比较该提交 - 合并base-to commit git diff。和

foo

做同样的事情,找到HEADHEAD之间的合并基础 - &#34;合并基础&#34;是交换所以这些应该是相同的,如7 + 2和2 + 7都是9 - 但这次将合并基础与提交$ git diff HEAD...foo # again, three dots 区分开来。 1 < / p>

(对于其他命令 - 不是HEAD的东西 - 三点语法产生对称差异:两个分支上的所有提交的集合,但不是两个对于具有单个合并基础提交的分支,这是&#34;每个提交合并基础之后,在每个分支上&#34;:换句话说,两个分支的并集,不包括合并基础本身和任何早期的提交。对于具有多个合并基础的分支,这会减去所有合并基础。对于foo,我们只假设那里只有一个合并基础,而不是减去它和它的祖先,我们用它作为左边或&#34;在差异之前#);

1 在Git中,分支名称标识一个特定的提交,即分支的 tip 。实际上,这就是分支实际工作的方式:分支名称命名一个特定的提交,并为了向branch- 分支添加另一个提交,这意味着提交链 -Git进行新的提交,其父项是当前的分支提示,然后将分支名称指向新的提交。单词&#34; branch&#34;可以引用分支名称或整个提交链;我们应该根据具体情况找出哪一个。

在任何时候,我们都可以命名一个特定的提交,并将其视为一个分支,方法是将提交及其所有祖先:其父级,其父级的父级,等等上。当我们点击合并提交 - 一个包含两个或更多父母的提交时 - 在此过程中,我们将所有父提交,以及他们的父母&#39;父母,等等。

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

答案 1 :(得分:2)

在合并中,仅标记包含冲突的更改。

Rev A中的更改和Rev B中的不同更改将直接合并。只有Rev A和Rev B在同一位置的更改才会标记为冲突。系统会通知用户文件中存在冲突,需要解决。

当您去解决冲突时,合并后的文件已经具有Rev A和Rev B的独立更改,以及冲突部分的冲突标记。

相关问题