Git,如何判断合并文件时发生了什么变化 'deleted by us'
Git, how to tell what changed when merging a file 'deleted by us'
我在功能分支中删除了一个文件,因为我在其他地方重构了它的代码。
Develop 分支更改了此文件中的代码以修复错误。
我将开发分支合并到我的功能分支中,以便在我继续开发该功能时使其保持最新。
我收到有关文件的 'Deleted by us' 冲突。
如何获得对开发分支上的文件所做更改的差异,以便我可以将这些更改重新实现到我的功能分支上的重构代码中?
您想查看索引槽 #1,并将其与索引槽 #3 中的内容进行比较,以获得路径:
git diff :1:path :3:path
您还可以提取各种index-slot版本,使用git checkout-index
,然后使用普通文件操作而不是仅使用[=276来检查它们=] 工具。 git mergetool
程序执行后者,因此如果您使用 git mergetool
,您将拥有该文件的两个版本。 (我自己从不使用 git mergetool
。)
这是什么意思?
根据定义,任何合并都有三个输入。三个输入是 commits,1,其中包含文件的快照,所以通常这三个输入会导致每个文件的三个版本。这三个文件版本结束了索引中的这些编号槽:也就是说,对于正在合并的某些文件 path/to/file.ext
,有一个 :1:path/to/file.ext
、一个 :2:path/to/file.ext
和一个 :3:path/to/file.ext
. (在这种情况下,当 b运行 中的一个 删除 一个文件时,这三个条目中的一个不存在。)
让我们倒序列出索引槽,因为最后一个是这里的关键:
插槽 3(他们的) 保存 他们的 文件,在 develop
b 的顶端运行ch:你运行git merge develop
。请记住,每次提交都是所有文件的完整快照。这不是一组 更改 ,它只是一个快照。然而不知何故,Git 知道他们 更改了 某个特定文件。改了,关于what?
插槽 2(我们的) 保存您当前提交的文件(也称为 HEAD
)。在这种情况下,HEAD
中的某些文件已被删除。但是:Git 怎么知道你 删除了 文件?删了,关于什么?
插槽 1(基地) 保存来自 合并基地 的文件。这就是这两个中的什么。
合并基础是您的分歧 branch-tips 开始的(单一/最佳)共同提交。 Git 将此提交(实际上是整个提交)与两个 branch-tip 提交(作为一个整体)中的每一个进行比较,以匹配文件。2 然后,匹配了三个提交中的所有文件后,Git 将开始整个 "put some files into special slots" 过程。
也就是说,如果我们在您开始 git merge
时绘制提交图,它看起来像这样 — 尽管确切的细节会有所不同,而且通常很难扫描图来找到 B
和 R
(HEAD
或提交 L
通常很容易找到):
o--...--L <-- our-branch (HEAD)
/
...--A--B
\
o--...--R <-- develop
提交 A
和 B
(以及早于 A 的所有内容)都在 both b运行ches 上,因此是共享的, 但 B
是 最好的 一个,因为它是 最后一个 共享的一个。
为了进行合并,Git 比较了 B
与 L
以查看 我们 发生了什么变化,并且 B
vs R
看看 他们 改变了什么。没有人更改的文件在所有三个提交中都是相同的,因此 Git 使用此类文件的任何版本。对于只有 他们 更改的文件,Git 从提交 R
获取他们的文件版本。对于只有 we 更改的文件,Git 从提交 L
.
获取该文件的我们版本
对于我们 都更改的文件,在某些方面——包括 "delete the file entirely"–Git 必须更加努力。现在重要的是要了解索引——Git 使 new 提交的东西——在合并期间具有扩展的作用。
通常情况下,索引对于每个文件只有一个槽。这个插槽已编号,但它是 number-zero 插槽,因此您通常不需要做任何特别的事情来引用它:您只需告诉 Git 到 git 添加 <em>somefile</em>
将文件 somefile
从 work-tree 复制到索引中,使其准备提交。
但是,对于合并案例,我们和他们都对一个文件做了一些事情,Git 需要 三个 ——好吧,最多三—每个文件的副本。因此,对于这种特殊情况,Git 将来自提交 B
的文件的 merge base 版本放入索引槽 #1。 Git 将我们的文件版本(来自提交 L
并且已经在索引槽 #0 中)移动到索引槽 #2 中,并将来自提交 R
的文件的另一个版本放入索引中插槽 #3。
对于删除的文件(这种特殊情况)Git 将插槽 #2 或 #3 留空,具体取决于谁删除了文件。对于 add/add 冲突——文件不存在于 B
但存在于 L
和 R
中——Git 将插槽 #1 留空。 (不存在 delete/delete 冲突这样的事情:如果我们都删除了文件,Git 只是删除文件并继续。但是有一些重命名的情况,这更棘手。)
当合并因合并冲突而停止时,这些索引槽仍然填充三个,或者在第第二种情况,文件的版本。因此,您可以检查索引,查看 higher-stage(非零)插槽,并了解哪些文件存在冲突。各种 Git 工具,包括 git status
和 git diff
,都可以执行此操作。
当您通过任何方式解决冲突后,您必须告诉 Git 清除 higher-stage 槽并将文件的良好副本放入索引槽 #0。最简单的方法是 git add
文件的正确版本。 (如果没有正确的版本——如果它应该消失——你可以 git rm
文件,从所有索引槽和 work-tree 中删除它。一般来说,如果它不在 work-tree, git add
也将其从索引中删除,尽管我习惯于 git rm
-ing 应该消失的冲突文件,所以我没有测试 git add
是否一致关于在此处删除 higher-stage 条目。如果它不在 work-tree 中,git rm
将其从索引中删除,抱怨它不在 work-tree 中,然后一切都是好。)
1在偷偷摸摸的特殊情况下,合并尚未提交的文件是可能的。例如,git checkout -m
或 git stash apply
会出现这种情况。在这种情况下,Git 通常只是根据需要将项目从插槽 0 移动到插槽 2 ...以及 work-tree 中但从未提交的更改,can 被期望插槽 #2 是 work-tree 中内容的安全副本的工具破坏 and/or! (这是我不喜欢 git stash
的原因之一。)但是 运行ning git merge
不会调用这个奇怪的路径,事实上,如果你的存储库不在一个很好的 ready-to-start-a-merge状态。
2这是重命名检测的用武之地。如果您在提交中重命名了某个文件,是 :1:path/to/file.ext
现在可能是 :2:path/different/file.ext
或 :2:path/to/different.ext
。这些文件被检测为同一个文件,尽管它现在有两个不同的名称。这里有一个小缺陷,因为在合并的整个过程中插槽没有链接在一起。例如,如果合并因冲突而停止,则很难恢复 2:path/to/different.ext
与 :1:path/to/file.ext
一致的事实。 Git 打印 信息,因此它可能仍然在 window 中,您可以在笔记本电脑或其他任何设备上看到,但不会记录在其他地方。
脚注:more-tangled 图表
这是一个 b运行ch 的示例图,其中包含一些重复的合并:
...--A--B--C--D--G--H--K <-- branch1
\ \ \
E-----F--I--J--L <-- branch2
这里,F
和J
都是合并提交,有两个parent:F
的两个parent是E
和D
,J
的两个parent是I
和H
。如果您 运行 git checkout branch2; git merge branch1
,您将尝试进行新的合并提交 M
,其第一个 parent 是 L
,第二个是 K
。这里的合并基础是提交 H
因为从 K
开始并向后工作,我们得到 H
,而从 L
开始并向后工作,我们得到 J
然后——可以说是同时——到 I
和 H
,因为我们已经到达顶部的 H
,这就是合并基础。
请注意,合并基础计算是对称的。如果我们 git checkout branch1 && git merge branch2
,Git 仍然选择 H
作为合并基础。但是,如果合并基础不明显,您可以 运行:
git merge-base --all branch1 branch2
这将产生 "best merge base" 的所有候选人。
理想情况下,只有一个。然而,历史看起来像这样:
...--o--o---A--... <-- branch1
\ /
X
/ \
...--o--o---B--... <-- branch2
其中 A
和 B
都是 "equally close" 到两个 b运行ch 提示的合并提交,有 两个 合并基地。这是通过从 b运行ch1 合并到 b运行ch2 并立即进行合并,使用 git merge --no-ff
,从 b运行ch2 到 b运行ch1,有时称为 criss-cross 合并。
在这个模棱两可的合并基本案例中,没有单一的最佳候选者。由于合并通常是对称的,因此提交 A
和 B
的 contents 可能是相同的。在那种情况下,选择 A
和 B
Git 中的哪一个都无关紧要。但是如果内容不一样,也无所谓,这就是Git的递归合并的用武之地。当有多个合并基础时,Git会先,作为内部合并,合并两个合并基础(使用 他们的 最佳共同祖先,无论是什么)并根据结果进行新的提交。 Git 将使用该新提交的内容作为外部合并的合并基础。
-s resolve
合并策略(不是默认的)选择两者之一,A
或 B
,在 apparent-random(真的,只是更方便的算法)。如果 A
和 B
具有相同的内容,则可以正常工作;如果不是,递归 "merge A and B first" 的内部合并可能会 产生更好的结果。如果递归合并有冲突,它会产生一些混乱。 (通常明智的做法是避免 criss-cross 合并。)
我在功能分支中删除了一个文件,因为我在其他地方重构了它的代码。
Develop 分支更改了此文件中的代码以修复错误。
我将开发分支合并到我的功能分支中,以便在我继续开发该功能时使其保持最新。
我收到有关文件的 'Deleted by us' 冲突。
如何获得对开发分支上的文件所做更改的差异,以便我可以将这些更改重新实现到我的功能分支上的重构代码中?
您想查看索引槽 #1,并将其与索引槽 #3 中的内容进行比较,以获得路径:
git diff :1:path :3:path
您还可以提取各种index-slot版本,使用git checkout-index
,然后使用普通文件操作而不是仅使用[=276来检查它们=] 工具。 git mergetool
程序执行后者,因此如果您使用 git mergetool
,您将拥有该文件的两个版本。 (我自己从不使用 git mergetool
。)
这是什么意思?
根据定义,任何合并都有三个输入。三个输入是 commits,1,其中包含文件的快照,所以通常这三个输入会导致每个文件的三个版本。这三个文件版本结束了索引中的这些编号槽:也就是说,对于正在合并的某些文件 path/to/file.ext
,有一个 :1:path/to/file.ext
、一个 :2:path/to/file.ext
和一个 :3:path/to/file.ext
. (在这种情况下,当 b运行 中的一个 删除 一个文件时,这三个条目中的一个不存在。)
让我们倒序列出索引槽,因为最后一个是这里的关键:
插槽 3(他们的) 保存 他们的 文件,在
develop
b 的顶端运行ch:你运行git merge develop
。请记住,每次提交都是所有文件的完整快照。这不是一组 更改 ,它只是一个快照。然而不知何故,Git 知道他们 更改了 某个特定文件。改了,关于what?插槽 2(我们的) 保存您当前提交的文件(也称为
HEAD
)。在这种情况下,HEAD
中的某些文件已被删除。但是:Git 怎么知道你 删除了 文件?删了,关于什么?插槽 1(基地) 保存来自 合并基地 的文件。这就是这两个中的什么。
合并基础是您的分歧 branch-tips 开始的(单一/最佳)共同提交。 Git 将此提交(实际上是整个提交)与两个 branch-tip 提交(作为一个整体)中的每一个进行比较,以匹配文件。2 然后,匹配了三个提交中的所有文件后,Git 将开始整个 "put some files into special slots" 过程。
也就是说,如果我们在您开始 git merge
时绘制提交图,它看起来像这样 — 尽管确切的细节会有所不同,而且通常很难扫描图来找到 B
和 R
(HEAD
或提交 L
通常很容易找到):
o--...--L <-- our-branch (HEAD)
/
...--A--B
\
o--...--R <-- develop
提交 A
和 B
(以及早于 A 的所有内容)都在 both b运行ches 上,因此是共享的, 但 B
是 最好的 一个,因为它是 最后一个 共享的一个。
为了进行合并,Git 比较了 B
与 L
以查看 我们 发生了什么变化,并且 B
vs R
看看 他们 改变了什么。没有人更改的文件在所有三个提交中都是相同的,因此 Git 使用此类文件的任何版本。对于只有 他们 更改的文件,Git 从提交 R
获取他们的文件版本。对于只有 we 更改的文件,Git 从提交 L
.
对于我们 都更改的文件,在某些方面——包括 "delete the file entirely"–Git 必须更加努力。现在重要的是要了解索引——Git 使 new 提交的东西——在合并期间具有扩展的作用。
通常情况下,索引对于每个文件只有一个槽。这个插槽已编号,但它是 number-zero 插槽,因此您通常不需要做任何特别的事情来引用它:您只需告诉 Git 到 git 添加 <em>somefile</em>
将文件 somefile
从 work-tree 复制到索引中,使其准备提交。
但是,对于合并案例,我们和他们都对一个文件做了一些事情,Git 需要 三个 ——好吧,最多三—每个文件的副本。因此,对于这种特殊情况,Git 将来自提交 B
的文件的 merge base 版本放入索引槽 #1。 Git 将我们的文件版本(来自提交 L
并且已经在索引槽 #0 中)移动到索引槽 #2 中,并将来自提交 R
的文件的另一个版本放入索引中插槽 #3。
对于删除的文件(这种特殊情况)Git 将插槽 #2 或 #3 留空,具体取决于谁删除了文件。对于 add/add 冲突——文件不存在于 B
但存在于 L
和 R
中——Git 将插槽 #1 留空。 (不存在 delete/delete 冲突这样的事情:如果我们都删除了文件,Git 只是删除文件并继续。但是有一些重命名的情况,这更棘手。)
当合并因合并冲突而停止时,这些索引槽仍然填充三个,或者在第第二种情况,文件的版本。因此,您可以检查索引,查看 higher-stage(非零)插槽,并了解哪些文件存在冲突。各种 Git 工具,包括 git status
和 git diff
,都可以执行此操作。
当您通过任何方式解决冲突后,您必须告诉 Git 清除 higher-stage 槽并将文件的良好副本放入索引槽 #0。最简单的方法是 git add
文件的正确版本。 (如果没有正确的版本——如果它应该消失——你可以 git rm
文件,从所有索引槽和 work-tree 中删除它。一般来说,如果它不在 work-tree, git add
也将其从索引中删除,尽管我习惯于 git rm
-ing 应该消失的冲突文件,所以我没有测试 git add
是否一致关于在此处删除 higher-stage 条目。如果它不在 work-tree 中,git rm
将其从索引中删除,抱怨它不在 work-tree 中,然后一切都是好。)
1在偷偷摸摸的特殊情况下,合并尚未提交的文件是可能的。例如,git checkout -m
或 git stash apply
会出现这种情况。在这种情况下,Git 通常只是根据需要将项目从插槽 0 移动到插槽 2 ...以及 work-tree 中但从未提交的更改,can 被期望插槽 #2 是 work-tree 中内容的安全副本的工具破坏 and/or! (这是我不喜欢 git stash
的原因之一。)但是 运行ning git merge
不会调用这个奇怪的路径,事实上,如果你的存储库不在一个很好的 ready-to-start-a-merge状态。
2这是重命名检测的用武之地。如果您在提交中重命名了某个文件,是 :1:path/to/file.ext
现在可能是 :2:path/different/file.ext
或 :2:path/to/different.ext
。这些文件被检测为同一个文件,尽管它现在有两个不同的名称。这里有一个小缺陷,因为在合并的整个过程中插槽没有链接在一起。例如,如果合并因冲突而停止,则很难恢复 2:path/to/different.ext
与 :1:path/to/file.ext
一致的事实。 Git 打印 信息,因此它可能仍然在 window 中,您可以在笔记本电脑或其他任何设备上看到,但不会记录在其他地方。
脚注:more-tangled 图表
这是一个 b运行ch 的示例图,其中包含一些重复的合并:
...--A--B--C--D--G--H--K <-- branch1
\ \ \
E-----F--I--J--L <-- branch2
这里,F
和J
都是合并提交,有两个parent:F
的两个parent是E
和D
,J
的两个parent是I
和H
。如果您 运行 git checkout branch2; git merge branch1
,您将尝试进行新的合并提交 M
,其第一个 parent 是 L
,第二个是 K
。这里的合并基础是提交 H
因为从 K
开始并向后工作,我们得到 H
,而从 L
开始并向后工作,我们得到 J
然后——可以说是同时——到 I
和 H
,因为我们已经到达顶部的 H
,这就是合并基础。
请注意,合并基础计算是对称的。如果我们 git checkout branch1 && git merge branch2
,Git 仍然选择 H
作为合并基础。但是,如果合并基础不明显,您可以 运行:
git merge-base --all branch1 branch2
这将产生 "best merge base" 的所有候选人。
理想情况下,只有一个。然而,历史看起来像这样:
...--o--o---A--... <-- branch1
\ /
X
/ \
...--o--o---B--... <-- branch2
其中 A
和 B
都是 "equally close" 到两个 b运行ch 提示的合并提交,有 两个 合并基地。这是通过从 b运行ch1 合并到 b运行ch2 并立即进行合并,使用 git merge --no-ff
,从 b运行ch2 到 b运行ch1,有时称为 criss-cross 合并。
在这个模棱两可的合并基本案例中,没有单一的最佳候选者。由于合并通常是对称的,因此提交 A
和 B
的 contents 可能是相同的。在那种情况下,选择 A
和 B
Git 中的哪一个都无关紧要。但是如果内容不一样,也无所谓,这就是Git的递归合并的用武之地。当有多个合并基础时,Git会先,作为内部合并,合并两个合并基础(使用 他们的 最佳共同祖先,无论是什么)并根据结果进行新的提交。 Git 将使用该新提交的内容作为外部合并的合并基础。
-s resolve
合并策略(不是默认的)选择两者之一,A
或 B
,在 apparent-random(真的,只是更方便的算法)。如果 A
和 B
具有相同的内容,则可以正常工作;如果不是,递归 "merge A and B first" 的内部合并可能会 产生更好的结果。如果递归合并有冲突,它会产生一些混乱。 (通常明智的做法是避免 criss-cross 合并。)