【问题标题】:Easy auto merge goes wrong轻松自动合并出错
【发布时间】:2019-11-29 05:35:12
【问题描述】:

我合并了两个分支:

  • 分支master 将行添加到文件config_dev.yml
  • 分支report 没有触及它(因此该文件没有冲突)。

我将report合并到master中,结果是:行消失了。

           1  2  3  4  5  6  7  8  9  10
master: ---O--O--O-----O--O-----O--O--O----
report: -------\----O--------O-------/ 
    changes on master__^              ^__merge that silently removes changes

为什么?我可以信任git merge吗?


可能相关的其他信息:

  • 还有一个第三个分支(没有触及该文件)从master 的提交 3 开始,并且在提交时有点“合并”(实际上是一个空白合并,“仅我的”)到 report 7

【问题讨论】:

  • 好吧,我想我不能,因为我不知道问题的原因。
  • 我知道这不是你的问题,但这个question 可能对你有帮助。
  • 我们也不知道原因。我们甚至看不到您的存储库。所以……耸耸肩
  • 由于 merge 只查看被合并的两个最终提交状态及其合并基础,我发现这三个提交之间的三向差异通常可以回答此类问题。在这里,您要区分 9,10 之前的报告负责人,以及它们的合并基础,2。查看 git diff 2 10git diff 2 <report before merge>。或者,由于 Git 不直接提供三向差异,这些可能会有所帮助:stackoverflow.com/a/55831128/3216427 用于单文件三向差异,或 stackoverflow.com/a/56917121/3216427 用于完整的三向差异。
  • 您自己运行了合并吗?如果再次运行它会重现吗?如果您使用 git filter-branch 从历史记录中删除所有其他文件,它会重现吗?

标签: git git-merge


【解决方案1】:

这不是真正的答案(应该是评论),但它需要格式化并且不适合评论空间。相反,这是关于如何找到答案的说明,或者至少提出将导致答案的正确输入。

当你运行时:

git checkout master
git merge report

Git 首先找到一个 merge base 提交。很少——可能不是这里的情况——Git 发现多个合并基础提交,但我们会确保不是这种情况。

假设这个合并会出错,但是——重要的是——还没有完成。如果它已经完成,我们需要一个技巧,或者一个尚未完成的单独存储库:任何一个都足够了。诀窍是创建一个 new 分支,而不是命名为 master,它指向 master 在合并之前会指向的提交:

git checkout -b test-the-merge <hash-ID>

(我们可以稍后丢弃这个分支。或者,我们甚至可以根本不创建它,使用另一种技巧,但为简单起见,使用测试分支更容易。)

现在我们处于尚未完成合并的状态,我们首先运行:

git merge-base --all HEAD report

理想情况下,这会产生 one 哈希 ID。如果它产生多个哈希ID,我们会遇到有多个合并基础的罕见情况,我们需要一个我将省略的过程,因为它很长而且很无聊。 :-)

现在我们知道了作为合并基础的一个哈希 ID,下面是我们如何看到 Git 将看到什么,当 Git 进行合并时:

git diff --find-renames <hash> HEAD     # what does Git think *we* changed?
git diff --find-renames <hash> report   # what does Git think *they* changed?

您可能希望将这两个git diffs 发送到文件中,以便您可以在闲暇时和/或同时阅读它们。如果有很多正确合并的差异并且您只想专注于未正确合并的特定文件,您还可以将输出限制为特定文件。

请注意,git diff 发现的不一定是某些所做的更改。 git diff 发现的是一组最小的指令,产生相同的效果。这通常足够好。例如,假设在左侧提交(合并基础,由哈希 ID 指定)和右侧提交之间,有人删除了 second 行,第一个单词 the,冗余段落:

Paris in
the
the
spring

而 Git 选择生成指令:删除第二个 the(第三行)。

这有关系吗?可能不会——但有时会。如果左右两边分别读:

some vaguely C like code {
    with redundant stuff
}
more vaguely C like code {
    with redundant stuff
}

和:

some vaguely C like code {
    with redundant stuff
}
still vaguely C like code {
    with redundant stuff
}
more vaguely C like code {
    with redundant stuff
}

并且 Git 错误地在右大括号上同步并产生语法上无效的差异?好吧,即使通常仍然有效,但有时会产生不适当的冲突。在极少数情况下(尽管确实会发生,但很难说明),您可能会错过一个应该发生的冲突,因为 Git 提供了最小的编辑这在语法上是正确的,但在语义上是错误的。

无论如何,在生成两个差异列表之后,Git 现在所做的就是提出合并的源文件集。为此,Git 从所有文件的 merge base 版本开始。那么:

  • 对于您、他们或双方更改的每个文件...

    • 您是否更改了文件?他们没有更改文件吗?如果是这样,请获取您的文件。

    • 他们是否更改了文件,而您没有更改?如果是这样,请获取他们的文件。

    • 否则,你们都更改了文件。尝试合并更改,逐行查看差异。无论您在哪里添加了一些线条,都可以使用您添加的线条。无论他们在哪里添加了一些,都使用他们添加的行。无论您或他们在何处删除了某些行,请进行删除。如果你们都对完全相同的行进行了完全相同的更改,请复制一份更改。如果您对同一行或相邻(相互接触)或到达文件末尾的行进行了不同的更改,请声明合并冲突。

  • 对于您或他们完全删除、创建为全新或重命名的文件,请执行适当的操作。 (适当的确切含义很复杂,在这里似乎不是你的情况,所以我不会深入探讨。)

尽其所能将所有内容组合在一起,Git 现在:

  • 因合并冲突而停止,或
  • 因为你告诉它停止(例如--no-commit),或者
  • 进行合并提交,因为一切似乎都很顺利。

如果 Git 进行了合并提交——或者如果它停止并允许你提交并且你确实成功了——合并提交将你当前分支的旧提示提交作为其第一个父项——新提示提交是合并提交本身——并且合并提交将另一个分支的提示提交作为其第二个父级。也就是说,在mastertest-the-merge(无论我们在哪个分支)上合并之后,我们有:

git rev-parse <branch>^1   => hash ID of the previous branch tip
git rev-parse <branch>^2   => same hash ID as git rev-parse report

您可以查看两个输入,来自三个提交的两个差异 - 合并基础,HEAD 和他们的 /tip-of-report - 看看 Git 看到了什么,这将解释为什么 Git进行了它所做的合并。

无论如何,这就是 git merge 所做的事情: 它找到一个合并基础,创建两个差异,然后合并差异并将合并后的差异应用于合并基础以提出合并结果。 (这当然忽略了所有特殊情况,例如由于各种原因未实际合并:-s ours,或快进,或不相关的历史,或合并冲突等。)差异本质上是面向行的,不能重构某人真正所做的事情:他们只产生一组指令,这些指令会产生相同的最终文本。结合这些指令往往适用于大多数编程语言,但绝对不是完美的。

如果该过程适用于您的文件,您可以信任它。如果该过程处理您的文件,则您无法信任它,并且您必须仔细检查 - 并在需要时更正 - 每次合并。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-07
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    相关资源
    最近更新 更多