git `merge --squash` 不添加“合并”标题来提交

Posted

技术标签:

【中文标题】git `merge --squash` 不添加“合并”标题来提交【英文标题】:git `merge --squash` does not add "Merge" header to commit 【发布时间】:2016-12-30 00:09:18 【问题描述】:

我想知道所有工具如何知道合并了哪些分支/提交,直到我在提交中找到“合并”标题。

我的问题是:为什么 git merge --squash 不添加该标题,而 git merge 呢?

换句话说:为什么我在与git merge 合并时看到合并边缘而与git merge --squash 没有边缘?

谢谢。

一些修正信息:

“合并标题”是指合并后git log 中的第二行:

commit 7777777
Merge: 0123456 9876543
Author: Some Body <...>
Date:   Fri ....

...虽然git merge --squash产生该行,我的假设是 git gui 工具读取该标题以能够绘制该“合并边缘”,请参见下图。

我的问题还是一样,见上文。

【问题讨论】:

我已经修改了我的答案(并希望保持原样),但超简短的答案是:“不记录合并链接”正是--squash 的意思。这是--squash 唯一真正的效果。如果您确实想要记录合并,不要使用--squash。这不仅仅是 GUI 工具,它是 Git 构建提交 DAG 方式的基本结果。 【参考方案1】:

TL;DR:git merge --squash 合并(动词),但不进行合并(名词)。

您提到的“合并标头”不在该形式的提交中。相反,它只是git log 在遇到合并提交 时打印的内容。为了真正巩固这个想法,让我们看看合并做了什么——“合并”作为一个动词意味着什么——以及什么是合并。更恰当地说,让我们看看 merge commit 是什么,或者“merge”意味着什么作为 adjective 修改单词“commit”。不过,我们会像优秀的 Git 用户一样偷懒,并将其从“合并提交”缩短为“合并”,使其成为名词。

合并为名词

让我们先看看名词的用法,因为这样更简单。在 Git 中,合并提交 只是一个包含至少两个父提交的提交。真的就是这么简单。每个提交都有一定数量的父母。 root 提交的父提交为零(并且大多数存储库只有一个根提交,这是有史以来的第一次提交,显然不可能有父提交)。大多数普通提交只有一个父提交,而所有其他提交都有两个或更多父提交1,因此是合并提交。然后我们将短语“merge commit”缩短为“a merge”,将“merge”从形容词(修饰“commit”)改为名词(意为“a merge commit”)。

如果 merge commit 是有两个父级的提交,那么git merge 必须进行合并提交,而 to merge 应该意味着“进行合并提交”——确实如此,但只是有时。我们很快就会看到何时——更重要的是,为什么


1其他 VCS 可能会停在两点。例如,Mercurial 确实做到了:没有一个 hg 提交有 more 两个父母。然而,Git 允许任何提交拥有任意数量的父级。具有三个或更多父级的 Git 提交称为 octopus 合并,通常这些提交是使用 git merge -s octopus 或等效的,例如 git merge topic1 topic2 topic3:运行带有额外提交说明符的 git merge 意味着 @ 987654334@。这些都没有使用一系列双亲合并无法完成的事情,因此 Git 的 octopus 合并并没有比 Mercurial 赋予它更多的功能,但是 octopus 合并有时很方便,并且有利于炫耀你的 Git Fu。 :-)


合并为动词

to merge 的动词形式要复杂得多——至少在 Git 中是这样。我们也可以区分两种主要的合并形式:一种是合并源代码更改,另一种是合并分支。 (这带来了“什么是分支”的问题。这个问题比你想象的要多得多,所以请参阅What exactly do we mean by "branch"?)许多 Git 命令可以进行动词类型的合并,包括 git cherry-pick 和 @987654336 @,这两者本质上都是git apply -3的一种形式。2 当然git merge是最明显的做法,当它这样做时,你会得到合并变化的动词形式。

合并更改

