为什么 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-pick
和 git revert
命令总是2 执行合并为动词部分但从不 a最后合并
git commit
命令可以进行普通提交,或者在某些特殊情况下,进行合并提交或 root 提交:完全没有父项的提交。
要了解所有这些部分如何相互作用,我们还需要记住一些事情:
- Git 实际上是根据 Git 的 index.
中的内容构建新的提交
- 索引在 merge-as-a-verb 操作期间得到扩展。现在,它不再保存每个文件的 一个 副本,而是保存 三个.3
- 如果Git在冲突合并的中间停止,它会留下各种跟踪文件,例如
MERGE_HEAD
、MERGE_MSG
、CHERRY_PICK_HEAD
等。 git status
命令知道要查找这些并且可以告诉您您正处于冲突合并的中间,例如,文件尚未解决,或者所有冲突都已解决。
当你运行 git <em>command</em> --continue
or git commit
, Git 从中断处继续。 (--continue
品种作为完整性检查,此时有特定命令继续。)当您 运行 某些类型的 git reset
或 git <em>command</em> --abort
或 git <em>command</em> --quit
, Git 终止未完成的操作,并通过调用正确类型的重置(--hard
或 --soft
).
这意味着,例如,git merge --no-commit
可以开始合并,运行 它会尽可能地独立——甚至可能达到没有冲突的程度——然后停下来让你 fiddle 和 Git 的索引 and/or 你的工作树随你喜欢。您最终的 git merge --continue
或 git commit
将使用停止时留下的文件 Git 以及您对索引所做的任何更新(所谓的 邪恶合并; 参见 Evil merges in git?)。或者,您的 git reset --hard
或 git 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 以简化历史,然后 then 与 true合并。换句话说,停止使用 merge --squash
而是使用 squash(使用 reset 或 interactive rebase)然后合并。
这不是解决问题的问题,只是为了拓宽我对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-pick
和 git revert
命令总是2 执行合并为动词部分但从不 a最后合并
git commit
命令可以进行普通提交,或者在某些特殊情况下,进行合并提交或 root 提交:完全没有父项的提交。
要了解所有这些部分如何相互作用,我们还需要记住一些事情:
- Git 实际上是根据 Git 的 index. 中的内容构建新的提交
- 索引在 merge-as-a-verb 操作期间得到扩展。现在,它不再保存每个文件的 一个 副本,而是保存 三个.3
- 如果Git在冲突合并的中间停止,它会留下各种跟踪文件,例如
MERGE_HEAD
、MERGE_MSG
、CHERRY_PICK_HEAD
等。git status
命令知道要查找这些并且可以告诉您您正处于冲突合并的中间,例如,文件尚未解决,或者所有冲突都已解决。
当你运行 git <em>command</em> --continue
or git commit
, Git 从中断处继续。 (--continue
品种作为完整性检查,此时有特定命令继续。)当您 运行 某些类型的 git reset
或 git <em>command</em> --abort
或 git <em>command</em> --quit
, Git 终止未完成的操作,并通过调用正确类型的重置(--hard
或 --soft
).
这意味着,例如,git merge --no-commit
可以开始合并,运行 它会尽可能地独立——甚至可能达到没有冲突的程度——然后停下来让你 fiddle 和 Git 的索引 and/or 你的工作树随你喜欢。您最终的 git merge --continue
或 git commit
将使用停止时留下的文件 Git 以及您对索引所做的任何更新(所谓的 邪恶合并; 参见 Evil merges in git?)。或者,您的 git reset --hard
或 git 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
可能是错误的工具。 (但另请参阅
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 以简化历史,然后 then 与 true合并。换句话说,停止使用 merge --squash
而是使用 squash(使用 reset 或 interactive rebase)然后合并。