`git merge --squash`确实是合并操作吗?

时间:2018-04-20 03:38:31

标签: git git-merge

以下是merge process with different parameters described in graph。(由于格式兼容性,无法直接插入图表)

如我们所知,如果我们在git merge --no-ff target分支上使用mastertarget branch合并到master,那么它将生成一个包含两个前辈的新节点,我们可以看到使用git log打印的两个分支。

但是,如果我们使用git merge --squash + git commit,那么它只会生成一个具有一个前任的新节点,如果我们使用git log,则只打印当前分支。

从我的观点来看,作为合并的结果,新节点应该有两个前辈(--ff是一个特殊情况)。一个是current branch,另一个是目标分支是从那里合并。虽然--squash字面上是一个合并操作,但它实际上只是当前分支上的commit操作,对吗?

更重要的是,在--squash操作之后,当我checkouttarget branch时,该分支上的commits实际上并未被压扁。如果我使用{{1}在git log上,提交历史记录仍在那里而不是被压扁。

1 个答案:

答案 0 :(得分:2)

我想描述的方式是,在Git中,单词 merge 本身有两种形式。一个是动词 to merge ,它描述了一个动作。另一个使用单词作为形容词,在短语合并提交中,或作为名词,在短语 a merge 中,这只是合并提交(因此无论是用作形容词还是名词,它都扮演相同的角色)。

合并为动词

要合并的的动词形式描述了Git:

的过程
  • 选择一个合并库:一些特定的提交 1 用作合并过程的基础;
  • 选择两个提交 2 我们可以指定 L R left right < / em>,或本地远程,或--ours--theirs;
  • 生成一对(有时至少部分为虚拟)git diff个输出:一个将所选基数与 L 进行比较,另一个将所选基数与 R进行比较; < / em>的
  • 组合这些差异以产生合并结果,偶尔因合并冲突而失败。

失败时,冲突存储在Git的索引中,使用阶段编号系统:存储在索引阶段插槽1中的文件是来自合并库的文件,阶段插槽2中的文件来自 L --ours提交的那些,阶段插槽3中的文件来自 R --theirs提交。成功时,合并结果存储在Git的索引中,只是每个文件都在更正常的slot-zero条目中。如果您使用git mergetool,则会从这些阶段1-3条目中提取冲突的文件。文件的工作树副本在很多方面只是副作用:Git的提交都是从索引构建的,而不是从工作树构建的。

如果没有失败并且您没有说--no-commit,最终或立即离开 - 此合并的结果可能会被提交。如果失败并且索引中仍然存在混乱,则可以中止执行此合并动作过程中的任何操作。如果您确实说--no-commit,您同样可以中止而不是提交。但通常会有最终提交。这让我们看到了 merge 这个词的另一个含义,在下一节(在这些脚注之后)。

1 递归合并通过合并合并库来进行新的提交,处理多个最佳提交候选项时的罕见情况。对于我们在这里关心的所有情况,总有一个合并基础。

2 这会忽略章鱼合并,但它们并不适合这个模型。八达通合并不能使用索引来分阶段输入多个文件,也无法处理复杂的冲突。 (他们可能没有根本原因 - 人们可以想象Git存储阶段1到 N 对于一些大的 N - 但他们不会。 )

合并为形容词或名词

这更简单:合并提交是任何至少有两个父级的提交。除了拥有多个父级之外,合并提交与任何其他提交相同:每个提交都存储一个完整的快照,Git通过在您提交时将索引中的文件转换为其中一个永久只读来完成快照。同样,工作树只是一个副作用,除了它允许您将工作文件复制到索引中。索引充当提议但可写的提交。 (提交完全是只读的:一旦制作,就无法更改。)

快进不合并

快进根本不是合并。相反,它是Git参考运动的属性。这就是git pushgit fetch谈论快进或强制更新的原因:git push建议对其他一些Git的引用进行更改,通常是通过更改分支名称引用(如Git存储库中的refs/heads/master),您自己的Git调用origin。同样,git fetch会根据您自己的Git调用的{GIT存储库中的Git存储库中的参考值}对您自己的Git存储库中的远程跟踪名称(例如refs/remotes/origin/master)进行更改origin

git merge做什么

git merge前端命令可以执行上面列出的一项或多项操作:

  • (通常)找到一个共同的祖先用作合并基础。

  • 如果此共同祖先是来自 L R 不同提交,则需要进行真正的合并。如果未通过--ff-only禁止,则此git merge会执行合并动词。

  • 如果这个共同的祖先是 L 本身,那么 L R 。也就是说, L R 的祖先,或者两者是相同的提交。

    • 如果它们是相同的提交,则无需执行任何操作。

    • 否则可以快进。如果未通过--no-ff禁止,则此git merge将对当前分支执行快进操作,并检出commit R (以更新索引和工作树)。如果禁止快进,则此git merge将执行合并动词。

  • 如果共同的祖先是 R 本身,那么我们有 R L R L 的祖先。没有什么可做的:要么它们是同一个提交,要么你建议Git向后移动,但它不会。 merge命令将不执行任何操作。

此时,如果git merge执行了快进而不是合并,则一切都已完成,git merge只是退出(以适当的状态结束)。如果无事可做或(通过--ff-only)要禁止的事情,git merge退出。因此,只保留合并为动词的情况:git merge现在承诺执行的动词形式以合并

在任何情况下,git merge通常会记录关于此合并操作的内容,即它将此额外提交 R 作为输入。这将使最终提交具有两个父项:它将是合并提交。但是,如果您使用--squash,则git merge故意忽略这些额外的父信息。

如果要合并的动词形式失败,git merge会打印投诉和退出,但如果成功,git merge会继续执行最后一步,即运行git commit结束合并 - 当然,除非您使用--no-commit告诉它不要提交。由于没有特别好的理由,--squash始终隐含--no-commit

结论

这为我们提供了问题的答案:

  

git merge --squash是合并操作吗?

假设我们将所有特殊情况排除在外 - 我们消除了诸如快进合并之类的事情,或者没有任何要合并的投诉 - 那么答案取决于你是否表示动词形式, to合并,或形容词形式,合并提交。使用--squashgit merge 执行合并 - 它通过查找合并库并生成和组合变更集来运行合并机制 - 但它生成普通的非合并提交。因此答案是肯定和否定。