解决冲突融合算法 [英] Understanging Conflicts Merging Algorithm

查看:207
本文介绍了解决冲突融合算法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I look at a merge marker that looked all screwed up. To give you the situation lets have this:

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

现在合并(我使用SourceTree进行拉动) )。
标记符如下所示:

Now comes in a merge (I use SourceTree for pull). And the marker looks like this:

<<<<<<<<< HEAD
    try {
      doSomething();
    }
    catch(Exception e) {
      doSomethingElse();
    }
============================
private void methodB() {
    doOtherStuff();
>>>>>>>> 9832432984384398949873ab
}

所以拉取提交的作用是完全删除methodA并添加methodB 。

So what the pulled commit does is removing the methodA completely and adding methodB instead.

但是你注意到有些线路完全缺失。

But you notice that there are some lines entirely missing.

根据我对这个过程的理解, Git尝试所谓的自动合并,如果失败并且检测到冲突,则完整合并由标有'<<< * HEAD'+ + +'===='+ +'之后的部分表示>>> * CommitID'并准备手动冲突解决方案。

From what I understand of the process, Git is trying a so called auto-merge and if this fails and conflicts where detected, the complete merge is expressed by parts marked with '<<<* HEAD' + before + '====' + after + '>>>* CommitID' and prepare a manual conflict resolution.

那么为什么会遗漏一些行。它看起来更像是一个bug。

So why does it leave out some lines. It looks more like a bug to me.

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

I use Windows7 and the installed git version is 2.6.2.windows.1. While the newest version is 2.9, I wonder if anything is known about a git version having a merge problem of this magnitude? This is not the first time I experienced something like this... .

推荐答案

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

You are correct to be concerned: Git knows nothing of languages, and its built-in merge algorithm is based strictly on line-at-time comparisons. You do not have to use this built-in merge algorithm, but most people do because (a) it mostly just works, and (b) there are not that many alternatives.

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

