合并两个分支,如何接受一个分支中的所有冲突?

17

我正在合并两个分支,假设为 branchA 和 branchB。它们有大约 100 个文件存在冲突。

branchB 中包含了所有已批准的工作内容,并且完全符合我的需求。我不想强制推送 branchB 或做其他改变。

是否有一种方法可以合并这两个分支,并只说对于任何冲突都接受 branchB 上的内容,这样我就不必打开每个文件或者使用 "theirs" 或 "ours",因为这非常麻烦。

我尝试从 branchB 进行递归合并,使用 -s 和 -x 选项拉取 branchA,但似乎没有按预期工作。

谢谢


你是否处于冲突状态并想要接受所有theirs的更改? - A l w a y s S u n n y
你给 -x-s 提供了什么?我认为 git merge other_branch -s theirs 应该会做你想要的事情。 - Hawkins
1个回答

22

根据您的问题,答案是简单的序列:

git checkout branchA
git merge -X theirs branchB

在你盲目应用之前,请确保你知道自己在做什么!你还说你使用了-s-x,但是git merge没有-x选项。(也许你想说的是-X。展示你实际使用的命令和至少一些结果可能会有所帮助。)如果你遇到我所谓的高级冲突,-X选项无法帮助你,你需要进行一些手动清理,但你可以自动化部分或全部操作。
合并要点
记住,git merge所做的是合并更改。为此,它需要找到一个共同的起点。假设你是Alice或Adam,正在branchA上工作,或者是Bob或Barbara,在branchB上工作。用户“A”从某个提交*开始,并进行了一些提交o,最终以提交A结束:
          o--o--A   <-- branchA
         /
...--o--*

与此同时,用户"B"从同样标记有*的精确相同的提交开始,并进行了一些提交:

...--o--*
         \
          o--o--B   <-- branchB

现在,您的存储库中运行 git fetch 之后拥有的是完整的提交集:

          o--o--A   <-- branchA
         /
...--o--*
         \
          o--o--B   <-- branchB

当你运行git checkout branchA; git merge branchB时,Git会为您查找提交记录* -- 这是您所共同起始的提交记录,无论它在历史中有多远。然后,Git会运行两个diff命令:
git diff --find-renames <hash-of-*> <hash-of-A>
git diff --find-renames <hash-of-*> <hash-of-B>

如果可能,Git将尝试将这两组更改合并,并将它们应用于提交*中的任何内容。如果Git能够自行完成所有这些操作,则会生成一个最终的合并提交——几乎是普通提交,但它有两个父指针,分别指向提交A和提交B。这将更改名称branchA,使其也指向新提交,即:

          o--o--A
         /       \
...--o--*         M   <-- branchA (HEAD)
         \       /
          o--o--B   <-- branchB

与新提交的M相关联的快照是Git将您的更改(* vs A)与他们的更改(* vs B)应用于*的结果。在此过程中,任何中间提交都将被完全忽略。Git只对应用于*的两个更改集的并集感兴趣。
*-vs-A中的一些文件的更改与*-vs-B中的一些文件的更改冲突时,Git通常会停止合并并显示冲突。大写的-X选项允许您告诉Git,在发生冲突的情况下,它应该优先考虑"我们"(*-vs-A)或"他们"(*-vs-B)的更改。但是,如果没有冲突,则Git将继续采用两个更改。这适用于单个文件内。以下是一个示例。
假设*-vs-A更改集包括对文件README.txt的以下指令:
  • 将第3行的“yellow”更改为“brown”。(实际上它看到整行都已更改,但在这里我们使用“单词”)
  • 将第20行的“the yellow cow”更改为“a brown cow”。
假设*-vs-B更改集包括对文件README.txt的以下指令:
  • 将第20行的“the yellow cow”更改为“a large yellow cow”。
由于两个更改集都试图更改第20行,所以此处会出现冲突。因为只有一个更改集更改了第3行,所以该更改不会冲突,因此Git会应用它。使用-X theirs,Git将优先考虑第20行上的他们的更改,这将导致该行现在具有“the large yellow cow”的单词,即使它已将第3行更改为“brown”。
给定-X theirsGit认为以下是正确的解决方案:将您未发生冲突的第3行更改与他们发生冲突的第20行更改合并。这很可能是错误的,但这就是Git将要做的。有时候最好让冲突发生,然后自己去检查正确的解决方案;但如果您有良好的测试,那么您应该捕获大多数逃离Git的问题,因此-X theirs(或-X ours)是一个有用的工具。

高级别冲突

如果您已经使用了-X theirs,但仍然存在冲突,则您所拥有的是我所说的高级别冲突。请注意,上面两个git diff命令,以查找从*A和从*B的变更集,都使用--find-renames。这意味着在*A之间,Git可能会决定您(或用户A)将README.txt重命名为README.rst。用户B也重命名了它,但名称为read-me.rst。Git不知道使用哪个名称-X theirs也没有告诉它:使用他们的名称。Git只会停止并显示冲突。

