为什么这个GIT合并不会导致冲突?
我们今天在工作中发现了 GIT 的一个严重问题,我想知道这是一个错误还是设计使然,以及如何解决这个问题。
考虑以下事件序列:
- 在 Master 上创建分支“test1”
- 切换到大师:
- 编辑文件并将更改提交为提交“X”
- 切换到“test1”:
- 樱桃选择从大师提交“X”
- 恢复之前的提交
- 将 Master 合并到“test1”中
结果:即使文件已在两个分支中进行编辑,也不会报告合并冲突,更糟糕的是,即使是最近的提交,步骤 3.2 中的还原也没有保留。
这是一个巨大的问题,从以下最近的示例中可以看出:我的一位同事对不同的分支进行了类似的更改,注意到这些更改的一部分是恶意的,因此在其中一个分支上手动还原了其中的一部分. 合并分支后,他惊讶地发现他的恢复没有通过合并。
我上传了一个最小的例子到谷歌驱动器来演示这个问题。您可以将 Master 合并到 test1 中,反之亦然,以自己查看。
https://drive.google.com/drive/folders/19a-QPwOQKsn9PywUPd2DRnvUOml03nZ-?usp=sharing
如果有任何问题,我将 TortoiseGIT 2.12.0.0 与 Git for Windows 2.32.0.2 一起使用。
回答
你得到那个结果,因为那是正确的结果。
好吧,让我们修改该语句:这是 Git merge 规则的正确结果。(据我所知,根据大多数其他合并程序的规则,这也是正确的,但有些算法至少会将此标记为注意。Git 不使用这样的算法。)
如果 Git 的合并结果不是你想要的结果,你有补救措施:见下文。
当 Git 进行合并时,Git 会注意三个快照:
-
一个快照是当前快照,即,如果您运行 ,您将获得其哈希 ID 的提交
git rev-parse HEAD。如果HEAD附加到一个分支名称(通常是这样),那就是给定分支的提示提交。 -
一个快照是您在命令行中命名的快照:
git merge foo查找foo以获取提交哈希 ID。 -
第三个,在许多方面也是最重要的,快照是合并基础。(Git 将这个编号为“#1”,其中
HEAD/--ours为 #2,另一个 /--theirs为 #3,在内部,即使我们必须先定位其他两个输入才能定位此合并基础输入。)合并基础通过提交图定位。在您的情况下,它是您正在调用 commit X 的提交之前的提交。
让我们像这样绘制这些提交,将更新的提交放在右侧,单个大写字母代表每个实际提交哈希 ID:
X <-- master
/
...--G--H <-- here's where both branches start diverging
X'-X" <-- test1
在这里, commitX包含您所做的更改master;commitX'具有相同的更改,并X"撤消这些更改,以便 中的快照X"与 中的快照完全匹配H。
Git 的合并算法包括执行以下操作,假设您正在进行master(因此提交X是当前/HEAD提交)并且正在合并test1(提交X"):
-
将提交提取
H为“阶段 1”。 -
将提交提取
X为“第 2 阶段”。 -
将提交提取
X"为“阶段 3”。(请注意,我们刚刚确定 的内容X"与的内容匹配H。) -
对于索引/阶段中的每个文件,都存在于所有三个阶段插槽中:
- 比较插槽 1 中的副本与插槽 2 和 3 中的副本。
- 对于所有 3 个插槽中相同的任何文件,结果是该文件的任何版本(三个都匹配)。
- 对于插槽 1 和插槽 2 或插槽 3 中相同的任何文件,结果是不相同的版本:获取更改后的文件。
- 对于所有三个插槽都不同的任何文件,请运行差异算法以查找各个更改,并将它们组合起来。这一步可能有合并冲突,但如果没有,结果就是正确的合并。
-
对于不存在于所有三个插槽中的文件(例如,可能发生了重命名或复制的位置),事情变得更加复杂。这可能会导致高层次又名树冲突,这被视为冲突,但不会显示为冲突的工作树副本。但是这种情况在这里并不适用,所以我们可以忽略它。
肉git merge已经完成,但现在有清理步骤:
-
正确合并的文件被放到槽 0(如果/根据需要写出到工作树)。
-
有合并冲突的文件留在所有三个插槽中;工作树在合并时得到 Git 的最大努力,包括冲突标记。
-
在发生冲突的情况下,合并现在在中间停止;用户必须完成它。每个文件的工作树和索引副本都存在于此处供用户使用。
-
否则,除非被告知停止而不提交,否则会
git merge自行完成合并,通常是通过创建一个新的合并提交。
如果此合并结果不是您想要的,您的补救措施包括但不限于以下内容:
-
更改输入(例如,通过向一个或两个分支添加更多提交)。
-
使用
git merge -n以便git merge在提交合并结果之前停止。使用索引和工作树文件——现在都暂存提交,在索引槽 #0 中每个文件只有一个版本——来产生你想要的结果。然后,提交结果。请注意,这称为邪恶合并。它没有什么问题,但是如果你让 Git 重复合并——例如,使用花哨的新git rebase --rebase-merges代码——Git 不会知道使新合并成为一个邪恶的合并,所以用提交消息清楚地标记这一点是明智的或者其他的东西。 -
进行合并,让它成为“错误的”,提交它(并且可能标记它,特别是对于稍后的skip-during-bisect),然后添加一个提交来修复问题。