Note that this depends on your merge strategy (-s argument); the text below is for the default recursive strategy. The resolve strategy is pretty similar to recursive; the octopus strategy applies to more than just two commits; and the ours strategy is entirely different (and is nothing like -X ours). You can also select alternative strategies or algorithms for specific files, using .gitattributes and "merge drivers". And, none of this applies to files that Git has decided to believe are "binary": for these, it does not even attempt merging. (I am not going to cover any of that here, just how the default recursive strategy treats files.)


  • 合并从两个提交开始:当前的一个(也称为我们的,本地和 HEAD ),以及一些其他(也称为他们的和远程)

  • Merge在这些提交之间找到合并基础


    • 通常这只是另一个提交:隐含分支 1 加入的第一个点

    • 在某个特殊情况下案例(多个合并基础候选人),Git必须发明一个虚拟合并基础(但我们会在这里忽略这些案例)

    • Merge starts with two commits: the current one (also called "ours", "local", and HEAD), and some "other" one (also called "theirs" and "remote")
    • Merge finds the merge base between these commits
      • Normally that's just one other commit: the one at the first point where the implied branches1 join up
      • In some special cases (multiple merge base candidates), Git must invent a "virtual merge base" (but we'll ignore these cases here)

      • 这些已启用重命名检测

      • 您可以自己运行这些相同的差异看看合并会看到什么

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

      You can think of these two diffs as "what we did" and "what they did". The goal of a merge is to combine "what we did" and "what they did". The diffs are line based, come from a minimal edit distance algorithm,2 and are really just Git's guess about what we did, and what they did.

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

      The output of the first diff (base-vs-local) tells Git which base files correspond to which local files, i.e., how to follow names from the current commit back to the base. Git can then use the base names to spot renames or deletes in the other commit as well. For the most part we can just ignore rename and delete issues, and also new-file-creation issues. Note that Git version 2.9 turns on rename detection by default for all diffs, not just merge diffs. (You can turn this on yourself in earlier Git versions by configuring diff.renames to true; see also the git config setting for diff.renameLimit.)

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

      If a file is changed on only one side (base-to-local, or base-to-other), Git simply takes those changes. Git only has to do a three-way merge when a file is changed on both sides.

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

      To perform a three-way merge, Git essentially walks through the two diffs (base-to-local and base-to-other), one "diff hunk" at a time, comparing the changed regions. If each hunk affects a different part of the original base file, Git just takes that hunk. If some hunk(s) affect the same part of the base file, Git tries to take one copy of whatever that change is.

      For例如,如果本地更改说添加一个紧密的支撑线并且远程更改说添加(相同的地方,相同的缩进)紧密支撑线,Git将只采用一个闭括号的副本。如果两者都说删除一个支撑线,Git将只删除该行一次。

      For instance, if the local change says "add a close brace line" and the remote change says "add (the same place, same indentation) close brace line", Git will take just one copy of the close brace. If both say "delete a close brace line" Git will just delete the line once.

      仅当两个差异冲突 -eg时,说添加一个紧密的支撑线缩进12个空格,另一个说添加一个紧密的支撑线缩进11个空格将Git声明冲突。默认情况下,Git将冲突写入文件,显示两组更改 - 如果将 merge.conflictstyle 设置为 diff3 显示文件的合并基础版本中的代码

      Only if the two diffs conflict—e.g., one says "add a close brace line indented 12 spaces" and the other says "add a close brace line indented 11 spaces" will Git declare a conflict. By default, Git writes the conflict into the file, showing the two sets of changes—and, if you set merge.conflictstyle to diff3, also showing the code from the merge-base version of the file.

      任何非-glicting diff hunks,Git适用。如果存在冲突,Git通常会将文件保留为冲突合并状态。但是,两个 -X 参数( -X我们的 -X他们的)修改这个:用 -X our Git在冲突中选择我们的差异块,然后将这个变化放入,忽略他们的变化。随着 -X他们的 Git选择他们的差异并将这种变化放入,忽略我们的变化。这两个 -X 参数保证Git不会声明冲突。

      Any non-conflicting diff hunks, Git applies. If there were conflicts, Git normally leaves the file in "conflicted merge" state. However, the two -X arguments (-X ours and -X theirs) modify this: with -X ours Git chooses "our" diff hunk in the conflict, and puts that change in, ignoring "their" change. With -X theirs Git chooses "their" diff hunk and puts that change in, ignoring "our" change. These two -X arguments guarantee that Git does not declare a conflict after all.

      如果Git能够解决对于这个文件,它本身就是一切:它可以在工作树和索引/登台区域中获得基本文件,本地更改以及其他更改。

      If Git is able to resolve everything on its own for this file, it does so: you get the base file, plus your local changes, plus their other changes, in the work-tree and in the index/staging-area.

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

      If Git is not able to resolve everything on its own, it puts the base, other, and local versions of the file into the index/staging-area, using the three special nonzero index slots. The work-tree version is always "what Git was able to resolve, plus the conflict markers as directed by various configurable items."

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

      A file such as foo.java is normally staged in slot zero. This means it is ready to go into a new commit now. The other three slots are empty, by definition, because there is a slot-zero entry.

      在冲突合并期间,插槽零保留为空,插槽1-3用于保存合并基础版本,本地或 - 我们的版本,另一个或 - 他们的版本。工作树保存正在进行的合并。

      During a conflicted merge, slot zero is left empty, and slots 1-3 are used to hold the merge base version, the "local" or --ours version, and the other or --theirs version. The work-tree holds the in-progress merge.

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

      You can use git checkout to extract any of these versions, or git checkout -m to re-create the merge conflict. All successful git checkout commands update the work-tree version of the file.

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

      Some git checkout commands leave the various slots undisturbed. Some git checkout commands write into slot 0, wiping out the entries in slots 1-3, so that the file is ready for commit. (To know which ones do what, you just have to memorize them. I had them wrong, in my head, for quite a while.)

      你不能运行 git commit 直到所有未合并的广告位已被清除。您可以使用 git ls-files --unmerged 查看未合并的广告位,或 git status 以获得更加人性化的广告版。 (提示:使用 git status 。经常使用它!)

      You cannot run git commit until all unmerged slots have been cleared out. You can use git ls-files --unmerged to view unmerged slots, or git status for a more human-friendly version. (Hint: use git status. Use it often!)

      即使 git merge 成功自动合并所有内容,但这并不意味着结果是正确的!当然,当它因冲突而停止时,这也意味着Git无法自动合并所有内容,而不是它自己合并的内容是正确的。我喜欢将 merge.conflictstyle 设置为 diff3 ,以便我可以看到Git认为 base 之前,它用合并的两边替换了那个基础代码。通常会发生冲突,因为差异选择了错误的基础 - 例如一些匹配的括号和/或空行 - 而不是因为必须存在实际冲突。

      Even if git merge successfully auto-merges everything, that does not mean the result is correct! Of course, when it stops with a conflict, this also means that Git was not able to auto-merge everything, not that what it has auto-merged on its own is correct. I like to set merge.conflictstyle to diff3 so that I can see what Git thought the base was, before it replaced that "base" code with the two sides of the merge. Often a conflict happens because the diff chose the wrong base—such as some matching braces and/or blank lines—rather than because there had to be an actual conflict.

      使用至少在理论上,耐心差异可以用较差的基础选择来保持。我自己没有尝试过这个。 Git 2.9中新的压缩启发式是很有希望,但我也没有尝试过这个。

      Using the "patience" diff can held with poor base choice, at least in theory. I have not experimented with this myself. The new "compaction heuristic" in Git 2.9 is promising, but I have not experimented with this either.

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

      You must always inspect and/or test the results of a merge. If the merge is already committed, you can edit files, build and test, git add the corrected versions, and use git commit --amend to shove the previous (incorrect) merge commit out of the way and put in a different commit with the same parents. (The --amend part of git commit --amend is false advertising. It does not change the current commit itself, because it can not; instead, it makes a new commit with the same parent IDs as the current commit, instead of the normal method of using the current commit's ID as the new commit's parent.)

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

      You can also suppress the auto-commit of a merge with --no-commit. In practice, I have found little need for this: most merges mostly just work, and a quick eyeballing of git show -m and/or "it compiles and passes unit tests" catches problems. However, during a conflicted or --no-commit merge, a simple git diff will give you a combined diff (the same sort you get with git show without -m, after you commit the merge), which can be helpful, or may be more confusing. You can run more-specific git diff commands and/or inspect the three (base, local, other) slot entries, as Gregg noted in a comment.

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

      Besides using diff3 as your merge.conflictstyle, you can see the diffs that git merge will see. All you need to do is run two git diff commands—the same two that git merge will run.

      要做到这些,你必须找到 - 或者至少告诉 git diff 来找到合并基地的。您可以使用 git merge-base ,它可以找到(或所有)合并基础并打印出来:

      To do these, you must find—or at least, tell git diff to find—the merge base. You can use git merge-base, which literally finds the (or all) merge base(s) and prints them out:

      $ git merge-base --all HEAD foo
      4fb3b9e0570d2fb875a24a037e39bdb2df6c1114
      

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

      This says that between the current branch and branch foo, the merge base is commit 4fb3b9e... (and there is only one such merge base). I can then run git diff 4fb3b9e HEAD and git diff 4fb3b9e foo. But there is an easier way, as long as I can assume that there is only the one merge base:

      $ git diff foo...HEAD   # note: three dots
      

      这告诉 git diff (和 git diff )查找之间的合并基础foo HEAD ,然后比较该提交 - 该合并库 - 提交 HEAD 。并且:

      This tells git diff (and only git diff) to find the merge base between foo and HEAD, and then compare that commit—that merge base—to commit HEAD. And:

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

      做同样的事情,找到 HEAD foo之间的合并基础 - merge base是可交换的,因此这些应该与其他方式相同,例如7 + 2和2 + 7都是9 - 但这次将合并基础与commit <$ c区分开来$ c> foo 。 1

      does the same thing, find the merge base between HEAD and foo—"merge base" is commutative so these should be the same as the other way around, like 7+2 and 2+7 are both 9—but this time diff the merge base against commit foo.1

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

      (For other commands—things that are not git diff—the three-dot syntax produces a symmetric difference: the set of all commits that are on either branch, but not on both branches. For branches with a single merge base commit, this is "every commit after the merge base, on each branch": in other words, the union of the two branches, excluding the merge base itself and any earlier commits. For branches with multiple merge bases, this subtracts away all the merge bases. For git diff we just assume there's only the one merge base, and instead of subtracting it and its ancestors away, we use it as the left or "before" side of the diff.)

      1 在Git中,分支名称标识一个特定的提交,即分支的 tip 。实际上,这就是分支实际工作的方式:分支名称命名一个特定的提交,并且为了向branch- 分支添加另一个提交,这意味着提交链 -Git进行一个新的提交,其父级是当前的分支提示,然后将分支名称指向新的提交。 branch一词可以指分支名称或整个提交链;我们应该通过上下文找出哪一个。

      1In Git, a branch name identifies one particular commit, namely the tip of the branch. In fact, this is how branches actually work: a branch name names a specific commit, and in order to add another commit to the branch—branch here meaning the chain of commits—Git makes a new commit whose parent is the current branch-tip, then points the branch name at the new commit. The word "branch" can refer to either the branch name, or the entire chain of commits; we are supposed to figure out which one by context.

      在任何时候,我们都可以命名一个特定的提交,并将其视为一个分支,通过采取该提交及其所有祖先:其父级,父级的父级,依此类推。当我们点击合并提交 - 一个包含两个或更多父母的提交 - 在此过程中,我们将所有父提交及其父母的父母等等。

      At any time, we can name one specific commit, and treat that as a branch, by taking that commit and all its ancestors: its parent, its parent's parent, and so on. When we hit a merge commit—a commit with two or more parents—in this process, we take all the parent commits, and their parents' parents, and so on.

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

      2This algorithm is actually selectable. The default myers is based on an algorithm by Eugene Myers, but Git has a few other options.

      这篇关于解决冲突融合算法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