Git合并多个文件的冲突

时间:2016-06-09 03:02:22

标签: git git-branch git-merge git-merge-conflict

如何告诉git从一个特定分支中获取所有冲突的文件?

将branch1合并到branch2时出现合并冲突。我可以说从branch1获取所有冲突的文件,而不是添加每个文件。

2 个答案:

答案 0 :(得分:3)

你可以。

你可能不应该,但你可以。 (最多,您可能使用-X ours-X theirs。)

下面,在完成所有设置之后,确保阅读此答案的任何人都能理解正在进行的操作,这是简单的 - 尽管有时简单的方式来选择"我们的& #34;或者"他们的"每个冲突的文件。

设置,或者,我们如何在第一时间陷入混乱

$ git status              # make sure everything is clean
[git status is here]

$ git checkout branch2    # get onto branch2
[git's message about checking out the branch is here]

$ git merge branch1       # merge from branch1 into branch2, as in your text
... conflict messages ...

您现在有一些成功的合并和一些冲突。例如,公共合并库可能包含文件a.ab.bc.c,依此类推,直到z.z,因此,与通用合并相比基本提交,前三个(a.ab.bc.c)在branch1和后三个(b.bc.c中进行了修改,d.d)已在branch2中修改。显然,对a.a的更改没有冲突,对d.d的更改没有冲突,但可能b.bc.c冲突,在{{1}中进行了多次修复} branch1和branch2中b.b的一个不同修复,但两个修复重叠。 b.b发生了类似的事情,导致那里的冲突。

您现在希望通过从c.c(或{{1}获取文件版本来放弃在branch2(或branch1)中所做的所有修复 }} 分别)。您可以使用此命令:

branch1

或:

branch2

但这需要知道有问题的两个文件是git checkout branch1 -- b.b c.c git checkout branch2 -- b.b c.c

你也可以这样做:

b.b

或:

c.c

这些几乎与使用名称git checkout --ours -- b.b c.c git checkout --theirs -- b.b c.c 的{​​{1}}命令相同,但有两个很大的区别。我们稍后会谈到这些。

合并基础

我在上面提到了 merge base ,没有定义它。知道合并基础是什么是一个好主意,这样你就知道Git在做什么,从而知道正在做什么。有一个precise definition (which is also a bit complicated),但简单来说,当我们查看两个分支上的提交链时, merge base 是最近的共同祖先提交。也就是说,如果我们绘制(部分)提交图,我们通常会看到类似这样的内容:

git checkout

两个分支有两个不同的提示提交(分支名称指向)。这些提交指向他们的父提交,指向他们的父母,依此类推。也就是说,每个branch1(表示提交图中的提交节点)都有一个指向其父提交的左箭头。 ( merge 提交有两个或更多这样的箭头;上面显示了一个简单的情况,其中没有合并。)最终这两个分支应该(通常)会合,并从那个点向左 - 向后及时 - 所有提交现在都在两个分支上。

最右边的此类提交,我将其绘制为branch2而不是 o--o--o--o <-- branch1 / ...--o--* \ o--o--o <-- branch2 ,是合并基础

Git&#34; s&#34; index&#34; (又称临时区域)

在我们进入自动部分之前,我们需要看一下Git如何定义索引或临时区域。

索引基本上是#34; Git将在下一次提交中加入#34;。也就是说,它对于将在提交中的每个文件都有一个条目。当您刚刚提交时,索引为每个(跟踪的)文件都有一个条目,该条目与您刚刚提交的跟踪文件相匹配。无论如何,每次提交都有一个完整的工作树快照 - 所有跟踪文件 - 并且该快照是从索引中创建的,索引仍然与刚才的相同。

当您o文件时,您替换为工作树中的索引版本。也就是说,您可以修改*以获得一些新内容,然后o git add内容放入索引中。现有的b.bgit add b.b等等都保持不变,但b.b将替换为工作树版本。

(如果您a.a是一个全新的文件,例如c.c,则该新文件会进入索引,现在b.b已被跟踪&#34;。如果您git add一个文件,Git在索引中放入一个特殊的&#34; white-out&#34;条目,将文件标记为&#34;将被排除在下一个提交之外&#34;即使它&#34; #39;目前仍然被跟踪。在大多数情况下,你可以忽略这些小细节,只需将索引视为&#34;然而你可以做下一次提交&#34;。

在合并期间,索引会扮演一个新角色。

自动发现冲突文件

对于每个跟踪文件,索引中实际上有四个个插槽,而不仅仅是一个。通常Git只使用插槽零(0),这是普通的,非合并冲突的文件。当Git在合并期间遇到冲突时,它会将每个文件的多个副本放入索引,插槽1,2和/或3中,并将插槽0留空。

插槽1用于文件的合并基础版本。插槽2保存本地(当前或README)分支版本,插槽3保存另一个(待合并或README)版本。在上面的示例中,我们执行了git rm,因此广告位2保存--ours --theirs版本,广告位3保存git checkout branch2branch2(因为我们b.b)。同时,插槽1保存合并基础版本,来自提交branch1

其中一些插槽可能为空:例如,如果在两个分支中添加了名为b.b的文件,因此它没有合并基础版本,则插槽1将为空;将只有slot-2和slot-3条目。

git merge branch1的{​​{1}}和*标志告诉new.new使用插槽2或插槽3.没有--ours标志来提取版本1(合并基础版本),但它 可能得到它;有关--theirs语法,请参阅the gitrevisions documentation

你当然也可以使用分支名称,但这两种方式略有不同,正如我们稍后会看到的那样。

要查找未合并的文件,我们只需找到处于此未合并状态的任何路径。一种方法是使用git status,但这有点棘手。一种更好的方法是使用所谓的&#34;管道命令&#34;,特别是git ls-filesgit checkout命令具有git checkout以显示所有索引条目及其阶段编号。我们想知道哪些文件存在于阶段1,2和/或3中的任何一个:

--

产生如下输出:

:n:path

输出格式具有阶段编号(0到3),后跟文字选项卡,后跟文件名,因此我们要排除阶段0,收集阶段1-3名称。有很多方法可以做到这一点,例如ls-files,但我们还需要拆分文件名并使其唯一(因为如果它在所有3个插槽中都会出现多次) )。我们可以使用--stage一次完成所有操作:

