强制 git 合并没有像我认为的那样工作

Forcing git merge not working as I thought it should

我的提交树看起来像

---A---B---C   master
    \
     D-----E     dev

我想将 E 合并到 master 上,这样领先的 master 提交看起来 完全像 E:

---A---B--C--F   master
    \       /
     D-----E     dev

所以F应该和E完全一样

我所做的是:

> git checkout master
> git merge -X theirs E

F 生成时没有错误信息,但是E 和F 有几处不同。它们并不相同,就像我需要的那样。

我知道我可以手动合并和修复差异。但是,1) 我真的需要 E 来完全覆盖 C 和 2) 我不能把它搞砸,所以一些自动的方法比手工做更可取。

我做错了什么?我需要做哪些不同的事情?

更新:

> git merge -X theirs dev

也无济于事。同样的结果。

-X theirs option of git merge tells it how to resolve the merge conflicts when it uses the default strategy (-s recursive)。可以合并干净的更改不受影响。合并后的文件将包含在两个分支上操作的更改。

ours strategy 可用于创建忽略合并分支上更改的合并提交。为了使 F 看起来与 E 相同,您必须将 master 合并到 dev 中,但这不是您要求的。

针对您的请求(我没有测试过)的可能解决方案可能是这样的:

  • 像现在一样进行合并,但将 --no-commit 选项添加到命令行;这样,Git 准备合并提交,但允许您查看(并可能更改)它;
  • (强制)检查工作树文件以匹配 E 提交上的回购状态;
  • 将所有内容添加到索引并提交。

命令是:

git checkout master
git merge -X theirs --no-commit dev
git checkout --force E -- .
git add .
git commit

首先,请注意:您想要的结果是 "evil merge" 的示例,它可能会导致某些问题。如果它是你需要的,它就是你需要的;但请注意,结果与默认值不同的合并(尤其是当默认值不冲突时)在某些 rebase 操作(可能还有其他事情)期间可能会被误解。

也就是说:有些混乱,因为默认的合并策略采用 -X 参数 "ours" 或 "theirs" 来告诉它如何解决冲突;但这并没有改变合并操作从双方进行不冲突的更改。更令人困惑的是 还有一个名为 "ours" 的合并策略,它完全忽略了另一个分支的更改 - 但没有相应的 "theirs" 策略。

所以你可以做一些事情。一种选择是

git checkout dev
git merge -s ours master
git checkout master
git merge dev

(您希望最后一次合并是快进的;默认情况下是这样,但是如果您的设置更改了默认设置,您可能需要一个 --ff 选项。)

这与将 dev 合并到 master 并不完全相同,因为父级将被颠倒;但如果这对您来说无关紧要,那么这可能是最简单的方法。

另一种选择是手动编辑合并。

git checkout master
git merge --no-commit dev

现在您需要使您的工作树和索引看起来像提交 E。我会想

git checkout dev -- .
git clean

当然你可以使用git diff来验证。

这种方法在合并时保持父 "as expected" 的顺序 - 但不要忘记,它仍然是一个邪恶的合并。

TL;DR: 你需要 -s theirs 但不存在

虽然有几个替代品。查看此问题的其他答案,在 Whosebug 中搜索 "git merge strategy theirs",and/or 仔细阅读 Is there a "theirs" version of "git merge -s ours"? (因为最初的提问者实际上是在寻找 -X theirs,我在下面描述)。或者使用以下相当神奇的 sh/bash 脚本代码:

git merge --ff-only $(git commit-tree -p HEAD -p dev dev^{tree} -F /tmp/commitmsg)

将您想要的提交消息放入文件后 /tmp/commitmsg确定这真的是你想做的,因为它几乎从来都不是。

描述

-X(扩展参数)ourstheirs并不意味着使用我们的版本使用他们的版本版本。他们的意思是,相反,当存在冲突时,使用我们的更改使用他们的更改

这是什么意思

