我在功能分支中删除了文件,因为我在其他地方重构了其代码。
Develop分支已更改此文件中的代码以修复错误。
我将develop分支合并到我的feature分支中,以便在继续进行该功能时保持其最新状态。
有关文件我遇到了“被我们删除”的冲突。
如何获取对develop分支上的文件所做的更改的区别,以便可以将这些更改重新实现到我的功能分支上的重构代码中?
答案 0 :(得分:2)
您要查看索引槽#1,并将其与索引槽#3的路径进行比较:
git diff :1:path :3:path
您还可以使用git checkout-index
提取各种索引槽版本,然后使用常规文件操作而不是仅使用Git工具进行检查。 git mergetool
程序执行后者,因此,如果您使用git mergetool
,则将具有文件的两个版本。 (我自己从未使用过git mergetool
。)
根据定义,任何合并都有三个输入。我将按倒序列出它们,因为最后一个是此处的关键:
三个输入之一是其提交,位于develop
分支的顶端:您运行了git merge develop
。请记住,每次提交都是所有文件的完整快照。这不是一组更改,它只是一个快照。但是不知何故,Git知道他们更改了一些特定文件。关于什么?
这三个输入之一是您自己当前的提交(也称为HEAD
)。在这种情况下,HEAD
中的某些文件已被删除。但是:Git如何知道您删除了文件?关于什么?
三个输入中的最后一个是合并基础。这就是这两者中的什么。合并基础是(单个/最佳)常见提交,您的分支提示从此开始。
也就是说,如果我们在启动git merge
时绘制提交图,则它看起来像这样-尽管详细信息会有所不同,并且通常很难扫描图来找到{{1 }}和B
(通常很容易找到R
或提交HEAD
):
L
提交 o--...--L <-- our-branch (HEAD)
/
...--A--B
\
o--...--R <-- develop
和A
(以及所有早于A的内容)都在两个分支上,因此是共享的,但是B
是最佳之一,因为它是 last 共享的一个。
为了进行合并,Git将B
与B
进行了比较,以查看我们发生了什么变化,并比较了L
与B
查看他们发生了什么变化。在三个提交中没有更改的文件都是相同的,因此Git使用此类文件的任何版本。对于仅他们进行了更改的文件,Git从提交R
获取该文件的版本。对于仅我们更改过的文件,Git从提交R
中获取该文件的版本。
对于我们两个都更改过的文件,以某种方式(包括“完全删除文件”),Git必须更加努力。现在,重要的是要了解索引(Git生成 new 所提交的内容)在合并期间具有扩展的作用。
通常,每个文件的索引只有一个插槽。这个插槽是带编号的,但它是零编号的插槽,因此您通常不需要做任何特殊的引用即可:您只是告诉Git L
复制文件 git add somefile
从工作树进入索引,使其准备提交。
但是,对于合并情况,在我们和他们都对文件进行处理的情况下,Git需要 3 (每个文件最多 三个)副本。因此,对于这种特殊情况,Git将文件{em> merge base 的版本从提交somefile
放入索引插槽#1。 Git将文件的我们的版本(从提交B
并已在索引插槽#0中移动到索引插槽#2中,并将文件的另一版本从提交L
移入索引插槽#3中。
对于已删除的文件(在这种情况下),Git将插槽2或#3留空,具体取决于谁删除了文件。对于添加/添加冲突(文件在R
中不存在,但在B
和L
中都存在),Git将插槽#1留空。 (不存在删除/删除冲突的事情:如果我们都删除了文件,Git只会删除文件并继续前进。但是有一些重命名的情况比较棘手。)
当合并由于合并冲突而停止时,这些索引槽将始终填充文件的三个版本(在本例中为两个)。因此,您可以检查索引,查看高级(非零)插槽,并了解哪些文件有冲突。包括R
和git status
在内的各种Git工具。
解决冲突后,无论如何,都必须告诉Git清除较高级的插槽,然后将文件的良好副本放入索引插槽#0。最简单的方法是git diff
正确版本的文件。 (如果没有正确的版本(如果应该删除该版本,则可以git add
,将其从所有索引插槽和工作树中删除。通常,如果它不在工作树中,则{{ 1}}还将其从索引中删除,尽管我习惯git rm
-处理应该消失的冲突文件,所以我没有测试git add
是否与删除较高的文件一致阶段条目。如果它不在工作树中,则git rm
从索引中删除它,抱怨它不在工作树中,然后一切都很好。)
这是一个带有一些重复合并的分支的示例图:
git add
此处,git rm
和...--A--B--C--D--G--H--K <-- branch1
\ \ \
E-----F--I--J--L <-- branch2
都是合并提交,有两个父级:F
的两个父级分别为J
和F
,并且两个E
的父级是D
H J
git checkout branch2; git merge branch1 I and
M . If you run
L you'll be attempting to make a new merge commit
K whose first parent is
H and second is
K . The merge base here is commit
H because starting at
L and working backwards, we get to
顶部是J , while starting at
I and working backwards, we get to
H and then—simultaneously, as it were—to both
H`,这是合并基数。
请注意,合并基础计算是对称的。如果我们and
,Git仍会选择, and as we already got to
作为合并基础。如果合并基础不明显,则可以运行:
git checkout branch1 && git merge branch2
这将产生“最佳合并基础”的所有候选。
理想情况下,只有一个。但是,这样的历史记录如下:
H
git merge-base --all branch1 branch2
和...--o--o---A--... <-- branch1
\ /
X
/ \
...--o--o---B--... <-- branch2
都是与两个分支提示“相等接近”的合并提交,具有两个合并基础。这是通过从branch1合并到branch2并立即通过A
从branch2合并到branch1(有时称为纵横交叉合并)进行的。
在这种模棱两可的合并基础案例中,没有一个最佳候选者。由于合并通常是对称的,因此此处的提交B
和git merge --no-ff
的内容可能是相同的。在那种情况下,选择A
和B
Git中的哪一个都不重要。但是,如果内容不同,那确实很重要,这就是Git的递归合并的所在。当有多个合并基础时,Git首先将作为内部合并的两个合并基础(使用他们的最佳共同祖先,无论是什么),然后从结果中进行新的提交。 Git将使用该新提交的内容作为外部合并的合并基础。
A
合并策略(不是默认策略)以明显的随机性选择(实际上,只要算法中更方便),就选择B
或-s resolve
这两者之一。如果A
和B
具有相同的内容,则可以正常工作;如果不是,则来自递归“先合并A和B” 的内部合并可能会产生更好的结果。如果递归合并有冲突,它将产生混乱。 (通常最好避免纵横交错的合并。)