0

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

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

4

2 回答 2

4

你可以。

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

在完成所有设置以确保阅读此答案的任何人都了解发生了什么之后,下面是为每个冲突选择“我们的”或“他们的”文件的简单(尽管有时过于简单)的方法。

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

$ 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.a, b.b, c.c, 等到z.z, 并且由于——即,与共同合并基础提交相比——前三个 ( a.a, b.b, 和c.c) 被修改,后branch1三个 ( b.b, c.c, 和d.d) 在 中进行了修改branch2。显然,对 的更改a.a没有冲突,对 的更改d.d没有冲突,但可能会b.b发生冲突,在分支 2 中进行c.c了几个修复和一个不同的修复,但是这两个修复重叠。发生了类似的事情,导致那里发生冲突。branch1b.bb.bc.c

您现在想要通过从(或分别)获取文件版本来丢弃branch2在(或)中所做的所有修复。你可以使用这个命令:branch1branch1branch2

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

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

合并基地

我在上面提到了合并基础,没有定义它。了解合并基础是一个好主意,这样您就知道 Git 在做什么,从而知道在做什么。有一个精确的定义(也有点复杂),但简单地说,当我们查看两个分支上的提交链时,合并基础是最近的共同祖先提交。也就是说,如果我们绘制(部分)提交图,我们通常会看到如下内容:

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

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

*我画的最右边的提交o合并基础

Git 的“索引”(又名暂存区)

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

该索引本质上是“Git 将在您进行的下一次提交中放入的内容”。也就是说,对于提交中的每个文件,它都有一个条目。当您刚刚提交时,索引对每个(跟踪的)文件都有一个条目,并且该条目与您刚刚提交的跟踪文件匹配。每个提交都有你的工作树的完整快照——无论如何,所有跟踪文件的快照——并且该快照是根据索引制作的,并且索引仍然与刚才相同。

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

(如果您git add是一个全新的文件,例如README,则该新文件进入索引,现在README被“跟踪”。如果您git rm是一个文件,Git 会在索引中放置一个特殊的“white-out”条目,将文件标记为“被排除在下一次提交之外”,即使它目前仍在跟踪。在大多数情况下,您可以忽略这些小细节,只是将索引视为“您可以进行的下一次提交”。)

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

自动发现冲突文件

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

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

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

和标志告诉使用槽 2 或槽 3。没有用于提取版本 1(合并基础版本)的标志--ours,但可以获得它有关语法,请参阅gitrevisions 文档--theirsgit checkoutgit checkout--:n:path

当然,您也可以使用分支名称,但这有两种微妙的不同,我们稍后会看到。

要查找未合并的文件,我们只需要找到处于此未合并状态的任何路径。一种方法是使用git status,但这有点棘手。一种更好的方法是使用所谓的“管道命令”,尤其是git ls-files. 该ls-files命令必须--stage显示所有索引条目及其阶段编号。我们想知道在任何阶段 1、2 和/或 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 '$3 != 0 { path[$4]++; } END { for (i in path) print i }'

(这段代码仍然有一些缺陷,因为它对名称包含空格的文件做了错误的事情。修复它留作练习。)

更复杂的 awk(或其他语言)脚本会检查每个路径实际上是否存在三个版本,并针对其他情况做一些事情——确切地说是什么,取决于你的目标。

请注意,它git ls-files有一个标志,-u--unmerged显示未合并的文件。输出仍然是--stage格式(实际上是--unmergedforce --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.

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

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

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

你可能想知道为什么。我当然愿意。我可以告诉你,这是因为git checkout branch2 -- b.b将版本b.b从提交到哪个branch2点复制到阶段槽零,清除槽 1-3,然后从那里复制到工作树;但将stage-slot-3 ( )git checkout --ours -- b.b的版本复制到工作树中。b.b--ours

也就是说,git checkout有时复制索引中,然后复制到工作树,有时只是从索引槽复制到工作树。我无法解释的是为什么 git checkout有时必须先写入索引,有时则不需要,除了“在编写了一堆其他代码之后以这种方式编写代码更容易”。换句话说,这里没有明显的主要设计原则在起作用。您可以编造一个:“如果checkout要从树中读取,则应写入索引,该索引写入零槽并清除1-3;但如果只能从索引中读取,则不应写入索引。” 但这似乎很随意。

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

当 Git 准备合并并设置所有这些索引槽时,它也会重命名 detection。假设我们有,和, 而不是a.a通过的文件。假设变成in但变成in 。z.za.asillyname.bc.csillynameb.bbranch1bettername.bbranch2

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

同样,它们都在我们的新名称下,所以我们需要git checkout --ours -- bettername.bor git checkout --theirs -- bettername.b。如果我们尝试这样做git checkout branch1 -- bettername.b,Git 会抱怨它无法bettername.bbranch1. 事实上,它不能:它在b.b那里被调用。

有时您可能不在乎,或者可能想发现这种重命名检测。在这种情况下,您可能希望使用分支名称,而不是--oursor--theirs参数。你只需要意识到差异。

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

b.b我在上面提到了对in有多个修复的情况branch1,并且在 中只有一个修复(与 中的修复冲突branch1branch2

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

如果我们告诉合并命令使用-X ours,这表示在发生冲突的情况下,使用我们的更改,但是如果对b.b我们没有做任何事情的地方进行了一些更改,请继续进行更改。在这种情况下,我们将选择他们的两个拼写修复。

因此,git merge -X ours与后来git checkout --ours.

恢复合并冲突

如果你想把合并冲突放回去——如果你解决了一个文件,但后来又重新考虑了——<code>git checkout 有另一个标志,-m,它会这样做:

git checkout -m b.b

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

于 2016-06-09T04:11:09.663 回答
0

-s/--strategy命令选项指定的合并策略git merge可以告诉git应该使用哪个分支。你也可以看这里

于 2016-06-09T03:43:51.867 回答