在您的示例中,master 的提示是提交 Cdev 的提示是提交 E。所以如果当前分支是master,那么当前commit就是C。此时将名称 dev 或提交 E 的哈希 ID 传递给 git merge 都无关紧要:select 提交 E 作为提交合并。

在任何情况下,提交 A 是合并基础,因为同时从 CE 向后追踪到它们相遇的第一个点,到达提交 A.

因此,git merge 将:

  • diff commit A 与 commit C:这是 当前分支中发生的变化,即 ours.
  • 差异提交 A 与提交 E:这是 theirs.
  • 中发生的变化
  • 尝试合并这两个差异。

假设在所有三个提交中,其中一个文件名为 animals.txt。在提交 A 中,我们找到一些关于猫的行和一些关于大象的行。还有更多的台词(关于土豚、蝙蝠、狗、雀、沙鼠、仓鼠、鬣蜥、长耳大野兔等等——字母表中的每个字母都有一些内容!)但我们现在将专注于猫和大象……还有共享的 Shrews。

A 相比,在提交 C 中,有关于猫的新声明或不同声明。所以从 AC 的差异显示了 Cats (C) 的这种变化。鼩鼱也有改动:

$ git diff <hash-of-A> <hash-of-C>
...
@@ ... @@
 blah blah
-something about Cats
+something different about Cats
 blah blah
@@ ... @@
 etc
-the Shrew flies through the air, carefree
+the Shrew swims the ocean, carefree
 etc

A 相比,在提交 E 中,有关于大象的新的或不同的声明。所以 A-to-E 的差异显示了大象的这种变化。对 Shrews 有一个不同的、冲突的 更改——我们将省略除此处以外的所有内容:

@@ ... @@
 etc
-the Shrew flies through the air, carefree
+the Shrew eats rhinos, though it causes indigestion
 etc

当 Git 去 合并 这些变化时,它会从 A-to-C 和大象从 AE 的变化。 这些改动并不冲突所以合并它们没有问题。但是也会看到关于鼩鼱谎言的改动,而且这些改动相互冲突,无法合并

-X选项直接Git选择冲突的一方或另一方

因为这里有冲突,Git需要帮助解决合并。默认情况下,Git 只声明 "there is a conflict",将 both 更改写入工作树,并因冲突而停止,让您自己清理混乱。 (还有很多幕后工作可以帮助您解决冲突,但 none 现在很重要。)

但是,使用这些 -X 选项之一,Git 被告知选择我们的,或选择他们的,然后继续前进。但这 只会影响冲突的更改 。 Git 仍将像往常一样合并不冲突的更改:在上面的示例中,无论您选择哪个 -X,Git 都将采用 Cat 和 Elephant 更改,并且 -X 将简单地选择要选择的 Shrew 更改。

合并策略不同

有一个 Git 合并 策略 称为 ours,调用为 git merge -s ours,这与 -X ours 选项。这个策略告诉 Git 毕竟不要理会合并基础,只是为了保留来自 "ours" 提交的源代码树。 Git 将进行合并提交,在不做任何工作以产生适当的合并之后。效果是 "kill off" 另一个分支,同时保留其提交;这允许你删除其他分支名称,保留提交以防你想在某个时候查看它们,同时声明你打算永远不再使用它们(但显然不太确定:-))。

如果 git merge -s theirs 存在,它会做同样的事情,除了它会 "kill off" current 分支支持新分支。可能有时候你想要这个,当它们出现时,如果 Git 有 git merge -s theirs 可能会很好,但无论出于什么原因,它只有 git merge -s ours.

替代品有效,git commit-tree 的技巧允许您使用附加到任何现有提交的任意现有源树将任意多个提交捆绑在一起——因此它涵盖了 -s ours(使用 HEAD^{tree}) 和 -s theirs。生成的提交对象没有名称,但是是 HEAD 提交的直接后代,因此 git merge --ff-only 会将其添加到当前分支。