git 命令后面会发生什么"git merge pr --squash"

  • 本文关键字:git merge --squash pr 命令 git
  • 更新时间 :
  • 英文 :


假设你有一个主分支,你想将你的功能分支合并到一个合并--squash,如果你将拉取请求设置为壁球合并,就会发生。

据我了解,在"高级"解释中,功能分支将通过检查功能分支头部的临时分支来复制。现在,在这个临时功能分支上,所有提交都将以交互方式重新定位,因此与祖先提交不同的所有提交都将压缩到此临时功能分支上。

然后,该临时功能分支将重新定位到master上,因此之后主节点可以快进合并到新的sqaushed提交,并且临时功能分支将被删除。

将包含功能分支上的所有更改的新提交保留为主分支,但与该功能分支上的最后一个提交没有"合并连接/关系"。

通常在拉取请求中,源分支(在本例中为功能分支)也将被删除。如果不这样做,您仍将拥有一个未合并的功能分支,其状态与拉取请求之前的状态相同。

我的"高层次"理解是否正确,还是壁球融合的过程在幕后有所不同?

提前感谢!

你把几个必须分开的概念混合在一起。 特别是,拉取请求是 GitHub(或其他 Web 服务提供商)的功能。 这不是Git做的事情。1同时git merge是一个 Git 命令行命令,它在您自己的计算机(笔记本电脑或其他计算机)上您自己的存储库中运行。 在使用 GitHub 拉取请求时,您(或至少同意该请求的任何人)可能会操纵某些浏览器按钮来执行"合并"、"变基和合并"或"挤压和合并",这些按钮会在 GitHub 上托管的存储库中进行更改。 这些是相关的,但在几个重要方面有所不同,最大的一个是谁实际拥有存储库。 一旦你拥有多个 Git 存储库,重要的是要记住哪个仓库具有哪些提交指向哪个分支名称,因为你分支名称是你的,他们的是他们的:它们不需要彼此有任何关系,即使它们都被称为mastertopicfeature/short或其他什么。


1Git 有一个git request-pull命令,该命令生成(但不发送)电子邮件。 这显然与 GitHub 样式的拉取请求不同。


合并这个词,在 Git 中,是名词又是动词

gitglossary 以这种方式定义合并

作为谓词:将另一个分支的内容(可能来自外部存储库)引入当前分支。如果合并的分支来自不同的存储库,则首先获取远程分支,然后将结果合并到当前分支中。这种提取和合并操作的组合称为拉取。合并由一个自动过程执行,该过程标识自分支分叉以来所做的更改,然后将所有这些更改一起应用。如果更改发生冲突,可能需要手动干预才能完成合并。

作为名词:除非是快进,否则成功的合并会导致创建一个表示合并结果的新提交,并将合并分支的提示作为父级。此提交称为"合并提交",有时简称为"合并"。

这两段包含了很多内容,我不喜欢将"pull"的定义楔入动词合并的定义中——特别是因为可以告诉git pull运行git rebase而不是git merge作为其第二个 Git 命令——但动词/名词的区别就在这里。 当你执行操作时(动词作为合并部分),你通常会得到名词或形容词,即合并提交,作为结果。

考虑到这一点,让我们看一下实际合并的剖析

忽略git pull(它只是为你运行git merge,除非你另有说明),你调用主要的合并为谓词操作的方式是运行:

git merge <thing-to-specify-a-commit>

从命令行。 提交说明符参数可以是分支名称、标记名称、原始提交哈希 ID 或任何允许 Git 查找特定提交的内容。

在 Git 中,提交以图的形式存在,特别是有向无环或 DAG。 每个提交都由其自己唯一的哈希 ID 标识 - 哈希 ID 本质上是提交的真实名称,并且这个特定名称在任何地方的每个 Git 存储库中都用于表示一个特定的提交,如果该提交在该 Git存储库中。 同时,每个提交都包含其父级的实际哈希 ID,作为其元数据的一部分。

在数学上,图的定义是G = (V,E),其中V是一组顶点(或节点),E是连接这些节点的一组边。 在 Git 中,节点是提交(由其哈希 ID 标识),边缘是单向链接或,从子提交指向其父提交。 这些单向链接让 Git 四处移动,从最孩子提交的提交开始。 这为我们提供了一系列绘制图形的方法。 对于 StackOverflow 帖子,我喜欢的方式是像这样写下来,并在右侧添加较新的提交:

... <-F <-G <-H

在这里,每个字母代表一个实际的哈希ID,来自字母的箭头是边/弧:如果H是提交,它持有其父G的哈希ID,所以H指向GG保存F的哈希 ID,所以G指向F,依此类推。 这些箭头都是向后移动的 — 典型的 DAG 具有从父级子项箭头,但 Git 更喜欢向后工作(出于各种充分的理由)。阿拉伯数字不过,为了我们自己的方便,我们可以在没有任何箭头方向的情况下开始绘制它们,并且只记住 Git 是倒退的:

