Git 合并多个文件的冲突

Git merge conflict on multiple files

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

我在将 branch1 合并到 branch2 时发生了合并冲突。我可以说从 branch1 中取出所有冲突的文件而不是添加每个文件吗?

git merge命令的-s/--strategy选项指定的合并策略可以告诉git应该是哪个分支used.you也可以看到here

可以。

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

下面,在确保阅读此答案的任何人都了解发生了什么的所有设置之后,是选择 "our" 或 "their" 每个冲突的文件。

设置,或者说,我们最初是如何陷入困境的

$ 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 有冲突,在 [=30= 中进行了多次修复] 到 b.b 和在 branch2 中对 b.b 的一个不同修复,但这两个修复重叠。 c.c 发生了类似的事情,导致那里发生冲突。

您现在想要丢弃在branch2(或branch1)中所做的所有修复,方法是从branch1获取文件版本(或分别为 branch2)。你可以使用这个命令:

git checkout branch1 -- b.b c.c

或:

git checkout branch2 -- b.b c.c

但这需要知道有问题的两个文件是 b.bc.c

您也可以这样做:

git checkout --ours -- b.b c.c

或:

git checkout --theirs -- b.b c.c

这些 几乎 与使用名称 branch1branch2git checkout 命令相同,但有两个很大的不同。我们稍后会谈到这些。

合并基地

上面提到了merge base,没有定义。知道合并基础是什么是个好主意,这样您就知道 Git 在做什么,因此知道 you 在做什么。有一个 ,但简单地说,当我们查看两个分支上的提交链时,merge base 是最近的公共祖先提交。也就是说,如果我们绘制(部分)提交图,我们通常会看到这样的东西:

          o--o--o--o   <-- branch1
         /
...--o--*
         \
          o--o--o      <-- branch2

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

最右边的这样的提交,我画成 * 而不是 o,是 merge base.

Git 的 "index"(又名暂存区)

在我们进入自动部分之前,我们需要看看Git 是如何定义索引或暂存区的。

索引本质上是"what Git will put in the next commit you make"。也就是说,对于将要提交的每个文件,它都有一个条目。当您刚刚提交时,索引为每个(跟踪的)文件都有一个条目,并且该条目与您刚刚提交的跟踪文件相匹配。每个提交都有你的工作树的完整快照——无论如何,所有被跟踪的文件——那个快照是从索引中创建的,索引仍然和刚才一样。

当您 git add 一个文件时,您 将索引版本 替换为您的工作树中的版本。也就是说,您可以编辑 b.b 以具有一些新内容,然后 git add b.bnew b.b 内容放入索引中。现有的 a.ac.c 等都保持不变,但 b.b 被工作树版本替换。

(如果你 git add 一个全新的文件,例如 README,那个新文件进入索引,现在 README 是 "tracked"。如果你 git rm 一个文件,Git 将一个特殊的 "white-out" 条目放入索引中,以将文件标记为 "to be left out of the next commit" 即使它当前仍在跟踪。对于大多数情况,您可以忽略这些一些细节,只是将索引视为 "the next commit you can make"。)

不过,在合并期间,索引承担了新的角色。

自动发现冲突文件

对于每个跟踪的文件,索引中实际上有 四个 个位置,而不仅仅是一个。通常 Git 仅使用槽零 (0),这是正常的、不合并冲突的文件所在的位置。当 Git 在合并过程中遇到冲突时,它会将每个文件的多个副本放入索引中的插槽 1、2、and/or 3 中,并将插槽 0 留空。

插槽 1 用于文件的合并基础版本。插槽 2 保存本地(当前或 --ours)分支版本,插槽 3 保存其他(待合并或 --theirs)版本。在我们上面的例子中,我们做了 git checkout branch2,所以插槽 2 保存了 b.bbranch2 版本,插槽 3 保存了 b.bbranch1 版本(因为我们做了 git merge branch1)。同时,插槽 1 包含来自提交 *.

的合并基础版本

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

git checkout--ours--theirs 标志告诉 git checkout 使用插槽 2 或插槽 3。没有 -- 标志来提取版本1(合并基础版本),但有可能得到它; :<em>n</em>:path 语法见 the gitrevisions documentation

您当然也可以使用分支 names,但这在两个方面有细微的不同,我们稍后会看到。

