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
)。现在 merge
、merge --squash
和 rebase
这三个都会给你一张图片,最后我们可能会这样画:
...--o--*--o--o--A--B' <-- ???
\ ?
B????????? <-- ???
和新提交B'
的内容,即最终快照,在所有三种情况下都是相同的。但是,对于 git merge
,新提交将在分支 mainbr
上,并将指向提交 A
和 B
,sidebr
指向 [=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
。那为什么会自动停止呢?
试图理解为什么命令
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
)。现在 merge
、merge --squash
和 rebase
这三个都会给你一张图片,最后我们可能会这样画:
...--o--*--o--o--A--B' <-- ???
\ ?
B????????? <-- ???
和新提交B'
的内容,即最终快照,在所有三种情况下都是相同的。但是,对于 git merge
,新提交将在分支 mainbr
上,并将指向提交 A
和 B
,sidebr
指向 [=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
。那为什么会自动停止呢?