【问题标题】:Git Merge Erroneously Identifies Conflicts in BlocksGit Merge 错误地识别块中的冲突
【发布时间】:2020-12-21 05:57:47
【问题描述】:

我的存储库中有一个文件 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

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

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

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

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

【问题讨论】:

  • @matt 感谢您的链接。它回答了我为什么会发生这种情况的问题,但我想知道是否有办法强制 git 更改其算法以逐行检查这些更改。例如,我注意到可以指定git mergediff-algorithm 参数。这有帮助吗?
  • 虽然 diff 算法定义了每个更改的 范围,但选择如何组合这些范围的是 合并策略。理论上可以编写新的合并策略,但这非常困难:Git 现在(可能是今年或明年)近 20 年来第一次获得新的合并策略。也可以编写一个 merge driver 与现有的默认合并策略一起使用,这更加现实。这将是您的特定案例的方法。
  • @paulinho 我想知道new Git 2.30 Q1 2021 ORT merge strategy 是否会在这里改变任何东西(“表面上递归的双胞胎”)。

标签: git merge diff git-merge git-diff


【解决方案1】:

正如我mentioned in a comment 一样,您今天处理这个问题的方法是编写一个合并驱动程序。编写一个好的合并驱动程序并非易事,但您将能够对其进行试验,并将其仅应用于特定文件。

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

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

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

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

您可以传递给您自己的程序的额外数据包括用户所需的冲突标记大小(默认为 7)和合并结果最终将被复制到的路径名。也就是说,假设我们正在合并一个文件,其在合并库中的名称为orig.wrongsuffix,其在--ours 提交中的名称为ours.csv,其在--theirs 提交中的名称为renamed-wrongly.csv。这三个输入文件可能具有.git-tmp-1234567 或类似形式的文件名。鉴于现有的recursiveresolve 策略,驱动程序的输出最终将在一个名为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 文件上使用您的驱动程序。

定义这些的说明在the gitattributes documentation

【讨论】:

    【解决方案2】:

    重叠或邻接规则的存在是有原因的。您可以找到不需要它的情况,但是,对于 dvcs 来说,如果您拉说 linux 历史并使用 automerge-abutting-changes 规则重新运行过去 15 年中的所有合并,您会发现它会产生非常糟糕的结果很多情况。没有规则是完美的,你必须在某处划清界限,重叠或邻接是一种可以将不必要的大惊小怪最小化的规则,同时在实践中几乎不会犯应受责备的错误。

    【讨论】:

      【解决方案3】:

      当您合并两个修改了同一个文件的分支时,总是会发生合并冲突。在示例中,您遇到了合并冲突,因为分支 A 修改了 data.csv,分支 B 也修改了 data.csv。要解决此冲突,您必须决定在 >>>>> 之间要保留哪些行以及要删除哪些行。此外,您必须删除 >>>>>> A.
      之后运行 git add data.csv 命令解决冲突,然后运行 ​​git commit 结束合并。

      【讨论】:

      • 嗨@MohitNatani,我的理解是修改同一个文件并不总是会导致合并冲突。这些文件也必须以不同的方式在同一行上进行修改。请参阅我对原始帖子的评论以了解我仍然想知道的内容,如果您知道这些问题的答案,如果您能更新您的答案,我将不胜感激!
      猜你喜欢
      • 2021-12-23
      • 1970-01-01
      • 2019-01-11
      • 2019-05-18
      • 2020-08-30
      • 2020-05-28
      • 1970-01-01
      • 2015-10-28
      • 2017-11-05
      相关资源
      最近更新 更多