...--F--G--H

在向前方向上 - Git 实际上没有这样做 - 提交F有一个子GG有一个子H。 从这里开始,让我们H获得两个孩子,每个孩子都有自己的一个孩子:

I--J
/
...--F--G--H

K--L

为了让 Git找到这些提交,它需要一些分支名称。 分支名称包含分支中最后一个提交的实际哈希 ID——这意味着每次我们向分支添加新提交时,与名称关联的值都会更改——但现在让我们绘制它:

I--J   <-- br1
/
...--F--G--H

K--L   <-- br2

为了进行合并(运行动词类型的合并),我们将选择一个分支进行签出,例如br1,然后在另一个分支上运行git merge

git checkout br1
git merge br2

这将启动合并为动词的过程。 如果一切顺利,它将进行合并提交,这只是至少有两个父级的提交:3

I--J
/    
...--F--G--H      M   <-- br1 (HEAD)
    /
K--L   <-- br2

这个新的合并提交M被添加为当前分支br1上的最后一次提交,因此名称br1现在指向新的合并提交M

Git 实际上使用三个输入计算合并提交M的内容(合并文件):两个提示提交(此处、JL)和合并基提交,它被定义为其他两个提交的最佳共同祖先。 在这种特殊情况下,最好的共同祖先很容易看到:它是提交H你可以将 Git 处理此问题的方式视为:

  • 运行git diff --find-renameshash-of-Hhash-of-J以查看我们更改了哪些内容;
  • 运行git diff --find-renameshash-of-Hhash-of-L以查看它们更改了哪些内容;
  • 合并
  • 这两个差异输出,将合并的更改应用于提交H中的文件。

忽略冲突解决的所有机制,以及git merge可以执行的各种特殊非合并情况,这确实是任何合并的关键。Git 找到公共起始点提交,进行两组差异,将它们组合在一起,并将组合的差异应用于公共起点快照。这将形成合并结果的快照。


2特别是,主 Git 对象数据库中的所有内容(提交和文件,以及各种粘附对象)都将永久冻结。 Git 需要这个来使其哈希 ID 工作。 提交在创建时知道其父级或父级:其哈希 ID 已存在。提交还不知道其子项。 承诺刚刚诞生! 它的记忆现在被冻结了。 最终,后来,它有了第一个孩子。 父级无法了解其子项的哈希 ID,因为提交已全部冻结。 如果父母得到第二个孩子、第三个孩子等等,情况也是如此。 孩子认识他们的父母,但父母不了解他们的孩子。

3Git 将具有三个或更多父项的合并提交称为章鱼合并。 章鱼合并没有什么是一系列普通合并不能做的——事实上,情况正好相反:你可以用普通合并做一些 Git 拒绝作为单个章鱼合并做的事情。 章鱼合并比常规合并的事实在某种程度上很有用,当你检查一个新的存储库时,你可以合理地保证这个合并不会引入秘密更改,也没有任何冲突解决方案。 但是其他只允许提交对合并的版本控制系统与 Git 一样强大

4从技术上讲,合并基是两个提交的最低共同祖先,使用 LCA 算法的广义形式。 LCA 在树上得到了很好的定义 — 我上面显示的图形片段实际上是树而不是 DAG——但在 DAG 中可能有多个 LCA。 在这个特定的答案中,我不打算讨论 Git 如何处理多 LCA 情况。


壁球合并现在微不足道

一旦你理解了上述所有内容,git merge --squash就变得非常简单。 Git 会为真正的合并做所有事情,然后在最后,它进行一个有一个父级而不是两个父级的提交。5

也就是说,我们从:

I--J   <-- br1
/
...--F--G--H

K--L   <-- br2

并运行git checkout br1; git merge --squash br2. Git 找到合并基础H,执行两个差异,合并差异,并将其应用于H的内容。 然后 Git 制作——好吧,让你制造;请参阅脚注 5 — 新的合并提交,但 Git 不是与父JLM,而是使用一个父级进行提交,我们可能希望用一个父级调用S(用于 squash),J

I--J--S   <-- br1
/
...--F--G--H

K--L   <-- br2

如果 Git 进行了合并M——事实上我们仍然可以进行M哈希不会匹配,但MS快照会匹配。 您可以通过制作M来确认这一点:

git checkout -b experiment <hash-of-J>
git merge br2

这导致:

I--J--S   <-- br1
/    
...--F--G--H      M   <-- experiment (HEAD)
    /
K--L   <-- br2

MS的内容将匹配:

git diff br1 experiment

什么都不会显示。


5当你运行git merge --squash时,Git 会让你自己运行git commit--squash标志打开--no-commit标志。 这是古代版本的 Git 的遗留物,--squash只是在运行git commit之前退出git merge- 真正的合并实际上在最后调用git commit,除非您使用--no-commit或合并有冲突。

最新更新