我看一下看起来搞砸了的合并标记。为了给你这种情况,我们可以:
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版本的知识?这不是我第一次经历这样的事情......
答案 0 :(得分:8)
你是正确的:Git对语言一无所知,它的内置合并算法严格基于时间线比较。 您不必使用此内置合并算法,但大多数人这样做是因为(a)它主要起作用,(b)没有那么多替代方案。
请注意,这取决于您的合并策略(-s
参数);以下文字适用于默认的recursive
策略。 resolve
策略与recursive
非常相似; octopus
策略适用于不止两次提交;并且ours
策略完全不同(并且与-X ours
完全不同)。您还可以使用.gitattributes
和&#34;合并驱动程序&#34;为特定文件选择其他策略或算法。并且,这些都不适用于Git已经决定相信的文件&#34;二进制&#34;:对于这些文件,它甚至不会尝试合并。 (我不会在这里讨论任何内容,只是默认的recursive
策略如何处理文件。)
git merge
的工作原理(使用默认-s recursive
时)HEAD
),以及一些&#34;其他&#34 ;一个(也称为#34;他们的#34;和#34;远程&#34;)git diff base local
和git 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 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
做同样的事情,找到HEAD
和HEAD
之间的合并基础 - &#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的独立更改,以及冲突部分的冲突标记。