Git: 只有一些未修改但已删除的文件是合并冲突。为什么?

Git: Only some unmodified but deleted files are merge conflicts. Why?

我想了解为什么会这样:

我已经从 master 分支了 mybranch 并进行了几次涉及文件 ABC 的提交。与此同时,其他人已将删除文件 XY 的提交推送到 master 以及一些其他更改。当我将 master 合并到 mybranch 时,git status 告诉我 X 是“未合并的”,而 Y 是“已删除的”。为什么这些文件被区别对待?

我相信这会减少 http://github.com/borglab/SwiftFusion

现实生活中发生在我身上的事情

[编辑:我弄错了,所以上面的描述是不准确的。不过,@torek 的 非常出色且具有指导意义,所以我不会尝试删除这个问题]

在标签 conflict-merge-source 上有 master 和在标签 conflict-merge-target 上有 mybranch,但我发布了实际的回购以防我遗漏了什么。这两个文件是Sources/SwiftFusion/Core/FixedShapeTensor.swift(冲突)和Tests/SwiftFusionTests/Core/FixedShapeTensorTests.swift(删除).

TL;DR

如您所述,两个文件之一存在 modify/delete 冲突,而另一个完全没有冲突。两个文件之一没有冲突的原因是合并的“一侧”根本没有触及文件,但另一侧触及了。

对于有冲突的文件,Git 将修改后的文件留在您的 work-tree 中,将冲突留在 Git 的索引中,您必须在此处解决它。 如何解决由你决定,但如果解决方案是“保留删除”,你可以在该名称上使用git rm,如果是保留修改后的文件你可以使用 git add。无论哪种方式,Git 的 index——建议的 next 提交——现已更新,并且此特定冲突已解决。

我克隆了 the repository in question 并发现了以下内容:

$ git merge-base --all conflict-merge-source conflict-merge-target
a07af749bdd416c5217c363b3f1da509c58d2d14
$ base=$(git merge-base --all conflict-merge-source conflict-merge-target)
$ git diff --find-renames --name-status $base conflict-merge-source
M       Sources/BeeDataset/BeeFrames.swift
M       Sources/SwiftFusion/Core/DataTypes.swift
D       Sources/SwiftFusion/Core/FixedShapeTensor.swift
M       Sources/SwiftFusion/Core/MathUtil.swift
A       Sources/SwiftFusion/Core/TensorVector.swift
M       Sources/SwiftFusion/Image/OrientedBoundingBox.swift
A       Sources/SwiftFusion/Inference/AppearanceTrackingFactor.swift
M       Sources/SwiftFusion/Inference/FactorGraph.swift
M       Sources/SwiftFusion/Inference/FactorsStorage.swift
M       Sources/SwiftFusion/Inference/JacobianFactor.swift
A       Sources/SwiftFusion/Inference/PPCA.swift
M       Sources/SwiftFusion/Inference/PPCATrackingFactor.swift
M       Sources/SwiftFusion/Optimizers/CGLS.swift
M       Sources/SwiftFusion/Optimizers/LM.swift
M       Sources/SwiftFusionBenchmarks/Patch.swift
M       Tests/BeeDatasetTests/BeeDatasetTests.swift
A       Tests/BeeDatasetTests/BeePPCATests.swift
D       Tests/SwiftFusionTests/Core/FixedShapeTensorTests.swift
M       Tests/SwiftFusionTests/Image/PatchTests.swift
M       Tests/SwiftFusionTests/Inference/FactorGraphTests.swift
R062    Tests/SwiftFusionTests/Inference/PPCATrackingFactorTests.swift
        Tests/SwiftFusionTests/Inference/PPCATests.swift
M       Tests/SwiftFusionTests/Inference/SwitchingMCMCTests.swift
M       Tests/SwiftFusionTests/Optimizers/LMTests.swift
$ git diff --find-renames --name-status $base conflict-merge-target
M       Sources/SwiftFusion/Core/FixedShapeTensor.swift
M       Sources/SwiftFusion/Inference/AnyArrayBuffer+Differentiable.swift
R050    Sources/SwiftFusion/Inference/ValuesStorage.swift
        Sources/SwiftFusion/Inference/AnyArrayBuffer+Vector.swift
