说您有一个主分支,要将功能分支合并到其中的master分支,如果您将拉取请求设置为squash合并,则将发生合并--squash。
据我了解,在“高级”解释中,可以通过在功能分支头上签出一个临时分支来复制功能分支。现在,到该临时特征分支上的所有提交都将以交互方式重新建立基础,因此所有与祖先提交不同的提交都将被压缩到该临时特征分支上。
然后,将temp功能分支重新建立到master上,以便master可以快速向前合并到该新的已提交的提交,并删除temp feature分支。
将新的提交保留在master分支中,该提交包括功能分支上的所有更改,但与该功能分支上的最后提交没有“合并连接/关系”。
通常,在拉取请求中,源分支(在这种情况下为功能分支)也将被删除。如果不这样做,您仍将拥有一个未合并的功能分支,其状态与拉取请求之前的状态相同。
我的“高水平”理解是否正确,或者壁球合并的过程在幕后不同?
谢谢!
答案 0 :(得分:1)
您正在将必须分开的几个概念混合在一起。特别是 pull request 是GitHub(或其他Web服务提供商)功能。这不是 Git 所做的事情。 1 同时git merge
是一个Git命令行命令,可在位于您自己的 存储库中运行您自己的计算机(笔记本电脑或其他)。在使用GitHub拉取请求时,您-或至少同意请求的人-可以操纵某些浏览器按钮进行“合并”,“重新合并和合并”或“压缩并合并”,以及这些在GitHub上托管的存储库中进行更改。这些是相关的,但是在几个重要方面有所不同,最大的是实际上拥有存储库的 。一旦拥有多个Git存储库,请务必牢记哪个存储库具有哪个分支名称指向的提交,因为您的分支名称是您的 ,并且他们是他们的:他们彼此之间没有任何关系,即使他们都被称为master
或topic
或feature/short
或其他任何事物。
1 Git有一个git request-pull
命令,该命令生成(但不发送)电子邮件。显然,这与GitHub风格的拉取请求不同。
gitglossary通过这种方式定义合并:
作为动词:将另一个branch的内容(可能来自外部repository)带入当前分支。如果合并的分支来自其他存储库,则首先通过fetching远程分支,然后将结果合并到当前分支中来完成此操作。提取和合并操作的这种组合称为pull。合并是通过自动过程执行的,该过程识别自分支分支以来所做的更改,然后将所有这些更改一起应用。如果更改冲突,则可能需要手动干预才能完成合并。
作为名词:除非它是fast-forward,否则成功的合并会导致创建代表合并结果的新commit,并且具有parents的技巧合并后的branches。此提交称为“合并提交”,有时也称为“合并”。
这两段中有很多内容,我不喜欢将“ pull” 的定义混入动词合并的定义中-特别是因为git pull
可以被告知运行git rebase
而不是git merge
作为其第二个Git命令-但动词/名词的区别在这里。当您执行动作(作为合并的动词部分)时,通常会得到名词或形容词,即 merge commit 作为结果。
忽略git pull
(除非您另行告知,否则git merge
将为您运行git merge <thing-to-specify-a-commit>
),调用主“作为一个动词的合并”操作的方式是运行:
... <-F <-G <-H
从命令行。 commit specifier参数可以是分支名称,标记名称,原始提交哈希ID或任何允许Git查找特定提交的内容。
Git中的提交以 graph 的形式存在,特别是有向无环图或DAG。每个提交都由其自己唯一的哈希ID标识-哈希ID本质上是提交的真实名称,并且在每个每个 Git存储库中,每个地方都使用这个特定名称来表示如果该提交完全在该Git存储库中,则提交。同时,每个提交都包含其父级的实际哈希ID作为其元数据的一部分。
数学上,图的定义为 G =(V,E),其中 V 是一组顶点(或节点),而 E 是连接这些节点的一组边。在Git中,节点是提交(由其哈希ID标识),边缘是单向链接或 arcs ,从子提交指向其父节点。这些单向链接使Git从最子提交开始就走动。这给了我们绘制图形的许多方法。对于StackOverflow发布,我喜欢的方式是这样写下来,并向右添加新的提交:
H
每个字母代表一个实际的哈希ID,字母中的箭头为边/圆弧:如果G
是一个提交,它将保存其父H
的哈希ID。 ,因此G
指向 G
。 F
拥有G
的哈希ID,因此F
指向...--F--G--H
,依此类推。这些箭头都向后移动-典型的DAG的箭头是从父母 到孩子的箭头,但是Git倾向于向后工作(出于各种良好的原因)。 > 2 但是,为了我们自己的方便,我们可以开始绘制它们而没有任何箭头方向,并且记住Git向后做事:
F
在向前的方向上(一个Git实际上并没有做),提交G
有一个孩子G
,而H
有一个孩子H
。从这里开始,让 I--J
/
...--F--G--H
\
K--L
获得两个 个孩子,每个孩子都有一个自己的孩子:
I--J <-- br1
/
...--F--G--H
\
K--L <-- br2
要让Git 查找这些提交,它需要一些分支名称。分支 names 保留分支中 last 提交的实际哈希ID-这意味着每次我们向该分支添加 new 提交,与名称相关联的值会发生变化-但我们现在就来绘制它:
br1
为了进行合并(要运行动词类型的合并),我们将选择一个分支进行检出,例如git merge
,然后在另一个分支上运行git checkout br1
git merge br2
:< / p>
I--J
/ \
...--F--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
这将启动“动词合并”过程。如果一切顺利,它将进行 merge commit ,这仅仅是一个至少有两个父级的提交: 3
M
此新的合并提交br1
被添加为当前分支br1
上的 last 提交,因此 name {{1} }现在指向新的合并提交M
。
Git实际上使用三个输入来计算合并提交M
的内容(合并文件):两个提示提交(此处为{{1}) }和J
-合并基础提交,它被定义为其他两个提交的最佳共同祖先。在这种情况下,最好的共同祖先很容易看到:它是L
。 4 您可以想到Git处理此问题的方式为:
H
来查看我们 所做的更改; git diff --find-renames hash-of-H hash-of-J
来查看他们的更改; git diff --find-renames hash-of-H hash-of-L
中的文件。忽略冲突解决的所有机制以及H
可以执行的各种特殊非合并情况,这实际上是任何合并的关键。 Git找到公共起点提交,制作两组差异,将它们组合,然后将组合的差异应用于共同起点快照。这形成合并结果的快照。
2 特别是,主Git对象数据库中的所有内容(提交和文件以及各种粘合对象)始终被冻结。 Git需要它来使其哈希ID起作用。提交创建时便知道其父母或父母:其哈希ID已存在。提交尚不知道。提交刚刚诞生!现在它的内存已冻结。最终,以后,它获得了第一个孩子。父级无法了解其子级的哈希ID,因为该提交已被冻结所有时间。如果父母何时生第二个孩子,第三个孩子等等,情况也是如此。孩子们认识他们的父母,但父母不认识他们的孩子。
3 Git将具有三个或更多父项的合并提交称为章鱼合并。章鱼合并是您无法做的,而一系列普通合并是您做不到的—实际上,事实恰恰相反:您可以使用普通合并来做一些事情,而Git会拒绝做为单个章鱼合并。章鱼合并比常规合并更“弱”的事实在您检查新创建的存储库时会有所帮助,因为可以合理地确定此合并不会引入秘密变化,也没有任何冲突解决方案。但是其他仅允许提交对合并的版本控制系统与Git一样具有强大的功能。
4 从技术上讲,使用LCA算法的通用形式,合并基础是两次提交中的最低公共祖先。 LCA在树上定义明确-我上面显示的图形片段实际上是一棵树而不是DAG-但是在DAG中可能有多个LCA。在这个特定的答案中,我将不讨论Git如何处理多重LCA情况。
一旦您了解了以上所有内容,git merge
就变得非常简单。 Git会做它为真正的合并所做的一切,然后最后,它进行的提交具有一个父而不是两个 。 5
也就是说,我们开始于:
git merge --squash
并运行 I--J <-- br1
/
...--F--G--H
\
K--L <-- br2
。 Git找到合并基础git checkout br1; git merge --squash br2
,执行两个比较,组合比较,然后将其应用于H
的内容。然后,Git做出了-好,使您做出了;参见脚注5-新的合并提交,但是Git进行了一次提交,我们可能希望将其与H
和M
一起使用,将{ 一个父母,J
:
L
Had Git合并了S
,实际上我们仍然可以合并J
,哈希不匹配,但是快照 I--J--S <-- br1
/
...--F--G--H
\
K--L <-- br2
和M
中的一个。您可以通过输入M
来确认这一点:
M
结果为:
S
M
和git checkout -b experiment <hash-of-J>
git merge br2
的内容将匹配:
I--J--S <-- br1
/ \
...--F--G--H M <-- experiment (HEAD)
\ /
K--L <-- br2
什么也不会显示。
5 运行M
时,Git使您自己运行S
。 git diff br1 experiment
标志打开git merge --squash
标志。这是Git的一个古老版本的遗留物,其中git commit
只是使--squash
在运行--no-commit
之前退出 —真正的合并实际上在以下位置调用--squash
最后,除非您使用git merge
或合并有冲突。