根据您的要求,答案很简单:
git checkout branchA
git merge -X theirs branchB
在你盲目地应用它之前,请确保你知道你在做什么!您还说您使用了-s 和-x,但git merge 没有-x 选项。 (也许您的意思是-X。显示您使用的实际命令,以及至少一些结果,可能会有所帮助。)如果您遇到了我所说的高级冲突 em>,-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 brachA; git merge branchB 时,你有你的 Git find 为你提交 *——这是你们俩开始的地方,无论它在历史上可能有多远——然后你就有了你的 Git 运行两个 diff 命令:
git diff --find-renames <hash-of-*> <hash-of-A>
git diff --find-renames <hash-of-*> <hash-of-B>
如果可以的话,Git 现在将尝试合并两组更改,将它们应用于提交 * 中的任何内容。如果 Git 可以自己完成所有这些,它会进行最终的合并提交——一个几乎普通的提交,除了它有两个父指针,指向提交 A 和提交 @ 987654343@。这会将名称 branchA 更改为也指向新的提交,给出:
o--o--A
/ \
...--o--* M <-- branchA (HEAD)
\ /
o--o--B <-- branchB
与新提交 M 关联的快照 是 Git 将您的更改(* 与 A)与它们的更改(* 与 B)组合的结果到*。 (在此过程中,任何中间提交都将被完全忽略。Git 只对申请 * 两个变更集的联合感兴趣。)
当*-vs-A 中某些文件的某些更改与*-vs-B 中某些文件的某些更改发生冲突时,Git通常会因合并冲突而停止。大写的 -X 选项让您告诉 Git,如果发生冲突,它应该更喜欢“我们的”(*-vs-A)或“他们的”(@ 987654360@-vs-B) 更改。但是,如果没有冲突,Git 将继续进行两个更改。这适用于在单个文件中。一个例子可能会有所帮助。
假设*-vs-A 变更集包括文件README.txt 的这些说明:
假设*-vs-B 变更集包括文件README.txt 的这些说明:
- 将第 20 行的“the yellow cow”改为“a large yellow cow”。
因为两个变更集都尝试更改第 20 行,所以您会在这里遇到冲突。因为只有 one 更改集更改了第 3 行,所以该更改不会冲突,因此 Git 将应用它。使用-X theirs,Git 将更喜欢他们在第 20 行的更改,即使将第 3 行更改为“棕色”,它现在也将包含“大黄牛”字样。
鉴于-X theirs,Git 认为这是正确的解决方案: 将您的 无冲突更改带到第 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 不知道要使用哪个 name,-X theirs 也没有告诉它:使用他们的名字。 Git 只是因冲突而停止。
如果您都添加了同名的新文件,或者如果您修改了某个文件并且他们将其删除,或者反之亦然,则会发生类似的高级冲突。前者是添加/添加冲突,后者是修改/删除冲突。还有几个这样的冲突。所有这些都意味着 Git 要么不知道要保留哪些文件,要么要使用哪个名称。
在所有这些情况下,Git 都会因合并冲突而停止,就像您没有使用 -X 时一样。当 Git 确实以这种方式停止时,它会将 所有 文件留在索引/暂存区域中,使用 阶段编号 来识别哪个是哪个。例如,如果 README.txt 中存在冲突,则索引中现在有 三个副本 README.txt:
-
:1:README.txt 是来自基本提交的版本,提交 *。
-
:2:README.txt 是来自HEAD 提交的版本,提交A。
-
:3:README.txt 是他们提交的版本,提交 B。
对于添加/添加冲突,版本 1 不存在(文件不在基础中)。对于修改/删除冲突,版本 2 或 3 都不存在(文件已在 A 或 B 中删除)。
例如,您可以在这些名称上使用 git show:git show :1:README.txt 以查看基本版本。
工作树有 一个README.txt 的副本,这通常是 Git 在组合三个输入时的最大努力,可能带有冲突标记。
您现在的任务是为文件创建阶段零条目:0:README.txt,清除阶段 1-2-3 条目。最简单的方法通常是编辑工作树文件并运行git add README.txt。 git 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
这些将 commit 文件从 commit A 或 commit B 复制到第 0 阶段的索引中,然后从索引复制到工作树.复制到阶段 0 使文件准备好提交,因为它已经清除了插槽 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 --
(只要文件名中没有空格)。