M       Sources/SwiftFusion/Inference/ArrayBuffer+Differentiable.swift
M       Sources/SwiftFusion/Inference/ArrayBuffer+Vector.swift
M       Sources/SwiftFusion/Inference/PenguinExtensions.swift
M       Tests/SwiftFusionTests/Inference/AnyArrayBufferTests.swift

注意 R062R050 行:Git 检测到,自从合并基础提交 $base (a07af749...) 以来,这些文件已重命名在两个选定的提交中。这在这里并不是那么重要,但它们排得很长,我将它们分开以便发布。

我不完全确定你签出的两个提交中的哪一个作为你的分支,以及你使用 git merge 命令选择了哪个,但是由于合并过程大部分是对称的,1没关系。

我们可以用分离的 HEAD 进行合并,但我喜欢有一个分支名称,所以我现在创建了一个:

$ git checkout -b t1 conflict-merge-source
Switched to a new branch 't1'
$ git merge conflict-merge-target
CONFLICT (modify/delete): Sources/SwiftFusion/Core/FixedShapeTensor.swift
deleted in HEAD and modified in conflict-merge-target. Version
conflict-merge-target of Sources/SwiftFusion/Core/FixedShapeTensor.swift
left in tree.
Automatic merge failed; fix conflicts and then commit the result.

(再说一次,为了发帖,我分了很长的一行。)


1任何不对称都是将一个提交视为“我们的”而将另一个视为“他们的”的结果。例如,-X extended-options 自己必须选择我们的与他们的,而 rename/rename 冲突,如果有冲突,则必须选择要重命名的名称(如果有)。这些 tie-breakers 引入了不对称性。


git merge 如何合并更改

在所有 git merge 的情况下,Git 将 merge base(我们在上面找到的)与每个要合并的提交进行比较。这会将这两个快照中的每一个都变成 changes-since-a-common-starting-point。我们可以使用 git diff(见上面的例子)找出哪些文件包含什么样的变化。

找到这些更改后,合并代码的工作现在是合并这些更改。在合并的任何“一侧”进行一些更改与在另一“侧”进行 no 更改的组合,是进行大规模更改。大多数这些文件都是这种情况——例如 M 中的所有 22 个文件。所以 git merge 可以只从 更改 文件的那一侧获取文件的副本,在这种情况下。合并的这一部分很简单:有时,合并必须从 merge-base 提交的文件的基本版本开始,然后添加 双方的 更改。

但我们列出了其他八个状态(不是 M)文件,一侧 7 个,另一侧 1 个:

  • 从底部到一侧有四个 A 个文件(新文件)。它们对另一个没有 same-named 操作,因此合并操作只是将这些新文件视为新文件。

  • 有两个 R 文件。它们位于另一方未触及的不同文件中,因此 Git 仅保留重命名的副本:我们仅获得新名称(如果内容也已更改,则包含新内容);合并基础提交中原始文件的原始副本被完全删除。 (如果合并更难,情况可能并非如此。)

  • 有两个 D 文件,只有一侧。它们是:

     D       Sources/SwiftFusion/Core/FixedShapeTensor.swift
     D       Tests/SwiftFusionTests/Core/FixedShapeTensorTests.swift
    

    (与您记下的两个相同)。这是我们在 other 端看到的,对于这两个文件:

     M       Sources/SwiftFusion/Core/FixedShapeTensor.swift
    

    也就是说,第二个文件根本没有出现:它没有被触及。

应该Git如何将“已删除”与“未修改”结合起来? Git的回答是“保留删除”。 应该Git如何将“删除”与“修改”结合起来? Git 的答案是 将修改后的文件保留在 work-tree 中,并在索引 中将文件标记为冲突。这并不总是正确的答案,但如果不是,您可以手动修改索引内容,例如,从 HEADMERGE_HEAD.

恢复已删除的文件

最终的合并提交,一旦完成,将包含您 Git 复制到 Git 索引中的所有文件。您的 work-tree 文件副本对 Git 来说并不重要;它们就在那里,以便您可以与它们一起工作。每个文件的索引副本都是 Git 自己的内部压缩和 de-duplicated 形式。