问题描述
我的存储库中有一个文件 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,6
1,6,7
4,6
当我从分支 B 发出 git merge A
时,它实际上报告了以下冲突,而不是自动合并这些版本:
<<<<<<< HEAD
1,7
=======
1,4
>>>>>>> A
4,6
但在我看来,实际上这些版本应该可以在逐行级别上与 3 路 diff 逻辑自动合并,因为 A 只修改第一行,而 B 只修改第二行。>
我的问题:为什么会发生这种情况?有没有办法强制 Git 进行更细粒度的差异(例如逐行)? (或者,有什么方法可以强制 Git 意识到这些更改实际上是可自动合并的?)
解决方法
像我mentioned in a comment一样,您今天处理这个问题的方法是编写一个合并驱动程序。编写一个好的合并驱动程序并非易事,但您将能够对其进行试验,并将其仅应用于特定文件。
如果您不自己定义合并驱动程序,Git 会使用自己的内置驱动程序。这个内置的几乎与 the git merge-file
command 相同。 (它可能完全相同,因为它们是从 Git 中的各种共享源文件构建的。请注意,ll-merge.c
中内置的“低级”合并驱动程序是运行配置合并驱动程序的选择,或者使用内置代码,确实发生了。)
请注意,您的合并驱动程序至少需要三个输入(您最多可以给它五个输入):
- 驱动程序可以在其中找到文件的合并基础版本的路径名;
- 驱动程序可以在其中找到文件的当前 (
--ours
) 版本的路径名,并且驱动程序必须将文件的最终合并版本写入该路径名;和 - 驱动程序可以访问文件的其他 (
--theirs
) 版本的路径名。
驱动程序的工作是读取三个输入版本,无论它选择什么,然后将正确的合并结果写入这三个路径名中的中间一个。路径名将是临时文件的名称:不要假设这三个文件名中的任何一个都有意义或与被合并文件的历史名称有任何关系。
您可以传递给您自己的程序的额外数据包括用户所需的冲突标记大小(默认为 7)以及合并结果最终将复制到的路径名。也就是说,假设我们正在合并一个文件,其在合并库中的名称为 orig.wrongsuffix
,其在 --ours
提交中的名称为 ours.csv
,其名称在 {{1} } 提交是--theirs
。这三个输入文件可能具有 renamed-wrongly.csv
或类似形式的文件名。给定现有的 .git-tmp-1234567
或 recursive
策略,驱动程序的输出最终会在一个名为 resolve
的文件中结束,尽管因为存在重命名/重命名冲突(我们修复了名称,他们试图修复名称),即使我们的合并驱动程序能够产生合并结果,合并也会因冲突而停止。
为了指示成功的合并——即,合并不必因您自己的合并驱动程序发现的冲突而停止——您的合并驱动程序在终止时应返回成功退出状态。换句话说,从 C 代码中,调用 ours.csv
;来自 Python,使用 exit(0)
或等价物;从 Go 开始,使用 sys.exit(0)
;等等。为了表明,尽管您的驱动程序尽了最大努力,您的代码仍无法产生正确的合并结果——因此可能会或可能不会在其输出文件中留下合并冲突标记——提供一个非零退出状态(最好是一个小的非零值,例如1;在 125-127 附近有一些特殊值用于 os.Exit(0)
之类的东西,这些值在 Git 的其他部分也可能被特殊对待;出于传统的 Unix 编程原因,值不应超过 127)。>
要告诉 Git使用您的合并驱动程序,您需要做两件事:
- 创建一个
git bisect
或.git/config
或其他定义驱动程序的条目,告诉 Git 如何运行它; - 创建一个
$HOME/.gitconfig
条目(如果需要,首先创建文件)告诉 Git 在这个特定的.gitattributes
文件中使用您的驱动程序。
定义这些的说明在 the gitattributes documentation 中。
,重叠或邻接规则的存在是有原因的。您可以找到不需要它的情况,但是,对于 dvcs 来说是的,如果您拉出 linux 历史并使用 automerge-abutting-changes 规则重新运行过去十五年中的所有合并,您会发现它会产生非常糟糕的结果很多情况。没有任何规则是完美的,你必须在某处划清界限,重叠或邻接是一种可以最大限度地减少不必要的麻烦,同时在实践中几乎不会犯下应受指责的错误的规则。
,当您合并两个修改了同一个文件的分支时,总是会发生合并冲突。在示例中,您遇到了合并冲突,因为分支 A 修改了 data.csv,分支 B 也修改了 data.csv。要解决此冲突,您必须决定在 >>>>>> A 之间要保留哪些行以及删除哪些行。此外,您必须删除 >>>>>> A.
之后运行 git add data.csv 命令解决冲突,然后运行 git commit 结束合并。