繁体   English   中英

为什么这个 GIT 合并不会导致冲突?

[英]Why does this GIT merge not result in conflicts?

我们今天在工作中发现了 GIT 的一个严重问题,我想知道这是一个错误还是设计使然,以及如何解决这个问题。

考虑以下事件序列:

  1. 在 Master 上创建分支“test1”
  2. 切换到大师:
    1. 编辑文件并将更改提交为提交“X”
  3. 切换到“test1”:
    1. 樱桃选择从大师提交“X”
    2. 恢复之前的提交
    3. 将 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 会注意三个快照:

  • 一个快照是当前快照,即,如果您运行git rev-parse HEAD您将获得其哈希 ID 的提交。 如果HEAD附加到分支名称(通常是这样),那就是给定分支的提示提交。

  • 一个快照是您在命令行中命名的快照: git merge foo查找foo以获取提交哈希 ID。

  • 第三个,在许多方面也是最重要的,快照是合并基础 (Git 将这个编号为“#1”, HEAD / --ours为 #2,另一个 / --theirs为 #3,在内部,即使我们必须先定位其他两个输入才能定位此合并基础输入.) 合并库的提交哈希 ID 通过提交图定位。 在您的情况下,它是您正在调用 commit X 的提交之前的提交。

让我们像这样绘制这些提交,将更新的提交放在右侧,单个大写字母代表每个实际提交哈希 ID:

          X   <-- master
         /
...--G--H   <-- here's where both branches start diverging
         \
          X'-X"  <-- test1

在这里, commit X包含您在master所做的更改; 提交X'具有相同的更改,而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 rebase --rebase-merges不会知道让合并成为一个邪恶的合并,所以明智的做法是用提交消息或其他东西清楚地标记这一点。

  • 进行合并,让它成为“错误的”,提交它(并且可能标记它,特别是对于稍后的skip-during-bisect),然后添加一个提交来修复问题。

在您描述的情况下:这是git merge操作的预期结果——torek 的回答详细说明了原因。

在 git 方面:

您提到的工作流程的一个问题是git cherry-pick没有注册原始提交的链接。 您必须知道,不知何故,管理cherry-pick有点像管理代码中的“复制/粘贴”:如果在一侧修复了错误,则应手动将其移植到另一侧。

顺便说一下, git rebase也是如此:如果您应用了一个 rebase 操作,该操作以某种方式保留了原始分支,并且还创建了一个带有一些复制提交的新分支,那么这也是复制/粘贴提交。

一个更通用的原则是:

git merge没有冲突的情况下成功”并不意味着“生成的代码没有错误”。

您应该始终验证生成的代码是否符合您的期望:

  • 应审查结果代码,
  • 您应该使用其他步骤验证您的代码,例如编译/构建脚本、单元测试和 QA,
  • 在您的示例中:如果该问题足够严重,您可以例如添加一个 linter(或任何检查代码的脚本)来检查被控代码不存在

对于您的发布分支尤其如此; 有时,例如您描述的那个(开发人员合并到一个分支),这可能更多是关于您的团队意识到潜在的陷阱——并检查差异。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM