简体   繁体   English

为什么 git 合并不创建共同祖先?

[英]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:我的合并策略执行以下操作: 图 1

Is there anyway to get the following, without deleting and recreating the dev branch?无论如何都可以获得以下内容,而无需删除和重新创建开发分支?

在此处输入图像描述

TL;DR TL;博士

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形式的消息没有实际价值)。

Long

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-pickgit 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:要了解所有这些部分是如何相互作用的,我们还需要记住一些事情:

  • Git actually builds new commits from what is in Git's index . Git 实际上是从 Git 的index中构建新的提交。
  • The index gets expanded during a merge-as-a-verb operation.在合并为动词的操作过程中,索引会被扩展。 Now, instead of holding one copy of each file, it holds three .现在,它不再保存每个文件的一份副本,而是保存三个. 3 3
  • If Git stops in the middle of a conflicted merge, it leaves various trace files, such as MERGE_HEAD , MERGE_MSG , CHERRY_PICK_HEAD , and so on.如果 Git 在冲突合并的中间停止,它会留下各种跟踪文件,例如MERGE_HEADMERGE_MSGCHERRY_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 --continuegit 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:它:

  • starts the merge process, like git merge would;开始合并过程,就像git merge一样;
  • has an implied --no-commit , so that it stops before committing;有一个隐含的--no-commit ,因此它在提交之前停止; and/but但是
  • it does not create any "merge going on" files, so that the status after stopping is that git merge --continue is not allowed, and git commit will make an ordinary commit, not a merge commit.不会创建任何“merge going on”文件,因此停止后的状态是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:答案是:不是那么多,但在一种情况下它绝对是有意义的:那就是当你:

  • create a branch for experimentation;创建一个实验分支;
  • do your experimenting by writing multiple commits;通过编写多个提交来进行实验;
  • at the end of the experimenting, decide that the result is good, but it should just be one commit;在实验结束时,确定结果是好的,但它应该只是一次提交; and
  • want to easily make that one commit.想要轻松地做出那一次提交。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM