假设我用git rebase -i
进行了一次交互式重基。如果出现冲突,我可能会遇到合并冲突,并被要求进行3路合并。使用meld
,我看到了三个窗口:LOCAL
(左)、???
(中)和REMOTE
(右)。这里所说的???
只是指meld
没有提供一些特殊的名称来附加到文件中。
在正常的合并过程中,这是有意义的,因为中间是共同的祖先,并且您正在合并该祖先的本地和远程更改。然而,在交互式重新基准期间,情况似乎并非如此——不清楚每个文件代表什么。
三向合并中的这些文件在交互重定基准期间分别代表什么?编辑这些文件时,我的目标是什么?
更新:基于我看到的评论和实验:
- Left(
LOCAL
):在提交-回放序列中,此时文件的本地版本 - Right(
REMOTE
):最初应用当前提交之后的文件状态 - Middle:原始提交序列中权限的父级
因此,我的任务是确定从中间到右边的增量,然后将该增量应用于左边。Middle应该反映在新提交序列中应用当前提交增量之后文件的状态。
请注意,至少在某种程度上,这种配置似乎是熔化物特有的。Git的3向合并行为可能与其他编辑器不同。
中间版本是合并基础,就像git merge
一样。
(名称"other"可能比"remote"更合适,因为没有要求合并的另一端是远程的,而且Mercurial一直使用名称"other",这并不是说Git需要匹配Mercurial,但一些一致性可能很好。请注意,Git在这里也使用名称"ours"one_answers"their",所以我们永远不会从Git获得100%的一致性。:-)
但是等等,怎么会有合并基础呢
总是有合并基。
通常我们甚至不必找到它,因为当将每个补丁作为补丁处理时(不尝试三方合并),它都会干净地应用。但有时补丁不会干净地应用,我们确实不得不回到三方合并。
(顺便说一句,您可以禁用此回退。请参阅git-am文档中的--3way
、--no-3way
和am.threeWay
,尽管由于这些控件最近发生了更改,此处链接的页面已经过期。)
$ git rebase -i
pick aaaaaaa first commit
pick bbbbbbb second commit
pick ccccccc third commit
让我们也画一个提交图,这样我们就可以看到我们从到的基础
A - B - C <-- branch
/
... - o - *
G - H <-- origin/branch
我们将挑选提交A
、B
和C
(A
=aaaaaaa
等),以便最终得到以下结果:
A - B - C [abandoned]
/
... - o - * A' - B' - C' <-- branch
/
G - H <-- origin/branch
让我们仔细看看A
的第一个樱桃。
这将(diffs)A
与其父级(即提交*
)进行比较,并尝试将得到的diff应用于提交H
。
然而,提交H
与提交*
有些偏离。事实上,我们可以在A
和H
之间找到一个合并基,它是。。。提交*
。这实际上是一个相当不错的合并基础,尽管最好Git可以按原样应用补丁,而不必退回到三方合并代码。
因此,当在H
上选取A
时,提交*
是合并的基础。合并完成后,我们得到新的提交A'
。(例如,它的新SHA-1 ID可能是aaaaaa1
。可能不是;我们就叫它A'
。)
现在我们来挑选B
。这使B
与其父级A
发生差异,并尝试将差异应用于A'
。
然而,提交A'
与提交B
有些偏离。事实上,我们可以在B
和A'
之间找到一个合并基,即。。。再次提交*
。不幸的是,这是一个糟糕的合并基地。幸运的是,只有当补丁不能按原样应用时,Git才会依赖它,而且通常情况下它可以。但如果不能,Git将区分编辑Git作弊。(该代码最近在2.6版本中发生了更改,尽管总体策略保持不变。)*
与B
以及*
与A'
,并尝试合并这两个差异。请注意,*
和B
包含了我们在A
中所做的所有更改,但*
和A'
也包含了所有相同的A
更改,所以如果幸运的话,Git会注意到已经包含的更改,并且不会重复它们
当仅用于显示从提交A
到提交B
的更改时,请考虑git diff
的实际输出。这包括一条index
线路:
diff --git a/foo b/foo
index f0b98f8..0ea3286 100644
左边的值是提交A
中文件foo
版本的(缩写)哈希。右边的值是提交B
中文件版本的哈希。
Git只是从左侧散列中伪造一个合并基。换句话说,提交A
中的文件版本成为伪造的合并基础。(Git将--build-fake-ancestor
传递到git apply
。这要求特定的文件blob对象在存储库中,但由于它们在提交A
中,所以它们是在存储库。对于通过电子邮件发送的修补程序,Git使用相同的代码,但blob可能存在,也可能不存在。)
请注意,Git在挑选提交A
时也会这样做,但这次的合并基础文件是提交*
的版本,它实际上是合并基础。
最后,我们精心挑选了C
。这与B
与C
的差异,正如我们上次与A
与B
的差异一样。如果我们能按原样使用补丁,那就太好了;如果没有,我们将返回,再次使用commit 和以前一样,假装*
作为合并基础。这又是一个相当糟糕的合并基地B
中的版本是公共基础。
顺便说一句,这也解释了为什么对于这些rebase,你会一次又一次地看到相同的合并冲突:我们每次都使用相同的合并base。(启用git rerere
会有所帮助。)
合并和重基在这方面是相同的。合并和rebase之间的唯一区别是,有了rebase,历史看起来更好(更线性)。但是,对于即将出现的和你必须解决的冲突,它们是一样的。