git-merge 和 git-rebase 如何处理从另一个主题分支中分叉出来的主题分支?

How git-merge and git-rebase behave with a topic branch forked off of another topic branch?

让我们假设存储库如下所示:

            master
               |
A--B--C--K--L--M
       \
        E-F-G
            |\
            | H-I-J
          topicA  |
                topicB

git merge topicBmaster 时会做什么?它会仅应用提交 HIJ 吗?或 topicA?

初始分叉点的所有提交

git rebase master topicA会做什么?会不会导致这样:

            master
               |
A--B--C--K--L--M
                \
                 E'-F'-G'
                       |\
                       | H'-I'-J'
                     topicA    |
                             topicB

或类似的东西?

            master
               |
A--B--C--K--L--M
       \        \
        \        E'-F'-G'
         \             |
          \          topicA
           \          
            E-F-G-H-I-J
                      |
                    topicB

除了将分支名称解析为提交 ID 之外,merge 和 rebase 都不关心任何分支标签指向的位置。他们只看提交图。

合并

我认为 git merge 的工作方式更容易理解。除了为默认合并提交消息保存分支名称外,git merge 仅使用 git rev-parse 查找提交 ID。

对于您的第一个问题,假设 git merge topicB 在分支 master 上,git 找到三个提交 ID:

  • HEAD 解析为提交 M(分支 master 上的最尖端提交;这也是您在 运行 [=18 时看到的提交 ID =]).
  • topicB 解析为提交 J(分支 topicB 上的最尖端提交;这也是您在 运行 [=22 时看到的提交 ID =]).
  • 这两个提交的合并基础,(仅)由提交图确定。在这种情况下,即提交 C.

找到合并基础后,git 然后 运行 两个 git diff:合并基础和 HEAD 之间的一个提交(看看 "your branch" 做了),合并基础和 topicB 之间的一个提交(看看 "their branch" 做了什么)。它结合了两组更改,如果没有冲突(或者如果它可以通过 git rerere 解决它们),则创建一个新提交,其两个父项是 - 按此顺序 - 当前 HEAD 提交,以及 topicB 提交。使新提交像往常一样更新当前分支,因此 HEAD 现在指向新提交(或更准确地说,HEAD 仍然指向 master,并且 master 指向到新的提交)。

本题:

Would it apply commits H, I, and J only? or all the commits from the initial fork point of topicA?

暗示了对 git 合并算法的根本误解:git 没有查看 任何 中间提交。它只查看此处标识的三个提交:在本例中为 MJC。 Git 可以 "cheat" 像这样,因为就提交的源代码树内容而言,每个提交都是完全独立的。

变基

Rebase 更复杂(你在上面的第二个猜测中是正确的,但让我们 运行 无论如何)。

它仍然使用相同的 "resolve branch names to IDs via git rev-parse" 技巧,但它使用了更复杂的 git rev-list 变体。 git rev-list 所做的是在提交图上进行计算集操作(一旦真正进入图本身,再次忽略任何标签)。

给定 git rebase master topicA,我们(嗯, 无论如何 :-))首先必须检查 the rebase documentation,我们发现 master 被视为 参数, topicA 被视为 参数,并且它所做的一切最后一个参数是 运行 git checkout <em><branch></em> 首先,然后继续,就好像你没有提供它一样。所以在心理上我们可以假设你开始做 git checkout topicA,然后 运行 git rebase master 参数是 master 并且 HEAD 指的是分支 topicA 所以它标识提交 G.

在过去,在 git rebase 拥有 --fork-point 之前,更容易确定接下来会发生什么。今天,根据文档,您必须确定 --fork-point 是否已启用。因为您 did 给出了一个实际的 参数,所以默认情况下它是禁用的:只有当您在命令中给出 --fork-point 时它才会启用线;你没有;因此我们不需要深入研究 --fork-point 的奥秘(哇,耶!:-))。所以,这里发生的是 rebase 本质上是 运行s git rev-list master..HEAD 来获取要复制的提交列表。这列出了提交 EFG,因为这些是 HEAD 可访问但 master.[=72= 无法访问的集合中的提交]

rebase 进程然后在 --onto 参数给出的提交(如果有)或 < 给出的提交处分离 HEAD上游> 参数。那是 master 所以这是提交 M。从这里开始,rebase 本质上只是挑选它之前找到的每个提交,这会为您提供提交 E'F'G'。如果所有这些都正常工作,git 的 rebase 的最后一步是将最初的当前分支(topicA,再次)指向最后一次提交,G'。其他标签没有变化,所以你得到你画的最终图表。