合并源更改更恰当地称为“三路合并”。有关这方面的更多信息,请参阅 the Wikipedia article3 或 VonC's answer 至 Why is a 3-way merge advantageous over a 2-way merge? 细节可能会变得相当复杂,但此合并过程的目标非常简单:我们想结合对一些共同基础所做的更改,即,给定一些起点B,我们会发现更改C1 em> 和 C2 并将它们组合成一个新的更改 C3,然后将该更改添加到基础 B 以获取新版本。

在 Git 中,合并源的行为(即进行三向合并本身)使用 Git 的 index,也称为“暂存区”。通常,索引对于将进入下一次提交的每个文件只有一个条目。当你 git add 一个文件时,你告诉 Git 更新文件的暂存版本,替换当前索引中的那个,或者如果文件之前“未跟踪”,则将文件添加到 index 以便它现在被跟踪并为下一次提交暂存。然而,在合并过程中,每个文件的索引最多有 三个 条目:一个来自合并基本版本,一个来自文件版本的条目,将被视为更改 #1(也称为“我们的”),一个用于更改#2(“他们的”)。如果 Git 能够自行组合更改,它会将4三个条目替换为一个常规(staged-for-commit)条目。否则它会因冲突而停止,并在文件的工作树版本中标记冲突。您必须自己解决冲突(可能是在此过程中编辑文件),并使用git add 将三个特殊的冲突合并索引版本替换为一个正常的暂存版本。

一旦所有冲突都得到解决,Git 将能够进行新的提交。

进行合并提交

一个正常的、合并的git merge 所做的最后一件事是进行合并提交。同样,如果没有冲突,Git 可以自己执行此操作:git merge 合并更改,git adds 每个合并结果以更新暂存文件,并为您运行 git commit。或者,如果有冲突,你修复它们,运行git add运行git commit。在所有情况下,实际上是 git commit,而不是 git merge 本身进行了合并提交。

这最后一部分其实很简单,因为索引/暂存区都设置好了。 Git 只是像往常一样进行提交,除了不给它当前 (HEAD) 提交的 ID 作为它的一个单亲,它给它至少 两个 父提交 ID:第一个是HEAD 像往常一样提交,其余的来自git merge (.git/MERGE) 留下的文件。


2git apply -3 中的-3,可以拼写为--3way,指示git apply 使用git diff 输出中的index 字符串来构造合并如果需要的话。当使用git cherry-pickgit revert 执行此操作以将它们转换为合并(而不是简单的补丁)时,Git 最终会使用精选或还原提交的父提交。这里值得注意的是,Git 仅在每个文件 的基础上执行此操作, 将补丁视为一个简单的补丁失败。仅当该提交是当前(HEAD)提交的祖先时,使用父提交的文件作为三向合并的基本版本通常会有所帮助。如果它实际上不是这样的祖先,那么将“base”生成的差异与 HEAD 与正在应用的补丁结合起来可能没有帮助。尽管如此,Git 还是会将其作为后备。

3像往常一样,我在***中发现了一些小错误——例如,可能有两个以上的 DAG LCA——但没有时间处理它,这不是一个糟糕的概述。

4通常,一开始就不必费心制作冲突的条目。如果可能,Git 甚至会缩短 git diff 阶段。例如,假设基本提交中有四个文件:u.txt 在任一提交中与基本提交相同,one.txt 从基本提交更改为HEAD,但未从基本提交更改为另一个提交,two.txt 更改为base 到另一个提交,但不是从 base 到 HEAD,并且 three.txt 在两者中都发生了更改。 Git 将直接复制u.txt,从HEAD 中获取one.txt,从另一个提交中获取two.txt,并且只需要生成差异,然后尝试将它们合并为three.txt。这进展很快,但确实意味着如果您有自己的特殊三向合并程序来处理这些文件,它永远不会运行u.txtone.txttwo.txt,仅适用于three.txt

我不确定 Git 是在尝试合并差异之前创建多个索引条目,还是在尝试和失败之后创建多个索引条目。但是,它必须在运行自定义合并驱动程序之前完成所有三个条目。


非合并“合并”

上面的顺序——检查一些提交(通常是一个分支提示),在另一个提交上运行git merge(通常是一些其他分支提示),找到一个合适的合并基础,制作两组diff,合并 diff,并提交结果——这是正常合并的工作方式,以及 Git 如何进行合并提交。我们合并(作为动词)更改,并进行合并(形容词)提交(或“合并”,名词)。但是,正如我们之前提到的,git merge 并不总是这样做。

