git - git`merge --squash`不添加“Merge”头来提交

标签 git merge

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

我的问题是:为什么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工具读取该标头以能够绘制该“合并边缘”,请参见下图。



我的问题再次相同,请参见上文。

最佳答案

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

您提到的“合并标头”不在该表单的提交中。相反,这只是git log在遇到合并提交时打印的内容。为了真正巩固这个想法,让我们看一下合并的含义(“合并”作为动词的含义)以及合并是什么。更正确地,让我们看一下什么是合并提交,或者作为adjective修改单词“ commit”的“合并”的含义。不过,我们会像Git的好用户一样变得懒惰,并将其从“合并提交”缩短为“合并”,使其成为名词。

合并为名词

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

如果合并提交是具有两个父级的提交,则git merge必须进行合并提交,并且合并的意思是“进行合并提交”,并且确实如此,但有时是这样。我们将很快看到何时,更重要的是为什么。



1其他VCS可能会停在两个。例如,Mercurial这样做:没有hg commit拥有两个以上的父母。但是,Git允许任何提交有任何数量的父母。具有三个或更多父项的Git提交称为章鱼合并,通常这些提交使用git merge -s octopus或同等功能进行,例如git merge topic1 topic2 topic3:运行带有额外提交说明符的git merge意味着-s octopus。这些操作无法执行一系列使用双亲合并的操作,因此Git的章鱼合并不会比Mercurial拥有更多的功能,但是章鱼合并有时很方便,并且很好地展示了您的Git Fu。 :-)



合并为动词

合并的动词形式要复杂得多,或者至少在Git中。我们也可以区分两种主要的合并形式:合并源代码更改的行为,然后合并分支的行为。 (这带来了“什么是分支”的问题。这个问题比您想象的要多得多,因此请参见What exactly do we mean by "branch"?。)许多Git命令可以进行动词合并,包括git cherry-pick,这两个元素本质上都是git revert的形式。2当然,git apply -3是最明显的实现方式,当这样做时,您会得到合并更改的动词形式。

合并变更

合并源更改更恰当地称为“三种方式合并”。有关更多信息,请参见the Wikipedia article 3或VonC's answerWhy is a 3-way merge advantageous over a 2-way merge?。细节可能会变得非常复杂,但是此合并过程的目标非常简单:我们希望将对某个通用基础所做的更改组合在一起,即给定在某个起点B,我们找到更改C1和C2,并将它们组合在一起以形成一个新的更改C3,然后将该更改添加到基本B中以获得新版本。

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

解决所有冲突后,Git将能够进行新的提交。

进行合并提交

正常的合并制作git add要做的最后一件事是进行合并提交。同样,如果没有冲突,Git可以自己完成:git merge合并更改,git merge每个合并结果更新暂存文件,然后为您运行git add。或者,如果存在冲突,请解决它们,运行git commit,然后运行git add。在所有情况下,实际上都是git commit而不是git commit本身来进行合并提交。

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



2 .git/MERGE中的-3(可以用git apply -3拼写)指示--3waygit apply输出中使用index字符串构造合并基础(如果需要)。在使用git diffgit cherry-pick进行此操作时,为了将它们转换为合并(而不是简单的补丁),Git会使用选择的或还原的提交的父提交结束。值得注意的是,在将补丁视为简单补丁失败之后,Git仅按文件执行此操作。仅当父提交是当前(HEAD)提交的祖先时,才使用父提交的文件作为三向合并的基本版本。如果实际上不是这样的祖先,则将“ base”到HEAD生成的差异与所应用的补丁结合起来可能没有帮助。不过,Git会作为备用。

3与Wikipedia一样,我现在发现其中存在一些小错误-例如,可能有两个以上的DAG LCA-但没有时间进行处理,而且它的概述也不是一件坏事。

4通常,它永远不会打扰首先要输入冲突的条目。如果可能的话,Git甚至可以缩短git revert阶段。例如,假设基本提交中有四个文件:git diff在任一提交中均与base保持不变,u.txt从base更改为one.txt,但未从base更改为另一个提交,HEAD从base提交到另一个提交,但不是从base提交到two.txt,并且两者都更改了HEAD。 Git会简单地直接复制three.txt,从u.txt中获取one.txt,从另一提交中获取HEAD,并且仅会产生差异,然后尝试将它们合并为two.txt。这进行得非常快,但这确实意味着,如果您对这些文件有自己的特殊三向合并程序,则永远不会为three.txtu.txtone.txt运行它,而只会为two.txt运行它。

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



非合并“合并”

上面的序列-签出一些提交(通常是分支提示),在另一个提交(通常是其他分支提示)上运行three.txt,找到合适的合并基础,进行两组比较,组合比较,然后提交结果-普通合并的工作方式以及Git如何进行合并提交。我们合并(作为动词)更改,并进行合并(形容词)提交(或“进行合并”,名词)。但是,正如我们前面提到的,git merge并不总是这样做。

快进

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