git ls-files --stage

(这段代码仍然存在轻微缺陷,因为它的名称包含空格的文件是错误的。修复它留作练习。)

更复杂的awk(或其他语言)脚本会检查每个路径实际上有三个版本,然后执行某些操作 - 这取决于您的目标 - 对于其他情况。

请注意,[mass snip] 100755 2af1beec5f6f330072b3739e3c8b855f988173a9 0 t/t6020-merge-df.sh 100755 213deecab1e81685589f9041216b7044243acff3 0 t/t6021-merge-criss-cross.sh 100755 05ebba7afa29977196370d178af728ec1d0d9a81 0 t/t6022-merge-rename.sh [more mass snip] 有一个标记grep -v $'0\t'awk,以显示未合并的文件。输出仍然是git ls-files --stage | awk '$3 != 0 { path[$4]++; } END { for (i in path) print i }' 格式(实际上git ls-files强制-u,就像the documentation所说的那样)。我们可以使用它而不是检查stage-number。

我们有一个未合并文件列表后,我们只需在其上运行--unmerged--stage

--unmerged

在此之后,我们仍然需要将它们标记为已解决,如果我们使用--stage

使用分支名称而非git checkout --ours --

的问题不那么微妙

我们上面说git checkout --theirs --。然后,我们必须git ls-files --unmerged | \ awk '{ path[$4]++; } END { for (i in path) print i }' | xargs git checkout --ours -- 标记已解决的文件,即通过将--ours的工作树版本放入stage-slot-zero来清除阶段1-3。

如果我们--ours,我们必须git checkout --ours -- b.b

你可能想知道为什么。我当然这样做。我可以告诉你,这是因为git add b.bb.b的版本从git checkout branch2 -- b.b指向的提交复制到stage-slot-zero,清除插槽1-3,然后从进入工作树;但git add b.bgit checkout branch2 -- b.b的版本从stage-slot-3(b.b)复制到工作树中。

也就是说,branch2有时将复制到索引然后将其复制到工作树中,有时只需将索引槽中的副本复制到工作中-树。我无法解释的是为什么 git checkout --ours -- b.b有时必须首先写入索引,有时不会写入,而不是&#34;编写代码后编写代码更容易一堆其他代码。&#34;换句话说,这里没有明显的主设计原则。你可以创建一个:&#34;如果要从树中读取b.b,它应该写入索引,写入slot-zero并清除1-3;但如果它只能从索引中读取,则不应写入索引。&#34;但这似乎很随意。

使用分支名称而不是--ours

的更微妙的问题

当Git准备合并并设置所有这些索引槽时,它也会重命名检测。假设我们git checkoutgit checkoutcheckout代替文件--oursa.a。假设z.z a.a成为sillyname.bc.c成为sillyname

Git通常会自动检测此重命名。然后,它将使用在该特定提交中命名的文件的内容填充插槽1,2和3。由于我们位于b.b且该文件现在名为branch1,因此索引名称将为bettername.b。插槽1中的条目将是提交branch2&#39; s branch2的条目。插槽2中的条目(我们的版本)将适用于我们的bettername.b。插槽3中的条目是另一个(bettername.b)版本,将用于*

同样,它们都是我们的新名称,因此我们需要sillyname.bbettername.b。如果我们尝试--theirs,Git会抱怨它在b.b中找不到git checkout --ours -- bettername.b。实际上,它不能:它在那里被称为git checkout --theirs -- bettername.b

有时您可能不关心,或者可能想要发现这种重命名检测。在这种情况下,您可能希望使用分支名称,而不是git checkout branch1 -- bettername.bbettername.b参数。你只需要意识到差异。

为什么branch1b.b不同(可能是您想要的)

我在上面提到了--ours--theirs有多个修复程序的情况,-X ours中只有一个修复程序(与-X theirs中的修补程序冲突)。< / p>

冲突可能只发生在一个地区。也就是说,假设有两个较小的拼写错误与更重要的修复无关。如果我们使用b.bbranch1,我们将失去两个拼写修复程序,即使我们采用了高级代码修复程序。

如果我们告诉 merge 命令使用branch1,那说明在发生冲突的情况下,请使用我们的更改,但是如果对{{1}进行了一些更改我们没有做任何事情,继续进行他们的更改。在这种情况下,我们将提取他们的两个拼写修复。

因此,branch2与以后的--ours 非常不同

恢复合并冲突

如果你想重新合并冲突 - 如果你解决了一个文件,但又有了第二个想法 - b.b还有另一个标志-X ours,那就是这样做:

b.b

这&#34;重新合并&#34;该文件,将冲突标记放回。(它实际上使用了一组特殊的&#34; undo&#34;索引条目,因为合并基础版本可能是虚拟合并基础而不是单个提交。它只能在提交合并之前使用;之后,您必须重新执行整个合并。)

答案 1 :(得分:0)

-s/--strategy git merge命令选项指定的合并策略可以告诉git应该使用哪个分支。您还可以看到here