快进

有时git merge 说它正在“进行快进合并”。这有点用词不当,因为“快进”在 Git 中被更准确地视为 分支标签更改的属性。还有两个使用此属性的命令,git fetchgit push,它们区分正常(或“快进”)分支更新和“强制”更新。对快速转发的正确讨论需要深入了解提交图的细节,所以我在这里要说的是,当您将分支标签从提交 O (旧)移动到提交 N(新),并且提交 N 具有提交 O 作为祖先。

git merge 检测到您的合并参数是其中一种情况——HEAD 是另一个提交的祖先——它通常会调用这个快进操作。在这种情况下,Git 只是使用您告诉它合并的提交。根本没有 new 提交,只是重用了一些 existing 提交。 Git 不会进行合并提交,也不会进行任何作为动词的合并。它只是将您切换到新的提交,几乎就像 git reset --hard:移动当前分支标签并更新工作树。

您可以使用--no-ff 抑制此快进操作。5 在这种情况下,即使可以进行快进,git merge 也会进行新的合并提交。您没有获得合并动词 action(没有工作要做),但您确实获得了新的合并 commit,并且 Git更新您的工作树以匹配。

壁球合并不是合并

请注意,我们在这里介绍了三种情况中的两种:

动词形式加形容词/名词形式:正常合并 形容词/名词形式的合并,但没有动词:一个可以快进的合并,但你跑了git merge --no-ff

缺少的第三种情况是verb-without-noun:我们如何获得合并的动作,组合更改,而没有合并的名词/形容词形式commit?这就是“壁球合并”的用武之地。运行 git merge --squash &lt;commit-specifier&gt; 告诉 Git 像往常一样执行合并操作,但 记录另一个分支/提交 ID,以便最终的 git commit进行正常的、非合并的、单亲提交。

就是这样——就是这样!它只是在最后进行正常的非合并提交。奇怪的是,它强制 做出该提交,而不是自己做出。 (它没有必要这样做的根本原因,我不知道 Git 的作者为什么选择让它这样做。)但这些都是 机制,而不是 策略:他们告诉你如何做出各种提交,但不告诉你应该做出哪些,或者什么时候,或者——最重要的——为什么.


5您可以告诉git merge,它应该在它可以快进的情况下继续:git merge --ff-only。如果新的提交可以快进,git merge 会对其进行更新。否则它只会失败。我做了一个别名git mff,这样做,因为通常我想git fetch,然后看看我是否需要合并、变基、完全创建一个新分支,或者其他什么。如果我可以快进,我不需要做任何其他事情,所以如果git mff 有效,我就完成了。


使用什么样的合并,何时,为什么

why 问题很难,并且像所有哲学问题一样,没有一个正确答案(但肯定有一堆错误答案 :-))。考虑一下这个事实:每次你使用git merge,你可以做一些不同的事情并获得相同的源代码来配合你最新的提交。 git merge 有三个成功的结果(即,您不以 git merge --abort 结束它,而是成功结束它的合并):

快进:没有新的提交;来源是现有提交的来源。 真正的合并:新的提交;来源是组合来源。 壁球合并:新提交;来源是组合来源。

这三个之间的唯一区别(除了第一个明显的“根本没有新的提交”)是他们在提交中留下的记录 graph .6 快进显然会留下 no 记录:该图与之前相比没有变化,因为您什么也没添加。如果这就是你想要的,那就是你应该使用的。在您关注别人的工作并且从不自己做任何事情的存储库中,这可能是您想要的。这也是您默认获得的,Git 将为您“正常工作”。7

如果您进行常规合并,则会留下合并记录。所有现有的提交都保持原样,Git 添加一个新的提交,有两个8 父级。以后出现的任何人都会看到谁做了什么,何时,如何等。如果这是你想要的,这就是你应该做的。当然,一些工具(如git log)会显示谁做了什么,什么时候等,通过显示所有历史的完整画面,可能会用所有的小细节来掩盖全局视图。换句话说,这既是有利的一面,也是不利的一面。

