为什么 git merge 不创建一个共同的祖先?

Why git merge does not create a common ancestor?

这不是解决问题的问题,只是为了拓宽我对git的理解。

我有一个基本上是单人完成的项目,所以它在我们的 gitlab 服务器上只有一个分支;在我的开发机器上,我通常有一个“通用”开发分支,并在需要时有一个 quickfix 分支。我通常在将 quickfix 分支合并到 master 后将其删除,但我保持 dev 分支处于活动状态的时间更长,因此随着项目的进展,从 dev 到 master 的提交很多。

当我在 master 上合并 dev 时,我通常使用 --squash 来摆脱不相关的“开发”提交;然后我提议的合并消息说,“压缩以下提交”,以及自共同祖先 以来的提交列表 ,即我从中创建 dev 的提交, 而不是我合并的上一个提交 。当然,我只是简单地删除了默认评论并编写了自己的评论;然而,令我惊讶的是 git 没有意识到 从 dev 到 master 的合并为两个分支创建了一个新的共同祖先 .

同样,这不是问题:我可以简单地删除 dev 分支并重新创建它——实际上,这就是我在意识到没有必要之前所做的事情。然而,我想了解 为什么 git 不将合并视为分支的共同祖先。也许我错过了一个选项?

编辑:

我的合并策略执行以下操作:

有没有办法在不删除并重新创建 dev 分支的情况下获得以下内容?

好吧,最后一题(至少,当前版本的问题)可以这样实现(假设some-branch在A上,other-branch在revision-6上):

git checkout some-branch
git merge revision-3 # by using its ID
# now we have created revision B
git checkout other-branch
# let's rebase it
git rebase some-branch # this should set up revision-4, 5 and 6 on top of B
git checkout some-branch
git merge other-branch

给你了。

TL;DR

您想要的是“作为合并气泡的特征”。要获得这些,请在您的主线中对每个功能使用 git merge --no-ff。您通常应该将每个功能放在其自己的分支上,但如果您愿意,您可以每次使用相同的 name(例如,dev)。分支名称并不重要,Git 通常不会存储它们(如果您愿意,可以将它们放入提交消息中,但是 merge branch blergh 形式的消息没有实际价值)。

答案的根源是git merge --squash 不进行合并(提交)

Git中的merge这个词既用作动词,合并,意思是合并两个不同的集合的变化,并作为形容词修饰单词 commit: a merge commit 是有两个或更多父项的提交。1 形容词形式 merge commit 通常缩写为简单名词 a merge。所以我们需要记住,一些 Git 命令执行合并类型 操作 ,即执行合并作为动词,而一些 Git 命令产生合并类型提交,即使合并,名词。

git merge命令经常但不总是两者兼顾。有时它只执行两者之一 - 合并操作,最后没有合并提交 - 有时它两者都不

git cherry-pickgit revert 命令总是2 执行合并为动词部分但从不 a最后合并

git commit 命令可以进行普通提交,或者在某些特殊情况下,进行合并提交或 root 提交:完全没有父项的提交。

要了解所有这些部分如何相互作用,我们还需要记住一些事情:

  • Git 实际上是根据 Git 的 index.
  • 中的内容构建新的提交
  • 索引在 merge-as-a-verb 操作期间得到扩展。现在,它不再保存每个文件的 一个 副本,而是保存 三个.3
  • 如果Git在冲突合并的中间停止,它会留下各种跟踪文件,例如MERGE_HEADMERGE_MSGCHERRY_PICK_HEAD等。 git status 命令知道要查找这些并且可以告诉您您正处于冲突合并的中间,例如,文件尚未解决,或者所有冲突都已解决。

当你运行 git <em>command</em> --continue or git commit, Git 从中断处继续。 (--continue 品种作为完整性检查,此时有特定命令继续。)当您 运行 某些类型的 git resetgit <em>command</em> --abortgit <em>command</em> --quit, Git 终止未完成的操作,并通过调用正确类型的重置(--hard--soft).

