[英]Why git merge does not create a common ancestor?
This is not a question to solve a problem, just to broaden my understanding of git.这不是解决问题的问题,只是为了拓宽我对git的理解。
I have a project that is basically a one-man effort, so it has just one branch on our gitlab server;我有一个项目基本上是一个人的工作,所以它在我们的 gitlab 服务器上只有一个分支; on my development machine, I usually have a "generic" dev branch, and a quickfix branch when needed.
在我的开发机器上,我通常有一个“通用”开发分支,并在需要时有一个 quickfix 分支。 I usually delete the quickfix branch after merging it on master, but I keep the dev branch active for longer periods, so there is a number of commits from dev to master as the project progresses.
我通常在将 quickfix 分支合并到 master 后将其删除,但我会在较长时间内保持 dev 分支处于活动状态,因此随着项目的进展,从 dev 到 master 会有许多提交。
When I merge dev on master, I usually use --squash
to get rid of the irrelevant "development" commits;当我在 master 上合并 dev 时,我通常使用
--squash
来摆脱不相关的“开发”提交; then the merge message I am proposed says, "squashed commit of the following", with a list of commits since the common ancestor , that is, the commit I created dev from, rather than the previous commit I merged .然后我建议的合并消息说,“压缩以下提交”,其中包含自共同祖先以来的提交列表,即我创建 dev 的提交,而不是我之前合并的提交。 Of course I simply delete the default comment and write my own;
当然,我只是简单地删除默认评论并编写自己的评论; yet, I am surprised that git does not realize that a merge from dev to master creates a new common ancestor for the two branches .
然而,令我惊讶的是 git 没有意识到从 dev 到 master 的合并为两个分支创建了一个新的共同祖先。
Again, not a problem: I can simply delete the dev branch and recreate it–actually, that's what I used to do before realizing that it was not necessary.同样,这不是问题:我可以简单地删除 dev 分支并重新创建它——实际上,这就是我在意识到没有必要之前所做的事情。 Yet, I would like to understand why git does not consider a merge as a common ancestor for the branches.
然而,我想了解为什么git 不将合并视为分支的共同祖先。 Maybe I am missing an option?
也许我错过了一个选择?
EDIT:编辑:
My merge strategy does the following:我的合并策略执行以下操作:
Is there anyway to get the following, without deleting and recreating the dev branch?无论如何都可以获得以下内容,而无需删除和重新创建开发分支?
What you want is "features as merge bubbles".您想要的是“作为合并气泡的功能”。 To get these, use
git merge --no-ff
from your mainline with each feature.要获得这些,请使用
git merge --no-ff
从您的主线与每个功能。 You should generally put each feature on its own branch, but if you like, you can use the same name (eg, dev
) each time.您通常应该将每个功能放在自己的分支上,但如果您愿意,您可以每次都使用相同的名称(例如
dev
)。 The branch names don't really matter and Git generally does not store them (you can get them into commit messages if you like, but messages of the form merge branch blergh
have no real value).分支名称并不重要,并且 Git 通常不会存储它们(如果您愿意,可以将它们放入提交消息中,但
merge branch blergh
形式的消息没有实际价值)。
The root of the answer is that git merge --squash
does not make a merge (commit) .答案的根源是
git merge --squash
不进行合并(提交) 。
The word merge in Git is used both as a verb, to merge , meaning to combine two different sets of changes, and as an adjective modifying the word commit: a merge commit is a commit with two or more parents. Git 中的单词merge既用作动词to merge ,意思是合并两组不同的更改,也用作修饰单词commit 的形容词:合并提交是具有两个或多个父项的提交。 1 The adjective form, merge commit , is often shortened to a simple noun, a merge .
1形容词形式,合并提交,通常缩写为一个简单的名词,合并。 So we need to keep in mind that some Git commands perform merge-type actions , ie, do merge-as-a-verb, and some Git commands produce merge-type commits , ie, make a merge , a noun.
所以我们需要记住,一些 Git 命令执行合并类型的操作,即合并作为动词,而一些 Git 命令产生合并类型的提交,即进行合并,一个名词。
The git merge
command often but not always does both. git merge
命令经常但并不总是两者兼而有之。 Sometimes it does just one of the two—the merge action, without the merge commit at the end—and sometimes it does neither .有时它只做两者之一——合并操作,最后没有合并提交——有时它都不做。
The git cherry-pick
and git revert
commands always 2 do the merge-as-a-verb part but never make a merge in the end. git cherry-pick
和git revert
命令总是2执行合并作为动词部分,但最后从不合并。
The git commit
command can make an ordinary commit, or in some special cases, a merge commit or a root commit: a commit with no parents at all. git commit
命令可以进行普通提交,或者在某些特殊情况下,合并提交或根提交:根本没有父提交。
To understand how all these parts interact, we need to remember a few more things:要了解所有这些部分是如何相互作用的,我们还需要记住一些事情:
MERGE_HEAD
, MERGE_MSG
, CHERRY_PICK_HEAD
, and so on.MERGE_HEAD
、 MERGE_MSG
、 CHERRY_PICK_HEAD
等。 The git status
command knows to look for these and can tell you that you are in the middle of a conflicted merge, for instance, with files as yet unresolved, or with all conflicts resolved. git status
命令知道要查找这些,并且可以告诉您您正处于冲突合并的中间,例如,文件尚未解决,或者所有冲突都已解决。 When you run git command --continue
or git commit
, Git picks up where it left off.当您运行
git command --continue
或git commit
时,Git 从它停止的地方继续。 (The --continue
variety acts as a sanity check, that there's that particular command to continue at this point.) When you run certain kinds of git reset
, or git command --abort
or git command --quit
, Git terminates the unfinished operation and either puts things back ( --abort
) or doesn't ( --quit
) by invoking the right kind of reset ( --hard
or --soft
). (The
--continue
variety acts as a sanity check, that there's that particular command to continue at this point.) When you run certain kinds of git reset
, or git command --abort
or git command --quit
, Git terminates the unfinished操作并通过调用正确类型的重置( --hard
或--soft
)来放回( --abort
)或不放回( --quit
)。
This means that, eg, git merge --no-commit
can start the merge, run it as far as it can on its own—perhaps even to the point that there are no conflicts remaining—and then just stop and let you fiddle with Git's index and/or your working tree as much as you like.这意味着,例如,
git merge --no-commit
可以启动合并,尽可能自行运行它——甚至可能到没有剩余冲突的地步——然后停下来让你摆弄随心所欲地使用 Git 的索引和/或工作树。 Your eventual git merge --continue
or git commit
will then finish the merge, using the files Git left behind when it stopped, plus any updates you made to the index (a so-called evil merge; see Evil merges in git? ). Your eventual
git merge --continue
or git commit
will then finish the merge, using the files Git left behind when it stopped, plus any updates you made to the index (a so-called evil merge; see Evil merges in git? ). Or, your git reset --hard
or git merge --abort
erases all the work that git merge
did, removes the merge-in-progress marker files, and leaves you set up as if you had not even started a git merge
command. Or, your
git reset --hard
or git merge --abort
all the work that git merge
did, removes the merge-in-progress marker files, and leaves you set up as if you had not even started a git merge
command. 4 4
Anyway, if you have gotten through to this part without getting lost, git merge --squash
becomes very easy to understand.无论如何,如果您已经通过这部分而没有迷路,
git merge --squash
变得非常容易理解。 It:它:
git merge
would;git merge
一样;--no-commit
, so that it stops before committing;--no-commit
,因此它在提交之前停止; and/butgit merge --continue
is not allowed, and git commit
will make an ordinary commit, not a merge commit.git merge --continue
是不允许的,并且git commit
将进行普通提交,而不是合并提交。 Since merges, and future merge bases, are determined by the commit graph —which is to say, the commits themselves including their parent linkages—and git merge --squash
does not put in the extra parent linkage, the final commit doesn't have the history you want.由于合并和未来的合并基础由提交图决定——也就是说,提交本身包括它们的父链接——并且
git merge --squash
没有放入额外的父链接,最终提交没有你想要的历史。 The solution, then, is to avoid git merge --squash
.那么,解决方案是避免
git merge --squash
。
You might (quite legitimately) wonder what git merge --squash
is good for.您可能(非常合理地)想知道
git merge --squash
有什么好处。 The answer is: not all that much, There's one situation in which it definitely makes sense, though: and that is when you:答案是:不是那么多,但在一种情况下它绝对是有意义的:那就是当你:
To make that one commit, you go back to the branch from which you created the experimental branch, and run git merge --squash experiment
(or whatever name is appropriate here).为了进行那一次提交,您将 go 返回到您创建实验分支的分支,然后运行
git merge --squash experiment
(或任何合适的名称)。 You then write the desired commit message for the one commit, and then delete the experimental branch .然后,您为一个提交编写所需的提交消息,然后删除实验分支。 It is now "dead": its commits have no further use.
它现在“死了”:它的提交没有进一步的用途。 They are all trash, to be hauled away with the rest of the rubbish in a month or so when the garbage collector gets around to it.
都是垃圾,一个月左右,当垃圾收集器处理完后,就用垃圾的 rest 将它们拖走。
If you don't intend to kill the branch, git merge --squash
is probably the wrong tool.如果您不打算杀死分支,
git merge --squash
可能是错误的工具。 (But see also matt's comment about using squash-merge with GitHub PRs.) (但另请参阅matt 关于将 squash-merge 与 GitHub PRs 结合使用的评论。)
1 A commit with more than two parents is an octopus merge . 1有两个以上父母的提交是章鱼合并。 These are normally made with
git merge -s octopus
, but the -s octopus
part is implied by giving git merge
two or more commit specifiers.这些通常是用
git merge -s octopus
制作的,但是通过给git merge
两个或多个提交说明符来暗示-s octopus
部分。 They don't do anything you can't do with more typical two-parent merges.他们没有做任何你不能用更典型的双亲合并做的事情。 In fact, they specifically don't do things—namely, resolve conflicts—that you can do with two-parent merges, which is probably the main justification for having octopus merge in the first place: since an octopus merge is "weaker" than a normal merge, if you see one in a set of commits, you can be pretty sure it was one of these easy, conflict-free merge cases.
事实上,他们特别不做事情——即解决冲突——你可以用双亲合并来做,这可能是首先让章鱼合并的主要理由:因为章鱼合并比“弱”一个正常的合并,如果你在一组提交中看到一个,你可以很确定它是这些简单的、无冲突的合并案例之一。
Overall, though, I still think octopus merges are mainly just for showing off.不过,总的来说,我仍然认为章鱼合并主要是为了炫耀。
2 "Always" here is a little too strong: sometimes git cherry-pick
can just error out, for instance, and if the merge-as-a-verb part of the action stops with a merge conflict , you're left in the middle of the operation. 2这里的“总是”有点太强了:例如,有时
git cherry-pick
可能会出错,如果动作的 merge-as-a-verb 部分因合并冲突而停止,你就会被留在操作的中间。
3 More precisely, it holds up to three, from the three input commits to a merge operation: the merge base, the "ours" or "local" or HEAD
commit, and the "theirs" or "remote" or "other" commit. 3更准确地说,它最多可容纳三个,从三个输入提交到合并操作:合并基础、“我们的”或“本地”或
HEAD
提交,以及“他们的”或“远程”或“其他”提交. But if a file is missing from one of the three commits—for instance, if we modified file path/to/file.ext
and they removed it entirely—there might be fewer than three index entries for the file.但是,如果三个提交之一中缺少一个文件——例如,如果我们修改了文件
path/to/file.ext
并且他们完全删除了它——那么该文件的索引条目可能少于三个。
4 Note that for this to work, the state that git reset --hard
writes—which is to say, the set of files that are in the HEAD
commit right now—must match the state that everything had when you first started the git merge
. 4 Note that for this to work, the state that
git reset --hard
writes—which is to say, the set of files that are in the HEAD
commit right now—must match the state that everything had when you first started the git merge
. Equivalently, git status
would have had to have said nothing to commit, working tree clean
(though perhaps with untracked files).等效地,
git status
将不得不nothing to commit, working tree clean
(尽管可能带有未跟踪的文件)。 That's why git merge
normally requires a "clean" state before it is willing to start.这就是为什么
git merge
通常需要“干净”的 state 才能开始。 The internal git merge-recursive
command is not so careful, and it's possible to start a merge with index and/or working tree in states that cannot be recovered by stopping the merge after all, if you run git merge-recursive
—as, eg, git stash apply
does.内部
git merge-recursive
命令不是那么小心,如果您运行git git merge-recursive
— 例如, git stash apply
可以。
Well, the final question (at least, current version of the question) can be achieved like this (assuming some-branch
is on A and other-branch
is on revision-6):好吧,最后一个问题(至少是问题的当前版本)可以这样实现(假设
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
And there you have it.你有它。
When I merge dev on master, I usually use --squash to get rid of the irrelevant "development" commits
当我在 master 上合并 dev 时,我通常使用 --squash 来摆脱不相关的“开发”提交
You are not accomplishing your stated purpose.你没有实现你的既定目的。 You are not getting rid of any commits.
你没有摆脱任何提交。
Moreover, a squash merge is not a merge at all, and it does not make any connection between this branch and the other branch.而且,squash 合并根本不是合并,它不会在这个分支和另一个分支之间建立任何联系。 Thus the merge base never moves and the history is lost.
因此,合并基础永远不会移动,并且历史记录会丢失。 That's why, as I say here, https://stackoverflow.com/a/67609758/341994 , squash merges and long lived branches are opposites.
这就是为什么,正如我在这里所说, https://stackoverflow.com/a/67609758/341994 ,壁球合并和长寿分支是相反的。
What you are describing is that you would squash the development commits first to simplify the history, and then merge with a true merge.您所描述的是,您将首先压缩开发提交以简化历史记录,然后与真正的合并合并。 In other words, stop using
merge --squash
and instead use squash (using reset or interactive rebase) and then merge.换句话说,停止使用
merge --squash
而是使用 squash(使用重置或交互式变基)然后合并。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.