要找到未合并的文件,我们只需要找到任何处于这种未合并状态的路径。一种方法是使用 git status, but this is a little tricky. A somewhat better way is to use the so-called "plumbing commands", particularly git ls-filesls-files 命令具有 --stage 以显示所有索引条目及其阶段编号。我们想知道在阶段 1、2、and/or 3:

中的任何阶段中存在哪些文件
git ls-files --stage

产生这样的输出:

[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]

输出格式特别包含阶段编号(0 到 3),后跟文字制表符和文件名,因此我们要排除阶段 0,收集阶段 1-3 的名称。有很多方法可以做到这一点,例如 grep -v $'0\t',但我们还需要拆分文件名并使其唯一(因为如果它在所有 3 个插槽中,它将出现多次)。我们可以使用 awk 一次完成所有这些:

git ls-files --stage | awk ' != 0 { path[]++; } END { for (i in path) print i }'

(此代码仍然存在轻微缺陷,因为它对名称包含 white-space 的文件做了错误的事情。修复它留作练习。)

一个更复杂的 awk(或其他语言)脚本会检查每个路径实际上有三个版本,并为其他情况做一些事情——具体是什么,取决于你的目标。

请注意 git ls-files 有一个标志,-u--unmerged,以显示 未合并的文件。输出仍然是 --stage 格式(实际上 --unmerged 强制 --stage,正如 the documentation 所说)。我们可以使用它来代替检查阶段编号。

一旦我们有了未合并文件的列表,我们只需对它们 运行 git checkout --ours --git checkout --theirs --:

git ls-files --unmerged | \
    awk '{ path[]++; } END { for (i in path) print i }' |
    xargs git checkout --ours --

在此之后我们仍然需要将它们标记为已解决,if 我们使用 --ours.

使用分支名称而不是 --ours 等的不那么微妙的问题

假设上面的内容 git checkout --ours -- b.b。然后我们必须 git add b.b 标记文件已解决,即通过将 b.b 的工作树版本放入阶段槽零来清除阶段 1-3。

如果我们git checkout branch2 -- b.b,但是,我们不必git add b.b

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

也就是说,git checkout 有时会将 复制到 索引 然后 到工作树,有时只是从索引槽复制到工作树。我无法解释的是 为什么 git checkout 有时必须先写入索引,有时不需要,除了 "it was easier to write the code that way after writing a bunch of other code." 换句话说,没有明显的主人设计原则在这里起作用。你可以编一个:"If checkout is to read from a tree, it shall write to the index, which writes slot-zero and clears 1-3; but if it can just read from the index, it shall not write to the index." 但这似乎很武断。

使用分支名称而不是 --ours 等的更微妙的问题

当 Git 准备合并并设置所有这些索引槽时,它还会进行 重命名检测 。假设我们有 a.asillyname.bc.c,而不是文件 a.az.z。假设 sillynamebranch1 中变为 b.b 但在 branch2 中变为 bettername.b

Git 通常会自动检测到此重命名。然后它将用文件的内容填充插槽 1、2 和 3,该文件 在该特定提交 中命名。因为我们在 branch2 上并且文件现在命名为 bettername.b,索引 name 将是 bettername.b。插槽 1 中的条目将用于提交 *sillyname.b。插槽 2 中的条目,即我们的版本,将用于我们的 bettername.b。插槽 3 中的条目,即另一个 (--theirs) 版本,将用于 b.b.

同样,它们都在我们的新名称下,所以我们需要 git checkout --ours -- bettername.bgit checkout --theirs -- bettername.b。如果我们尝试 git checkout branch1 -- bettername.b,Git 会抱怨在 branch1 中找不到 bettername.b。事实上,它不能:它在那里被称为 b.b

有时您可能不关心,或者可能想发现这种重命名检测。在这种情况下,您可能希望使用分支名称,而不是 --ours--theirs 参数。你只需要知道其中的区别。

为什么 -X ours-X theirs 不同(可能是您想要的)

我在上面提到了 branch1b.b 有多个修复,而 branch2 中只有一个修复(与 branch1 中的修复冲突)的情况.

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

如果我们告诉merge命令使用-X ours,那就是说在冲突的情况下,使用我们的改变,但是在一些改变的情况下到 b.b 我们没有做任何事情的地方,继续 他们的 找零。在这种情况下,我们将采用他们的两个拼写修复程序。

因此,git merge -X ours与后来的 git checkout --ours非常不同

恢复合并冲突

如果你想把合并冲突放回去——如果你解决了一个文件,但后来又改变了主意——git checkout有另一个标志,-m,这样做:

git checkout -m b.b

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