首页 > 解决方案 > Git Merge 错误地识别块中的冲突

问题描述

我的存储库中有一个文件 ,data.csv它代表 CSV 格式的数据库。为了举例,让我们假设的内容data.csv

1,2,3
2,3,4
4,5,6

原来,我只有master分支,我创建了两个分支A和B,我data.csv独立修改。我注意到有时,3-way diff 算法会识别出在我看来根本不应该是冲突的冲突。例如,如果 A 将文件修改为

1,4,5
2,3,4
4,5,6

和 B 将文件修改为

1,2,3
2,6,7
4,5,6

当我git merge A从分支 B 发出,而不是自动合并这些版本时,它实际上报告了以下冲突:

<<<<<<< HEAD
1,2,3
2,6,7
=======
1,4,5
2,3,4
>>>>>>> A
4,5,6

但在我看来,实际上这些版本应该可以与 3-way diff 逻辑在逐行级别上自动合并,因为 A 只修改第一行,而 B 只修改第二行。

我的问题:为什么会发生这种情况?有没有办法强制 Git 做一个更细粒度的差异(例如逐行)?(或者,有什么方法可以强制 Git 意识到这些更改实际上是可自动合并的?)

标签: gitmergediffgit-mergegit-diff

解决方案


正如我在评论中提到的,您今天可以处理这个问题的方法是编写一个合并驱动程序。编写一个好的合并驱动程序并非易事,但您将能够对其进行试验,并将其仅应用于特定文件。

如果您自己没有定义合并驱动程序,Git 会使用它自己的内置驱动程序。这个内置的大部分与commandgit merge-file相同。(它可能与它完全相同,因为它们是从 Git 中的各种共享源文件构建的。请注意,内置的“低级”合并驱动程序ll-merge.c是选择运行配置的合并驱动程序或使用内置代码的地方,确实发生了。)

请注意,您的合并驱动程序至少需要三个输入(您最多可以给它五个输入):

  • 驱动程序可以在其中找到文件的合并基本版本的路径名;
  • 驱动程序可以在其中找到文件的当前( ) 版本的路径名,并且--ours驱动程序必须将文件的最终合并版本写入该路径名;和
  • --theirs驱动程序可以在其中获得文件的其他 ( ) 版本的路径名。

驱动程序的工作是读取三个输入版本,无论它选择什么,然后将正确的合并结果写入这三个路径名的中间一个,无论它喜欢什么。路径名将是临时文件的名称:不要假设这三个文件名中的任何一个都有意义或与正在合并的文件的历史名称有任何关系。

您可以传递给您自己的程序的额外数据包括用户所需的冲突标记大小(默认为 7)和合并结果最终将被复制到的路径名。也就是说,假设我们正在合并一个文件,该文件在合并库中orig.wrongsuffix的名称--ours是 ,提交中ours.csv的名称是 ,提交中的名称--theirsrenamed-wrongly.csv. 这三个输入文件可能具有格式.git-tmp-1234567或类似的文件名。鉴于现有的recursiveorresolve策略,驱动程序的输出最终将在一个名为 的文件中结束ours.csv,但因为存在重命名/重命名冲突(我们修复了名称,他们试图修复名称),即使我们的合并驱动程序能够产生合并结果,合并也会因冲突而停止

为了指示一个成功的合并——即,合并不必因为你自己的合并驱动程序发现的冲突而停止——你的合并驱动程序应该在它终止时返回一个成功的退出状态。换句话说,从 C 代码中调用exit(0); 来自 Python,使用sys.exit(0)或等效;从 Go 开始,使用os.Exit(0); 等等。为了表明,尽管您的驱动程序已尽最大努力,您的代码仍无法产生正确的合并结果——因此可能会或可能不会在其输出文件中留下合并冲突标记——提供一个非零退出状态(最好是一个小的非零值,例如1;在 125-127 附近有一些特殊值可用于git bisect可能在 Git 的其他部分也被特殊处理的情况;出于传统的 Unix 编程原因,值不应超过 127)。

要告诉 Git使用您的合并驱动程序,您需要做两件事:

  • 创建一个.git/config$HOME/.gitconfig或其他定义驱动程序的条目,告诉 Git 如何运行它;
  • 例如,创建一个.gitattributes条目(如果需要,首先创建文件)告诉 Git在这个特定文件上使用您的驱动程序。.csv

定义这些的说明在gitattributes 文档中。


推荐阅读