当前位置: 首页 > 面试题库 >

强化冲突合并算法

邬弘化
2023-03-14
问题内容

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

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),以及一些“其他”一次(也称为“其”和“远程”)
  • 合并查找这些提交之间 的 合并基础
    • 通常,这只是另一个提交:隐含分支1联接的第一点上的提交
    • 在某些特殊情况下(多个合并基础候选对象),Git必须发明一个“虚拟合并基础”(但在这里我们将忽略这些情况)
  • 合并运行两个差异:git diff base localgit diff base other
    • 这些已打开重命名检测
    • 您可以自己运行这些diff来查看合并内容

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

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

如果 仅在一侧 (基础到本地或基础到另一方)更改了文件,则Git会简单地进行这些更改。混帐只有当一个文件时,改变做了三路合并 两个 方面。

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

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

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

任何不冲突的差异大块,Git均适用。如果存在冲突,Git通常会使文件处于“冲突合并”状态。但是,两个-X参数(-X ours-Xtheirs)对此进行了修改:使用-X oursGit在冲突中选择“我们的” diff大块,然后将该更改放入,而忽略“其”更改。使用-X theirsGit选择“他们的”差异块,然后将更改放入,而不考虑“我们的”更改。这两个-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认为
基本 是什么。经常发生冲突是因为diff选择了错误的基数(例如某些匹配的大括号和/或空行),而不是因为必须存在实际的冲突。

至少在理论上,使用“耐心”差异可能会导致基本选择不佳。我自己还没有尝试过。 Git
2.9中新的“压缩启发式”很有希望,但是我也没有尝试过。

您必须始终检查和/或测试合并的结果。 如果已经提交了合并,则可以编辑文件,构建和测试git add更正的版本,并用于git commit --amend推开先前的(错误的)合并提交,并使用相同的父项进行不同的提交。(该--amend部分git commit --amend是虚假广告它不会改变当前的承诺本身,因为它。 不能,而是它与同一个新的提交 的父类标识
为当前犯,而不是使用目前的常规方法提交的ID作为新提交的父级。)

您也可以禁止自动提交与的合并--no-commit。在实践中,我对此几乎没有什么需要:大多数合并大多只是工作git show -m而已,并且快速眼球和/或“它可以html" target="_blank">编译并通过单元测试”就可以发现问题。但是,在发生冲突或--no-commit合并时,简单的方法gitdiff会为您提供组合的差异(提交合并后,gitshow如果不带-m,则为相同的排序),这可能会有所帮助,也可能会更加令人困惑。您可以运行更特定的gitdiff命令和/或检查三个(基本,本地,其他)插槽条目

看看Git会看到什么

除了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(且 git diff)在foo和之间找到合并基础HEAD,然后比较该提交(该合并基础)以进行提交HEAD。和:

$ git diff HEAD...foo   # again, three dots

做同样的事情,发现HEAD和之间的合并基数foo-““合并基数””是可交换的,因此它们应该与其他方法相同,例如7 + 2和2 +
7均为9,但这次将合并基数与提交foo。1个

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

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

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

2该算法实际上是可选的。默认myers值基于EugeneMyers的算法,但是Git还有其他一些选择。



 类似资料:
  • 对于很多人来说,合并时出现冲突是非常可怕的事,这就好像一不小心格式化了自己的硬盘一样。在这一章节里我将为你消除这种恐惧。 你不会把事情搞砸 首先你应该记住,你总是可以撤销一个合并操作,并且返回到冲突发生之前的状态。也就是说,你永远有机会放弃并重新开始。 如果你已经掌握了一些关于其它的版本控制系统的使用经验,例如 Subversion ,你可能会很难过。因为在 Subversion 中处理冲突是被大

  • 我制作了一个git存储库,并向其中添加了一个文本文件。这是100%用于学习目的。 > 从master创建了一个新分支,并附加了“2”。 最后,从master创建了一个分支并添加了“3”。 请您解释一下在这种或任何其他情况下,冲突是如何发生的?

  • 把issue2分支和issue3分支的修改合并到master。 切换master分支后,与issue2分支合并。 $ git checkout master Switched to branch 'master' $ git merge issue2 Updating b2b23c4..8f7aa27 Fast-forward myfile.txt | 2 ++ 1 files chan

  • 在合并中我们已经学会了如何处理简单的Merge。 Mercurial当然也处理更加复杂的 Merge。很平常的情况是两个人同时更改同一个文件的同一段代码,然后必须给出处理的方法。这称之为冲突;处理这类冲突称之为合并。 首先让我们人为的创建一个冲突的实例。 正如我们前面所做的, 通过做一个my-hello的Clone"开始: $ cd .. $ hg clone my-hello my-hello

  • 问题内容: 您能否建议我在发布者和订阅者之间合并期间自动解决主键冲突的方法。看来Sql Server并没有开箱即用:(。 冲突查看器向我显示了下一条消息: 无法将“ publisher_server”上的行插入传播到“ subscriber_server”。此失败可能是由于违反约束引起的。违反主键约束’PK_ PartPlan FD9D7F927172C0B5’。无法在对象“ _table_nam

  • 为了更新核心数据中的数据(当核心数据中已经有数据时),我删除了所有数据,然后重新插入数据。但我不知道为什么会发生合并冲突。我对核心数据还不熟悉,所以我也搞不清楚它到底出了什么问题。我想我需要更改我的deleteAll函数,但我不知道该更改什么。 代码如下。 我可以使用saveContext来保存核心数据,但我也需要更改位置,并且在重新启动后它不起作用。重新启动后,元素的顺序就像我第一次设置的一样。