如果您进行壁球合并,则会留下 no 合并记录。您进行了一个新的提交,该提交获取了每个作为动词的合并动作,但新的提交不是作为名词的合并。以后来的任何人都会看到所有的工作,但看不到它来自哪里。 git log 之类的工具无法显示小细节,而您——以及其他所有人——将获得一张大图。同样,这既是有利的,也是不利的。但不利的一面可能更大一些,因为如果您以后发现需要这些细节,它们不存在。它们不仅不在git log 视图中,而且在未来git merge 中也不存在。

如果您永远不会在未来进行git merge 的压缩更改,那可能不是问题。如果您打算完全删除该分支,放弃所有个人更改作为个人并仅保留单个集体 squash-merge 更改,则执行 git merge --squash 的“坏”部分基本上为零“坏值”。但是,如果您打算继续在该分支上工作,并在以后再次合并它,则该特定的 badness 值会大大增加。

如果你正在做 squash 合并,特别是为了使 git log 输出“看起来更好”(显示更多的大图而不是用太多细节来掩盖它),请注意有各种 git log 选项被设计为 selective 关于它显示的哪些提交。特别是,--first-commit 避免完全遍历合并的分支,只显示合并本身,然后继续沿着提交图的“主线”向下移动。例如,您还可以使用--simplify-by-decoration 省略除标记 之外的所有提交。


6嗯,也在你的 reflogs 中;但是您的 reflog 是私有的,并且最终会过期,因此我们将忽略它们。

7这假设他们——无论“他们”是谁——不会通过变基或删除已发布的提交来“倒回”他们的提交图。如果他们确实删除了已发布的提交,您的 Git 默认会合并这些提交,就好像它们是您自己的工作一样。这是任何发布 Git 存储库的人都应该认真考虑“回退”此类提交的原因之一。

8假设没有花哨的章鱼合并。

【讨论】:

注意:git apply 与合并完全不同,因为它没有合并基础。见***.com/a/38989749/6309。 对不起,但我也找不到我的问题的答案。我知道 merge 或 squash merge 的作用。 @VonC: git apply-3 将使用 index 字符串来生成合并基础。不过,我应该在正文中解决这个问题。 @Aitch:现在您已经编辑了您的问题,我可以更直接地回答:您所说的“合并标题”正是git log 在遇到 merge 时打印的内容提交。它打印Merge:,后跟父 SHA-1 ID。如果您没有合并提交(即您没有作为名词合并),则没有记录的合并信息。 @torek 好的,所以合并壁球非常具有误导性。如果我想要一个没有微提交和合并元信息的真正合并,我应该使用git merge --no-ff ... (***.com/a/14865661/4469738)。实际上很奇怪,合并首先尝试快进,因为 99% 的修补程序大师开发合并都没有快进,有一天有快进,突然有一些奇怪的微提交我不想见发布分支。在我的情况下,我使用了壁球合并,因为我不知道这不是真正的提交。下次我将使用--no-ff。谢谢!【参考方案2】:

Git 合并 - 如果 master 中不存在这些提交,则将分支中的所有提交合并到 master [保留提交顺序]。它还添加了一个新的提交 ID,显示了合并 - 将http://repo-name 的分支'branch-name' 合并到“master”中

现在 Git merge --squash - 查找所有与 master 不同的文件并将它们标记为已修改。现在,当您提交这些内容时,它们会使用您提供的消息进入一个新的提交 ID,就像普通提交一样。

【讨论】:

请阅读我的问题。我知道合并或合并壁球的作用,但是 gui 工具如何能够通过简单的合并绘制合并边缘,而它不是合并壁球?我修改了更多信息,现在应该更清楚了。

以上是关于git `merge --squash` 不添加“合并”标题来提交的主要内容,如果未能解决你的问题,请参考以下文章

git中的merge --squash

git merge --squash 选项合并commit操作实例

git merge时merge/squash merge/rebase merge的区别

“git merge --squash”的正确用例是啥?

聊下git merge --squash

聊下git merge --squash