假设您在处理功能分支时从远程存储库中提取,并且新提交已添加到主分支。
您完成功能分支的工作,提交您的更改,然后将其合并/重新绑定到主服务器上。在主分支中,在提交的提交之前或之后插入了功能提交?如果你从未进行拉动,它们会插在哪里?它会有相同的结果吗?
此外,如果您将master合并/ rebase到功能分支而不是相反的方式会发生什么?如果你在拉动之前或之后合并/重新组合,那么master插入到功能分支中的位置?它会有相同的结果吗?它的历史与上述功能合并/重组到主人的历史有何不同?
更简洁的是,合并的方向和合并的顺序是否会对最终的合并产生不同的结果?我怀疑没关系。
ASIDE:这个问题可能很容易回答给Gits它的人,但是有数百个(如果不是数千个)文档和教程资源,我从未见过直接解决它。在不知道答案的情况下,Git可能会非常容易理解。
因为操作的顺序在一些Git概念和一般的编程中很重要,所以它可以使某人得出结论它有所作为。此外,我们通常倾向于将修订视为按时间顺序发生,并期望Git以相同的方式工作。用户界面和命令行也通过暗示操作顺序来发挥这种感知。
其他版本跟踪工具可以更好地抽象出这种认知腐蚀性操作。
此外,我们通常倾向于将修订视为按时间顺序发生,并期望Git以相同的方式工作。
这是一个可怕的错误。 Git按图形顺序工作,而不是按时间顺序。当图表顺序是按时间顺序 - 有时,但不总是 - 然后它可以正常工作。
(我甚至不打算在这里讨论rebase。有关rebase的正确讨论,请参阅之前的帖子。我将专注于合并中的对称性,以及接缝可以显示的位置。)
更简洁的是,合并的方向和合并的顺序是否会对最终的合并产生不同的结果?我怀疑没关系。
答案是否定和是。
要理解为什么这是答案,你需要了解Git的几个方面。
首先,让我们考虑Git如何处理提交,而不是其他人做多少(大多数其他人?所有其他人?必须至少有一个其他VCS也这样做......)。在版本控制系统中,在分支中进行提交的过程会将新提交添加到分支;但是在Git中,一旦创建了一个提交的分支集合就可以成为动态变化的东西。也就是说,提交一旦完成,就独立于任何分支,并且如果您愿意,可以在任何分支上进行。
例如,在Mercurial,这是不可能的。虽然Mercurial在许多方面与Git非常相似,但提交的存在需要其分支的存在。提交是在该分支上进行的,并且永远是该分支的一部分。这是持有提交的唯一分支:提交永久地附加到其分支。
在Git和Mercurial中,每个提交都有一个唯一的ID,每个提交存储其父(单数)提交的唯一ID(如果它是普通提交)。通过遵循这个向后看的链,从最后一次提交开始并向后工作,我们可以在分支上找到提交的历史记录:
... <-grandparent <-parent <-child
在Mercurial中,这些提交永远在他们的分支上,所以我们可以在左边写下分支名称:
branch-A: ...--I--J
在Mercurial中查找最新的提交很简单,因为提交在本地存储库中有一个简单的序列号(以及用于在存储库之间共享的唯一哈希ID)。
但是在Git中,分支名称是可移动的。在提交J
之前,名称branch-A
存储commit I
的原始哈希ID:
...--H--I <-- branch-A
在进行新提交时,Git只是将新提交的哈希ID写入分支名称,以便分支指向新提交:
...--H--I--J <-- branch-A
在版本控制中,合并,动词或合并某些提交是一个过程。结果 - 至少在Git和Mercurial中 - 是一个合并提交,我们使用单词merge作为形容词修改提交,或简称合并(名词)。
合并提交是合并提交的原因是他们有两个父母,将两行开发结合在一起:
...--I--J
\
M
/
...--K--L
在这里,Git和Mercurial基本上是相同的,有一个非常重要的区别:在Mercurial中,合并提交专门在一个分支上,即当你运行hg merge
时你所处的分支。提交 - 包括合并提交 - 永久地附加到其分支机构。一旦合并过程完成并且合并提交存在,那个合并提交就在那个分支上。在Git中,合并提交进入当前分支,但因为我们可以移动名称,在某种意义上它并不重要。
但我们必须看两个部分:动词部分,合并,然后紧密地在名词部分,合并。 (旁白:Git还允许合并提交有两个以上的父级,而Mercurial将每个合并限制为两个父级。没有什么是这些时髦的合并,Git称之为章鱼合并,可以做一些你不能做的一对成对合并,但在某些情况下,章鱼合并可以更清楚地显示意图。)
例如,假设branch-A
和branch-B
在合并之前是这样的:
...--I--J <-- branch-A
...--K--L <-- branch-B
在我们合并这些之前,我们必须追溯这两个历史,找到合并基础,这两个开发线的共同提交是分歧的。 (在Mercurial中也是如此。)所以让我们填写一下:
I--J <-- branch-A
/
...--H
\
K--L <-- branch-B
在这里,提交H
是常见的起点。在Git中,提交H
同时在两个分支上(但不在Mercurial中)。尽管如此,两个版本控制系统都以完全相同的方式完成合并过程:首先选择一个提交签出,例如使用J
或git checkout branch-A
提交hg update branch-A
。然后使用命令的merge
动词:git merge branch-B
或hg merge branch-B
选择另一个提交。 VCS找到合适的合并库,即提交H
,并将H
的内容与J
的内容进行比较,看看你改变了什么:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed
并重复这个相同的比较,找到他们改变了什么:
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed
Git将这些更改组合成一个大的“所有更改”,将这些更改应用到基础(提交H
),并进行新的提交。
(Mercurial也做了很多相同的事情,尽管Mercurial如何知道重命名方面存在一些非常重要的差异。这些只在你自合并基础以来重命名了一些文件时才有意义。如果你重命名了文件,并且有重命名/重命名冲突,合并顺序变得非常重要。但我们假设没有重命名问题。)
这里最有趣的是你可以控制变化的组合方式。在Git中,您可以通过合并策略和扩展策略参数来完成此操作;在Mercurial中,您可以通过选择的合并工具执行此操作。如果您使用“我们的”策略/工具,VCS将完全忽略其更改:组合更改只是您的更改,因此VCS使用与当前提交中相同的代码进行新的合并提交。这里的合并顺序显然很重要:如果我们忽略了他们的变化,我们最好确定谁是“我们”!
即使没有“我们的”策略,您的更改与其更改之间也可能存在冲突。如果是这样,可以告诉Git和Mercurial:更喜欢我的或者更喜欢他们的。这些将给出不同的结果,因此这里再次合并顺序很重要。当然,在这一点上有一个对称的选择:选择我的或选择他们的。如果你交换角色,你可以交换选项 - 所以当订单很重要时,它就不那么重要了。
让我们假设没有冲突,没有特殊的我们/他们的事情发生,并运行git checkout branch-A; git merge branch-B
。如果一切顺利,VCS使合并提交M
与其两个父母:
I--J
/ \
...--H M <-- branch-A
\ /
K--L <-- branch-B
在Mercurial中,合并顺序在这里很重要,因为一旦提交了M
,它就永远停留在它的分支上。但Git让我们在事后移动分支名称。我们可以像这样制作M
,把它放在原处,再推回branch-A
一步指向J
,然后将branch-B
向前移动指向M
,给出:
I--J <-- branch-A
/ \
...--H M <-- branch-B
\ /
K--L
这与我们做git checkout branch-B; git merge branch-A
时会得到的几乎相同。所以看起来在这里,合并顺序是无关紧要的(假设在动词部分没有任何障碍)。但是这里的图表中缺少一些东西,这就是第一个和非第一个父母的概念!
在Git和Mercurial中,合并提交M
的第一个父级是“同一分支”父级。也就是说,因为我们在运行合并时提交了J
,所以M
的第一个父级是提交J
。这使得L
成为第二个父母。如果我们在Git中推送分支标签,我们可以告诉我们已经这样做了,因为第一个和第二个父母仍然是另一个顺序。
所以我们在这里看到,对于Git,即使合并动词没有排序问题,最终的合并提交(形容词或名词)确实有一个顺序。由您(用户)决定该订单对您是否重要。如果不是,则可以使用对称性。如果提交父订单很重要,合并的方向/顺序在Git中与在Mercurial中一样重要。
在任何基于图形的版本控制系统中 - 因此在Git和Mercurial中 - 图形是历史。特别是在Git中,没有文件历史记录这样的东西;图表不仅是主要历史,而且是唯一的历史。 (Mercurial以更传统的方式存储文件数据,因此存在提交历史记录和文件历史记录,但是如果没有提交历史记录,文件历史记录将无用。)
为了能够理解Git的作用和原因,您需要了解足够的图论来驾驶或乘坐公共交通工具。这并不是那么多 - 但如果你没有意识到Git有这些单行铁路/街道/桥梁,从最后的提交回到第一个,很多事情都没有意义。 Git从最后开始,然后向后工作。分支名称可以让您进入分支末端的图形。其他一切都是提交图的功能。
您完成功能分支的工作,提交您的更改,然后将其合并/重新绑定到主服务器上。
在主分支中,在提交的提交之前或之后插入了功能提交?
之后:merge / rebase将考虑主分支的当前状态。 如果该分支已经进化(使用pull),那么merge / rebase将使用当前的主HEAD(现在引用提取的提交)
如果你从未进行拉动,它们会插在哪里?
相同:它将使用当前主HEAD(不反映任何新提交,因为没有完成提取)
它会有相同的结果吗?
不,考虑到主HEAD在两种情况下都不同。
此外,如果您将master合并/ rebase到功能分支而不是相反的方式会发生什么?
首先,不要:这不是最佳做法。 但是如果你这样做,那个merge / rebase将使用HEAD功能。
如果你在拉动之前或之后合并/重新组合,那么master插入到功能分支中的位置?它会有相同的结果吗?
同样的结果,因为在这两种情况下,它都使用了HEAD功能,当您执行(或不执行)拉动主控时,它没有改变。
合并时,远程和本地提交都完整保留,并且合并提交引用它们。
重新绑定时,会保留远程提交(或者至少应保留它们!),并在其上应用本地提交。永远不要试图强迫git向另一个方向转折,这是灾难的秘诀。
有关merge和rebase差异的更多信息,请阅读此答案What's the difference between 'git merge' and 'git rebase'?