git merge --squash 真的不是 git rebase -squash 吗?

Isn't git merge --squash really git rebase -squash?

试图理解为什么命令

git merge --squash mybranch

不叫

git rebase -squash mybranch

因为看起来它所做的操作更像是变基而不是合并。我的理解是它在提交树中搜索,直到找到当前分支和 mybranch 的公共基础提交。然后它将从该基节点到 mybranch 头部的所有提交重新应用(变基)到当前分支的头部。但是在 index/workspace 中执行此操作,因此它可以作为单个提交应用。完成后没有合并节点,因为在正常合并中显示合并的两个分支。我的理解正确吗?

好吧,合并和变基是根本不同的操作。合并——我指的是创建新合并提交的常规 git merge——确实会通过提交图搜索最近的常见提交:

...--o--*--o--o--A   <-- mainbr
         \
          B--C--D--E   <-- sidebr

在这里,最近的常见提交是 *。然后合并过程将比较(如 git diff)提交 * 与提交 A 以找出 "what we did",并比较 * 与提交 E 找出 "what they did"。然后它进行一个新的单一合并提交,有两个 parents:

...--o--*--o--o--A---M   <-- mainbr
         \          /
          B--C--D--E   <-- sidebr

连接两个历史记录,并结合 "what we did" 和 "what they did",因此比较 *M 得到 "one of each change".

请注意,您无法在此处选择合并基础:Git 弄明白了,仅此而已。

另一方面,Rebase 可以分别被告知要复制哪些提交以及将它们复制到哪里。确实,默认情况下,它会再次定位提交 *1——但随后它会 copies 原始提交,一个接一个,使用 git cherry-pick 或等价物;最后,它将分支 label 移动到指向最后复制的提交。

...--o--*--o--o--A   <-- mainbr
         \        \
          \        B'-C'-D'-E'   <-- sidebr
           \
            B--C--D--E

原始提交链(B-C-D-E,在这种情况下)仍在存储库中,并且仍然可以找到:它们可以通过哈希 ID 找到,并在 sidebr 的 reflog 中找到,如果任何 other 分支或标记名称使它们可以访问,它们仍然可以通过该名称访问。

git merge --squash 所做的只是稍微修改合并过程:不是进行合并提交 M,Git 通过合并 machinery 像往常一样,将合并基础(您无法选择)与当前提交和其他提交进行比较,并将索引和 work-tree 中的更改合并。然后它——没有明显的原因2——停止并让你运行git commit提交结果,当你这样做时,它是一个普通的,non-merge 提交,这样整个 graph-fragment 看起来像这样:

...--o--*--o--o--A--F   <-- mainbr
         \
          B--C--D--E   <-- sidebr

现在,提交F内容——合并产生的快照树——与当我们进行真正的合并时,commit M 的内容,并且——这是真正的关键——它 also 与我们执行 a 时的 commit E' 的内容相同变基。

此外,假设侧分支sidebr上只有一个提交(B)。现在 mergemerge --squashrebase 这三个都会给你一张图片,最后我们可能会这样画:

...--o--*--o--o--A--B'   <-- ???
         \          ?
          B?????????   <-- ???

和新提交B'内容,即最终快照,在所有三种情况下都是相同的。但是,对于 git merge,新提交将在分支 mainbr 上,并将指向提交 ABsidebr 指向 [=35] =];对于 git merge --squash,新提交将在 mainbr 上,并且只会指向 A;对于 git rebase,新提交将在 sidebr 上,根本没有明显指向 B,我们应该将其绘制为:

...--o--*--o--o--A   <-- mainbr
         \        \
          B        B'  <-- sidebr

因为 mainbr 将继续指向提交 A

最后,这看起来更像是合并而不是变基。 (但是,如果它根本不被称为 "squash merge",我会更高兴。)


1Git 找到 * 的方法有些不同:它实际上不是单个提交,而是 (通常)非常大的提交集,即所有可以从 <upstream> 参数到 git rebase 的提交。 (令人困惑的是,合并基础也可以是一组提交,但它是一个更受限制的集合。最好不要深入研究图论。:-))

2如果我们想要它停止,我们可以使用--no-commit就像我们对常规一样,non-squashgit merge。那为什么会自动停止呢?