这意味着,例如,git merge --no-commit 可以开始合并,运行 它会尽可能地独立——甚至可能达到没有冲突的程度——然后停下来让你 fiddle 和 Git 的索引 and/or 你的工作树随你喜欢。您最终的 git merge --continuegit commit 将使用停止时留下的文件 Git 以及您对索引所做的任何更新(所谓的 邪恶合并; 参见 Evil merges in git?)。或者,您的 git reset --hardgit merge --abort 擦除 git merge 所做的所有工作,删除正在进行的合并标记文件,并让您设置得好像您甚至没有启动 git merge 命令.4

总之,如果你没有迷路地读完这部分,git merge --squash就变得很容易理解了。它:

  • 开始合并过程,就像 git merge 那样;
  • 有一个隐含的 --no-commit,因此它在提交之前停止; and/but
  • 不会创建任何“正在进行合并”文件,因此停止后的状态是git merge --continue不允许,git commit将进行普通提交,而不是合并提交。

由于合并和未来的合并基础由提交图决定——也就是说,提交本身包括它们的父链接——而git merge --squash确实没有放入额外的父链接,最终提交没有你想要的历史。那么,解决方案是避免 git merge --squash.

您可能(非常git直接)想知道 git merge --squash 有什么用。答案是:没那么多!不过,在一种情况下它绝对有意义,那就是当您:

  • 创建一个分支进行实验;
  • 通过编写多个提交来进行试验;
  • 在实验结束时,确定结果很好,但它应该只是一次提交;
  • 想轻松地完成一次提交。

为了进行一次提交,您返回到您创建实验分支的分支,然后 运行 git merge --squash experiment(或此处合适的任何名称)。然后,您为一次提交编写所需的提交消息,然后 删除实验分支 。它现在“死了”:它的提交没有进一步的用处。都是垃圾,一个月左右等垃圾收集员来处理的时候,再和其他垃圾一起运走。

如果您不打算杀死 分支,git merge --squash 可能是错误的工具。 (但另请参阅 关于将 squash-merge 与 GitHub PR 一起使用。)


1比两个父项 多的提交是 octopus 合并。这些通常由 git merge -s octopus 组成,但 -s octopus 部分是通过给 git merge 两个或更多提交说明符来暗示的。他们不会做任何你不能用更典型的双亲合并做的事情。事实上,他们特别做一些事情——即解决冲突——你可以做双亲合并,这可能是首先进行章鱼合并的主要理由:由于章鱼合并比普通合并“弱”,如果您在一组提交中看到一个,您可以非常确定它是这些简单、无冲突的合并之一案例。

不过总的来说,我还是觉得章鱼合体主要是为了炫耀。

2这里的“始终”有点太强了:有时 git cherry-pick 可能会出错,例如,如果 merge-as-a-verb部分操作因合并冲突而停止,您被留在操作的中间。

3更准确地说,它包含最多三个,从三个输入提交到一个合并操作:合并基础, “我们的”或“本地”或 HEAD 提交,以及“他们的”或“远程”或“其他”提交。但是,如果一个文件在三个提交中的一个中丢失——例如,如果我们修改了文件 path/to/file.ext 而他们 删除了 它完全 - 可能少于三个索引条目文件。

4请注意,要使其工作,git reset --hard 写入的 state——也就是说,集合现在 HEAD 提交中的文件的数量 - 必须与您第一次启动 git merge 时所有内容的状态相匹配。等价地,git status 必须说 nothing to commit, working tree clean(尽管可能有未跟踪的文件)。这就是为什么 git merge 通常需要一个“干净”的状态才能开始。内部 git merge-recursive 命令不是那么小心,并且可以 开始 与索引 and/or 工作树的合并,其状态无法通过停止合并后恢复所有,如果你 运行 git merge-recursive——例如,git stash apply

When I merge dev on master, I usually use --squash to get rid of the irrelevant "development" commits

您没有实现您声明的目的。您没有摆脱任何提交。

此外,squash merge根本不是merge,它不会在这个分支和另一个分支之间建立任何联系。因此合并基地永远不会移动,历史就会丢失。这就是为什么,正如我在这里所说的,,壁球合并和长期存在的分支是相反的。

您所描述的是,您将压缩开发提交 first 以简化历史,然后 thentrue合并。换句话说,停止使用 merge --squash 而是使用 squash(使用 reset 或 interactive rebase)然后合并。