在Git的cherry-pick或rebase合并冲突中,如何确定BASE(也称为“祖先”)、LOCAL和REMOTE?

39
在正常的Git合并冲突中,涉及到三个文件版本的三向合并大致如下:
  • LOCAL: 我的分支中的版本
  • REMOTE: 其他分支中的版本
  • BASE: 两个分支共同祖先的版本(特别是,我的分支HEAD和其他分支HEAD的共同祖先)
当Git cherry-pick生成合并冲突时,实际上没有共同祖先,所以这些内容是如何确定的?同样的问题也可以用于rebase。
1个回答

50

挑选

除非我自己误导了自己,否则如果您执行 "git cherry-pick <commit C>",则会得到以下结果:

  • LOCAL:您正在合并的提交(即分支的 HEAD)
  • REMOTE:您正在挑选的提交(即 <commit C>)
  • BASE:您正在挑选的提交的父提交(即 C^,即 C 的父提交)

如果不清楚为什么 BASE 应该是 C^,请参阅下面的“why”部分。

同时,让我们举一个例子,并查看在挑选过程中 BASE 可能是,但通常不是常见祖先。假设提交图像如下:

E <-- master
|
D 
| C <-- foo_feature(*)
|/
B
|
A

你现在在foo_feature分支(因此有星号)。如果你执行命令“git cherry-pick <commit D>”,那么该cherry-pick的BASE将是提交B,它是C和D的共同祖先。(C将是LOCAL,D将是REMOTE。)然而,如果你改为执行“git cherry-pick <commit E>”,那么BASE将是提交D。(C将是LOCAL,E将是REMOTE。)

变基

为了提供背景信息,变基大致上是迭代式地进行 Cherry-pick。特别是,在主分支上重置topic分支(即“git checkout topic; git rebase master”)意味着大约执行以下操作:

git checkout master # switch to master's HEAD commit
git checkout -b topic_rebased # create new branch rooted there
for each commit C in master..topic # for each topic commit not already in master...
    git cherry-pick C # bring it over to the new branch
finally, forget what "topic" used to mean and now defined "topic" as the HEAD of topic_rebased.
在此过程中应用的标签是常规 Cherry-pick 规则的扩展:
- LOCAL:您在其上执行 Cherry-pick 的提交 - 这是新 topic_rebased 分支的 HEAD - 仅对于第一个提交,它将与 master 的 HEAD 相同 - REMOTE:要进行 Cherry-pick 的提交(即 <commit C>) - BASE:要进行 Cherry-pick 的提交的父提交(C^,即 C 的父提交)
这意味着需要注意 LOCAL vs. REMOTE,如果想要避免混淆:
“即使您在启动 rebase 时正在 topic 分支上,LOCAL 在重新排序期间也永远不会引用 topic 分支上的提交。相反,LOCAL 总是指正在创建的新分支(topic_rebased)上的提交。”
如果没有牢记这一点,那么在进行复杂的合并时,可能会自问:“等等,为什么它说这些是本地更改?我发誓它们是在主分支而不是我的分支上进行的更改。”
具体来说,以下是一个示例:
D <-- foo_feature(*)
|
| C <-- master
B |
|/
|
A

我们目前在foo_feature分支上(由“*”表示)。如果运行“git rebase master”,则rebase将分两步进行:

首先,B的更改将被重放在C之上。此期间,C为LOCAL,B为REMOTE,A为BASE。请注意,A是B和C的实际共同祖先。完成第一步后,您将获得一个大致如下的图形:

   B' <-- foo_feature
D  |
|  |
|  C <-- master
B /
|/
|
A

(在现实生活中,B和D可能已经在这个阶段被剪枝了,但为了更容易地发现任何可能的共同祖先,我将它们留在这里。)

其次,D所做的更改将会在B'的基础上重新应用。此时,B'是LOCAL,D是REMOTE,而B是BASE。请注意,B并不是任何内容的相关共同祖先。(例如,它不是当前LOCAL和REMOTE(即B'和D)的共同祖先,也不是原始分支头C和D的共同祖先)。在此步骤之后,您将得到一个类似于以下的分支:

   D' <-- foo_feature
   |
   B'
D  |
|  |
|  C <-- master
B /
|/
|
A

为了完整起见,需要注意的是,在变基结束时,图表中的 B 和 D 被删除,结果如下:

D' <-- foo_feature
|
B'
|
C <-- master
|
A

为什么 BASE 被定义为它现在的样子?

如上所述,无论是 cherry-pick 还是 rebase,BASE 都是要被拉入的提交 C 的父提交 (C^)。在一般情况下,C^ 并不是一个共同的祖先,那么为什么称之为 BASE? (在普通合并中,BASE 是一个共同的祖先。而 git 在合并中成功的部分原因是能够找到一个好的共同祖先。)

基本上,这样做是为了通过常规的三方合并算法实现“补丁”功能。特别是你会得到以下“补丁”的属性:

  • 如果 <commit C> 没有修改文件的某个给定区域,则你的分支版本中该区域的版本将优先。(也就是说,不需要更改的区域不会被打补丁。)
  • 如果 <commit C> 修改了文件的某个给定区域,而你的分支没有修改该区域,则来自 <commit x> 的该区域版本将优先。(也就是说,需要更改的区域将被打补丁。)
  • 如果 <commit C> 修改了文件的某个给定区域,而你的分支也修改了该区域,则会产生合并冲突。

1
@Jefromi,你认为我应该将暂定答案作为原始问题的一部分包含在内吗?(我认为这将有助于作为独立的答案,因为这样可以与原始问题分开讨论/投票/反对等。)我想另一种选择是根本不包括它们。 - Chris
我个人会等待看是否能够快速发布更明确的答案。 - Cascabel
1
我认为你做对了。顺便说一下,这些的正常名称分别是“我们的”(合并/应用版本),“他们的”(合并/应用版本)和“基础”(共同祖先)。 - Cascabel
1
@Jefromi 好的,Stack Overflow 的提示很不错。至于我们/他们/基础,我在其他 git 上下文中看到过这个术语。我认为 LOCAL/REMOTE/BASE 是 git 的合并工具(manpage 在此)的术语,在我的设置中,这些术语会出现在传递给 kdiff3 的文件名中。 - Chris
天啊,我一直以为“BASE”是共同祖先,这段时间我一直非常非常困惑。*扇自己一个耳光* - Qwerty
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接