git push检测到您的merge参数是这些情况之一时-git merge是该另一提交的祖先-通常它将调用此快进操作。在这种情况下,Git仅使用您告诉它的提交来合并。根本没有新的提交,只是重新使用了一些现有的提交。 Git不会进行合并提交,也不会进行任何合并即动词。它只是将您切换到新提交,就像通过HEAD一样:移动当前分支标签并更新工作树。

您可以使用git reset --hard .5抑制此快进操作。在这种情况下,即使可以进行快进,--no-ff也会进行新的合并提交。您没有得到“动词合并”动作(没有工作要做),但是得到了新的合并提交,Git更新了您的工作树以进行匹配。

壁球合并不是合并

请注意,这里我们涵盖了三种情况中的两种:


动词形式加上形容词/名词形式:普通合并
合并的形容词/名词形式,但没有动词:可以快速转发但您运行git merge的合并


缺少的第三种情况是动词-无名词:我们如何获得合并的动作,结合更改,而没有合并提交的名词/形容词形式?这是“ squash merges”出现的地方。运行git merge --no-ff告诉Git照常执行合并操作,但不记录其他分支/ commit-ID,以便最终的git merge --squash <commit-specifier>进行正常的非合并,单亲提交。

就是这样,就是所有!最后,它只是进行普通的非合并提交。奇怪的是,它迫使您进行该提交,而不是自己进行。 (没有根本的理由必须这样做,而且我不知道Git作者为什么选择使其表现为这种方式。)但是,这些都是机制,而不是政策:它们告诉您如何进行各种调整。提交,而不提交您应该提交的内容,或者什么时候(最重要的是为什么)。



5您可以告诉git commit仅当它可以快速前进时才应继续:git merge。如果新提交是可快速转发的,则git merge --ff-only对其进行更新。否则,它只会失败。我创建了一个别名git merge来执行此操作,因为通常我要git mff然后查看是否需要合并,重新设置基址,完全创建一个新分支,还是其他。如果我可以快进,则无需执行其他任何操作,因此,如果git fetch有效,就可以完成。



何时,为什么使用哪种合并

为什么这个问题很难,并且像所有哲学问题一样,没有一个正确的答案(但肯定是一堆错误的答案:-))。考虑以下事实:每次完全使用git mff时,您可能会做一些不同的事情,并获得相同的源代码来进行最新的提交。 git merge有三个成功的结果(也就是说,合并时您不必git merge结束它,而是成功地结束它):


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


这三个之间的唯一区别(除了第一个明显的“根本没有新的提交”)是它们在提交图中留下的记录。6快速前进显然不留下任何记录:该图与之前相比没有变化,因为您未添加任何内容。如果这是您想要的,那应该使用。在您正在跟踪别人工作而从不做任何事情的存储库中,这可能就是您想要的。这也是默认情况下得到的,Git将为您“工作”。7

如果执行常规合并,则将保留合并记录。所有现有的提交都保持原样,Git添加了一个新的提交,其中有两个父母。以后再走的任何人都会看到谁做了什么,何时,如何做等等。如果这是您想要的,这就是您应该做的。当然,某些工具(例如git merge --abort)将显示谁在做什么,何时做等等,这些操作通过显示所有历史的完整图片,可能会使所有细节都掩盖“大图片”视图。换句话说,这既是正面也是负面。

如果您执行壁球合并,则不会留下合并记录。您进行了一个新提交,以接过每个动词合并动作,但是新提交不是名词合并。以后再来的任何人都会看到所有工作,但看不到它来自何处。诸如git log之类的工具无法显示这些细节,您以及其他所有人将只能获得一幅全景图。同样,这既是正面也是负面。但是不利的一面也许更大,因为如果您发现以后需要这些细节,它们就不存在了。它们不仅不在git log视图中,而且在将来的git log中也不存在。

如果您永远不打算再进行压缩后的更改,这可能不是问题。如果您打算完全删除该分支,放弃所有单独的更改作为个人,而仅保留单个集体的南瓜合并更改,则执行git merge的“不良”部分的“不良值”实际上为零。但是,如果您打算继续处理该分支,然后再将其合并,则特定的不良值会大大增加。

如果要专门执行壁球合并,以使git merge输出“看起来更好”(显示更多的大图片而不是用太多的细节遮盖它),请注意,有多种git merge --squash选项旨在选择性地选择哪些提交表明。特别是,git log避免完全遍历合并的分支,只显示合并本身,然后继续沿提交图的“主线”向下移动。例如,您还可以使用git log忽略所有未标记的提交。



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

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

8假设没有章鱼合并。

关于git - git`merge --squash`不添加“Merge”头来提交,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39096500/

相关文章:

Github 存储库未显示最新提交

git - 如何将 1 个提交从一个分支 merge 到另一个分支

git - Capistrano 无法使用 SSH pull Stash 存储库

javascript - 合并具有相同 "key"的 JSON 对象并使用 JavaScript 添加它们的 "value"

MySQL MERGE 存储引擎 - DROP 和 ALTER

python-3.x - 在合并时应用函数

Git 命令将管理 (.git) 目录移动到不同位置

git - 如何在具有共同祖先的 2 个分支之间 merge 冲突?

sql - 如何在 PostgreSQL 中合并相同的表?

git - rebase 单个 Git 提交