TL;DR: git merge --squash
合并(动词),但不进行合并(名词)。
您提到的“合并标头”不在该形式的提交中。相反,它只是一些东西git log
当遇到一个时打印合并提交。为了真正巩固这个想法,让我们看看什么是合并does——“合并”意味着什么verb——真是一次合并is。更准确地说,让我们看看什么是合并提交是,或者“合并”意味着什么形容词修改“提交”一词。不过,我们会像优秀的 Git 用户一样偷懒,并将其从“合并提交”缩短为“合并”,使其成为一个名词。
合并为名词
Let's look at the noun usage first, since that's simpler. In Git, a merge commit is simply a commit with at least two parent commits. It really is that simple. Every commit has some number of parents. A root commit has zero parents (and most repositories have only one root commit, which is the first commit ever made, which obviously cannot have had a parent commit). Most ordinary commits have just one parent, and all the other commits have two or more parents1 and are therefore merge commits. Then we shorten the phrase "merge commit" to just "a merge", changing "merge" from an adjective (modifying "commit") to a noun (meaning "a merge commit").
If a 合并提交是两个父母的承诺,那么git merge
必须进行合并提交,并且to merge应该意味着“进行合并提交”——确实如此,但是只是有时。我们会看到when——更重要的是,why—soon.
1Other VCSes may stop at two. Mercurial does, for instance: no hg commit ever has more than two parents. Git, however, allows any commit to have any number of parents. A Git commit with three or more parents is called an octopus merge, and normally these commits are made using git merge -s octopus
or equivalent, e.g., git merge topic1 topic2 topic3
: running git merge
with extra commit specifiers implies -s octopus
. These do nothing that cannot be done using a series of two-parent merges, so Git's octopus merges do not give it more power than Mercurial, but octopus merges are sometimes convenient, and good for showing off your Git Fu. :-)
合并为动词
The verb form of to merge is much more complicated—or at least, it is in Git. We can distinguish between two major forms of merging as well: there's the act of merging source code changes, and then there's the act of merging branches. (This brings in the question of "what is a branch". There's a lot more to this question than you might think, so see What exactly do we mean by "branch"?) Many Git commands can do the verb kind of merging, including git cherry-pick
and git revert
, both of which are essentially a form of git apply -3
.2 Of course git merge
is the most obvious way to do it, and when it does, you get the verb form of merging changes.
合并变更
Merging source changes is more properly called "three way merging". For more about this, see the Wikipedia article3 or VonC's answer to Why is a 3-way merge advantageous over a 2-way merge? The details can get quite complex, but the goal of this merge process is simple enough: we want to combine changes made to some common base, i.e., given some starting point B, we find changes C1 and C2 and combine them to make a single new change C3, then add that change to the base B to get a new version.
In Git, the act of merging sources—of doing the three-way merge itself—uses Git's index, also called the "staging area". Normally, the index has just one entry for each file that will go into the next commit. When you git add
a file, you tell Git to update the staged version of the file, replacing the one in the current index, or if the file was previously "untracked", adding the file to the index so that it is now tracked and also staged for the next commit. During the merge process, however, the index has up to three entries for each file: one from the merge base version, one entry for the file-version to be treated as change #1 (also called "ours"), and one for change #2 ("theirs"). If Git is able to combine the changes on its own, it replaces4 the three entries with one regular (staged-for-commit) entry. Otherwise it stops with a conflict, leaving the conflict marked in the work-tree version of the file. You must resolve the conflict yourself (presumably editing the file in the process), and use git add
to replace the three special conflicted-merge index versions with the one normal staged version.
一旦所有冲突都解决了,Git 将能够进行新的提交。
进行合并提交
最后一件事是正常的合并git merge
所做的就是进行合并提交。同样,如果没有冲突,Git 可以自行执行此操作:git merge
合并更改,git add
s 每个合并结果更新暂存文件,并运行git commit
为你。或者,如果存在冲突,你可以解决它们,you run git add
, and you run git commit
。在所有情况下,实际上都是git commit
, 而不是git merge
本身,这使得合并提交。
最后一部分实际上非常简单,因为索引/暂存区域都已设置完毕。 Git 只是像往常一样进行提交,只不过不是给它当前的 (HEAD
) 提交的 ID 作为其单亲,它至少给出了它two父提交 ID:第一个是HEAD
像往常一样提交,其余的来自于留下的文件git merge
(.git/MERGE
).
2The -3
in git apply -3
, which can be spelled out as --3way
, directs git apply
to use the index
string in git diff
output to construct a merge base if needed. When doing this with git cherry-pick
and git revert
, to turn them into merges (instead of straightforward patches), Git winds up using the parent commit of the cherry-picked or reverted commit. It's worth noting here that Git does this only on a per file basis, after treating the patch just as a simple patch has failed. Using the parent commit's file as a base version for a three-way merge will normally help only if that commit is an ancestor of the current (HEAD) commit. If it's not actually such an ancestor, combining the diff generated from "base" to HEAD with the patch being applied is probably not helpful. Still, Git will do it as a fallback.
3As usual for Wikipedia, I spotted some minor inaccuracies in it just now—for instance, it's possible to have more than two DAG LCAs—but don't have time to work on it, and it's not a bad overview.
4Often, it never bothers to make the conflicted entries in the first place. Git will, if possible, short-cut-away even the git diff
phase. Suppose for instance that the base commit has four files in it: u.txt
is unchanged from base in either commit, one.txt
is changed from base to HEAD
but not from base to the other commit, two.txt
is changed from base to the other commit but not from base to HEAD
, and three.txt
is changed in both. Git will simply copy u.txt
straight through, take one.txt
from HEAD
, take two.txt
from the other commit, and only bother to generate diffs, then try to merge them, for three.txt
. This goes pretty fast, but does mean that if you have your own special three-way-merge program for these files, it never gets run for u.txt
, one.txt
, and two.txt
, only for three.txt
.
我不确定 Git 是否在尝试合并差异之前或尝试失败后创建多个索引条目。但是,在运行自定义合并驱动程序之前,它必须创建所有三个条目。
非合并“合并”
上面的序列 - 检查一些提交(通常是分支提示),运行git merge
在另一个提交上(通常是一些other分支提示),找到合适的合并基础,制作两组差异,合并差异,然后提交结果——这就是正常合并的工作方式,以及 Git 进行合并提交的方式。我们合并(作为动词)更改,并进行合并(形容词)提交(或“进行合并”,名词)。但是,正如我们之前指出的,git merge
并不总是这样做。
快进
有时git merge
说它正在“进行快进合并”。这有点用词不当,因为“快进”更准确地被认为是分支标签更改,在 Git 中。还有另外两个命令使用此属性,git fetch
and git push
,它区分正常(或“快进”)分支更新和“强制”更新。对快进的正确讨论需要深入了解提交图的细节,所以我在这里要说的是,当您从提交中移动分支标签时,会发生这种情况O(旧)承诺N(新),并提交N已提交O作为祖先。
When git merge
检测到您的合并参数是这些情况之一 - 即HEAD
是另一个提交的祖先——它通常会调用这个快进操作。在这种情况下,Git 仅使用您告诉它进行合并的提交。没有new完全提交,只是重用一些existing犯罪。 Git 不进行合并提交,也不进行任何合并作为动词。它只是将您更改为新的提交,几乎就像git reset --hard
:移动当前分支标签并更新工作树。
You can suppress this fast-forward action with --no-ff
.5 In this case, git merge
will make a new merge commit even if a fast-forward is possible. You get no merge-as-a-verb action (there's no work to do) but you do get a new merge commit, and Git updates your work-tree to match.
挤压合并不是合并
请注意,我们在这里讨论了三种情况中的两种:
- 动词形式加形容词/名词形式:正常合并
- 合并的形容词/名词形式,但没有动词:可以快进的合并,但你跑了
git merge --no-ff
缺少的第三种情况是动词没有名词:我们如何得到action合并的意思,合并更改,没有合并的名词/形容词形式commit?这就是“挤压合并”的用武之地。git merge --squash <commit-specifier>
告诉 Git 像往常一样执行合并操作,但是not记录其他分支/commit-ID,以便最终git commit
进行正常的、非合并的单父提交。
真的就是这样——这就是它的全部作用!它只是在最后进行正常的非合并提交。奇怪的是,它迫使you做出承诺,而不是自己做出承诺。 (没有根本原因表明它必须这样做,而且我不知道为什么 Git 作者选择让它这样做。)但这些都是机制, not policies: 他们告诉你how进行各种提交,但不包括您要提交的提交should制造,或者何时,或者——最重要的是——why.
5You can tell git merge
that it should only proceed if it can fast-forward: git merge --ff-only
. If the new commit is fast-forward-able, git merge
updates to it. Otherwise it simply fails. I made an alias, git mff
, that does this, since normally I want to git fetch
and then see whether I need to merge, rebase, make a new branch entirely, or whatever. If I can fast-forward, I don't need to do anything else, so if git mff
works, I'm done.
使用哪种合并、何时使用、为什么使用
The why这个问题很难,就像所有哲学问题一样,没有一个正确的答案(但肯定是一堆错误的:-))。考虑这个事实:每次你使用git merge
无论如何,你could做了不同的事情却得到了相同的结果源代码与您最新的提交一起进行。成功的结果有以下三种git merge
(也就是说,您不进行合并git merge --abort
结束它,而是成功结束它):
- 快进:没有新的提交;源是现有提交的源。
- 真正的合并:新提交;源是组合源。
- 挤压合并:新提交;源是组合源。
The only difference between these three (aside from the obvious "no new commit at all" for the first one) is the record they leave behind in the commit graph.6 A fast-forward obviously leaves no record: the graph is unchanged from before, because you added nothing. If that's what you want, that's what you should use. In a repository where you are following someone else's work and never doing anything of your own, this is probably what you want. It is also what you will get by default, and Git will "just work" for you.7
If you do a regular merge, that leaves a record of the merge. All the existing commits remain exactly as they are, and Git adds one new commit, with two8 parents. Anyone coming along later will see just who did what, when, how, etc. If this is what you want, this is what you should do. Of course, some tools (like git log
) will show who did what, when, etc., which—by showing a complete picture of all of history—may obscure the Big Picture view with all the little details. That's both the up-side and the down-side, in other words.
如果你进行挤压合并,剩下no合并记录。您进行一个新的提交,将每个合并作为动词action,但新的提交不是名词合并。后来的任何人都会看到所有进入的工作,但看不到它来自哪里。类似的工具git log
cannot展示小细节,你和其他人都会得到only大图景。同样,这既有好处也有坏处。但缺点可能更大一点,因为如果你发现你need稍后这些细节,它们是不在那里。他们不仅不存在于git log
看来,他们也不存在未来git merge
.
如果你是never要做一个未来git merge
压缩的变化,这可能不是问题。如果您打算完全删除该分支,放弃所有单独的更改as个人并只保留单个集体挤压合并变化,这是做的“坏”部分git merge --squash
“坏值”基本上为零。但是,如果您打算继续在该分支上工作,并稍后再次合并它,则该特定的不良值会大大增加。
如果您专门进行壁球合并以制作git log
输出“看起来更好”(显示更多的大图,而不是用太多细节来模糊它),请注意,有各种git log
选项设计为可选择的 about 哪个承诺表明。尤其,--first-commit
避免完全遍历合并的分支,仅显示合并本身,然后继续沿着提交图的“主线”向下。您还可以使用--simplify-by-decoration
省略除tagged例如,提交。
6Well, also in your reflogs; but your reflogs are private, and eventually expire, so we'll just ignore them.
7This assumes that they—whoever "they" are—do not "rewind" their commit graph, by rebasing or removing published commits. If they do remove published commits, your Git will by default merge those commits back in, as if they were your own work. This is one reason anyone publishing a Git repository should think hard about "rewinding" such commits.
8Assuming no fancy octopus merges.