类似的高级别冲突发生在以下情况下:您和他们都使用相同的名称添加了一个新文件,或者如果您修改了某个文件并且他们删除了它,反之亦然。前者是add/add冲突,而后者是modify/delete冲突。还有几种这样的冲突。所有这些情况下,Git要么不知道保留哪个文件,要么不知道使用哪个名称

在所有这些情况下,Git都将停止合并冲突,就像您没有使用-X一样。当Git以这种方式停止时,它将保留索引/暂存区中的所有文件,并使用阶段编号来标识每个文件。例如,如果在README.txt中存在冲突,则现在索引中有三份README.txt的副本:

  • :1:README.txt是基础提交(提交*)中的版本。
  • :2:README.txtHEAD提交(提交A)中的版本。
  • :3:README.txt是他们的提交(提交B)中的版本。

对于add/add冲突,版本1不存在(文件不在基础版本中)。 对于modify/delete冲突,版本2或3中的任何一个都不存在(文件已在AB中删除)。

您可以使用这些名称运行git showgit show: 1:README.txt,例如查看基础版本。

工作树中有一个README.txt的副本,这通常是Git尽力将三个输入组合起来的最好方式,可能带有冲突标记。

现在您的任务是为该文件创建一个零级条目:0:README.txt,清除1-2-3级条目。 最简单的方法通常是编辑工作树文件并运行git add README.txtgit add命令会将工作树中的内容复制到第0阶段,并删除1-3级别(如果它们存在)。

您还可以运行:

git checkout --ours README.txt

或者:

git checkout --theirs README.txt

这将从索引中复制版本2或版本3到工作树。请注意,这不会影响索引内容!它只是从索引中提取(记住,这也称为暂存区,但其中没有任何一个被暂存)到工作树。

您还可以运行:

git checkout branchA -- README.txt

或者:

git checkout branchB -- README.txt

这些命令将从提交 A 或提交 B 中复制已提交的文件到索引的零阶段,然后再从索引复制到工作树。将文件复制到零阶段使其准备好被提交,因为它已经清除了1-3号槽位的条目。

你也可以运行:

git rm README.txt

这将从所有阶段和工作树中删除文件。(仅当正确的合并结果应缺少文件README.txt时才应执行此操作。)

请注意,您可以从您编写的脚本中执行任何这些操作。您可以使用git ls-files --stage查看索引。在合并过程中,冲突的文件及其非零阶段号会出现在此处。或者,您可以使用git ls-files -u仅显示未合并的(阶段大于零)条目。

因此,如果您在使用-X theirs时遇到合并冲突后运行git ls-files -u,则会看到仍然存在问题的文件集,所有这些文件都是由高级冲突引起的。然后,您可以从中决定是否可以执行一次性操作来提取所有这些文件的版本。如果可以,请编写一个简短的命令或脚本,将所有这些文件名传递给git checkout branchB,例如:

git ls-files -u | cut -d$'\t' -f 2 | uniq | xargs git checkout branchB --

只要文件名中没有空格,就可以执行该操作。

请注意,即使在删除文件时没有冲突,这通常也无法正确地从您希望检出的分支中删除文件。 - ely
@ely:如果你指的是git ls-files -u脚本,那么是的,如果有未合并的文件遵循修改/删除高级冲突,您可能想要检查未合并的文件占用哪个暂存槽。但是,如果文件X不在branchB中(它在branchA一侧被修改,并在branchB一侧被删除),您至少会从git checkout branchB -- X看到一个错误。 - torek
我可以确认在你提到的最后一个情况中没有看到错误。执行 git checkout master 然后执行 git checkout origin/other-branch -- .,这将覆盖任何来自 other-branch 的内容到本地工作的 master 分支,但它会默默地保留任何在 master 上存在但在 other-branch 上被删除的文件,并且它们不会出现在 git status 的输出中。 - ely
@ely:我不建议使用 git checkout other-branch -- .,而是明确命名单个文件。这就是 xargs 的作用。 - torek
这通常是棘手的,例如在解决分支之间可能存在数千个不同文件冲突的情况下,你无法通过文件名来区分哪些文件应该保留。在其他情况下,你可能需要将一个分支的末端文字实际上成为另一个分支的末端,这意味着你需要完整地获取第二个分支的所有内容,并且不希望尝试合并解决方案会保留第一个分支上对计算合并解决方案有添加作用的文件方面的任何内容。 - ely
@ely:同意。我只是回答了OP的问题,我会重新表述为:“如何在保留Git自行解决的内容的同时,使用他们的版本来解决所有冲突”。这不是一个明智的做法,并不是问题的一部分。 - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接