我在2周前创建了一个功能分支(父分支是v8.2)。现在我在功能分支中进行了很少的代码更改。我必须将此功能分支与其父分支集成。
我可以按照以下步骤操作:
git checkout -b "topic branch" origin/v8.2
git merge feature-branch
并告诉我们这样做的区别
git checkout -b "topic branch" origin/feature-branch
git merge parentbranch
请告诉我,因为我是git的新手,这有点令人困惑。
答案 0 :(得分:2)
首先,空白 - 空格一般是not allowed in branch names:
git checkout -b "topic branch" origin/v8.2
我认为你试图用引号来暗示你还没有在这里选择一个特定的分支名称,但它确实很重要,并且假设没有证据的东西通常不是好的程序(在现实生活中如同以及在法庭上:-))。
让我们将其更改为:
git checkout -b foobranch origin/v8.2
并将其与之对比:
git checkout -b foobranch origin/feature-branch
这两个都创建了一个新的分支名称。差异(如果有的话)可能存在 no 差异,在一般情况下 - 新名称指向的提交ID。
在运行任一命令之前,请运行这两个命令并记下它们的输出:
git rev-parse origin/v8.2
git rev-parse origin/feature-branch
每个命令的实际输出将是一些40个字符的SHA-1哈希值。我们只是说,为了便于打字和阅读,第一个产生a000000...
(所有.
都易于阅读和记忆,但我已经忘记了:-) ),第二次打印c000000...
。
这些明显不同。他们 的是"真正的名字"两个提交。
接下来,您应绘制提交的图表,或至少其中一些提交。绘制图形并不是很有趣,但有必要了解Git将要做什么。幸运的是,有些程序会为您绘制图形,尽管它们往往会绘制非常混乱的图形。
这是我的绘图,最多只是一个猜测,并且基于这个主张:
我在2周前创建了一个功能分支(父分支是v8.2)。现在我在功能分支中进行了很少的代码更改。
我猜,现在,feature-branch
是您在两周前创建的功能分支的名称,origin/v8.2
仍然指向相同的提交它两个星期前指出。
也就是说,两周前,如果您运行git rev-parse origin/v8.2
,它仍会显示a000000...
。
此外,我猜测从那以后你只做了两次提交(数字不重要,它只影响图形绘制),并且你最近在某个时候完成了git push origin feature-branch
。
然后,你现在可以画出这样的东西:
...--A <-- origin/v8.2
\
B--C <-- feature-branch, origin/feature-branch
换句话说,名称 origin/v8.2
指向提交a000000...
(为了图形绘制的目的,我进一步压缩到了信A
)。同时,名称 origin/feature-branch
指向提交c000000...
(又名C
),事实上,名称feature-branch
也指向同一个提交。
请注意,每个提交都会返回其父提交。此处C
指回B
,B
指回A
。 (--
连接器应该是箭头<-
,但是很难绘制正确的箭头,特别是当它们需要像{{1}中那样指向左上角时} B
,所以我们只在这里使用行。)
A
所做的是使用人类可读的名称,如git rev-parse
,并将其转换为其中一个哈希值。这基本上是所有, 1 ,人类可读名称的要点是,它们是人类可读的。
运行origin/v8.2
时,您要求Git创建新分支名称,指向同一提交一些现有的名字。您可以使用原始SHA-1哈希ID替换 git checkout -b new-name existing-name
部分,并获得完全相同的效果。
在我们创建更多分支名称之前,请注意您可以执行此操作:
existing-name
如果您运行此命令,Git将检查git checkout origin/v8.2
名称的提交。在我的绘图中,这是提交origin/v8.2
(或提交a000000...
非常短)。但是,A
命令还会打印一条有关进入&#34;分离的HEAD&#34;模式。现在checkout
不再是分支名称;相反,它有一个原始提交ID,因此您当前的提交是HEAD
。
这意味着你不再&#34; on&#34;分支。 2 如果这有点可怕,您可以运行a000000...
来创建新分支。请注意,此表单未指定特定提交。这意味着&#34;使用当前提交&#34;。当前提交的内容是什么?好吧,我们刚才说它是git checkout -b newbranch
。因此,这会使a000000...
指向提交newbranch
,然后转到新分支a000000...
。
它非常简单:你是一个分支机构&#34;如果newbranch
包含分支名称而不是原始提交哈希。如果HEAD
说HEAD
,则ref: refs/heads/master
会on branch master
。git status
会说ref: refs/heads/feature-branch
。如果它显示feature-branch
,则表示您a000000...
。但是如果它有一个像...--A <-- origin/v8.2
\
B--C <-- HEAD->foo
这样的原始提交哈希,你就不会在任何分支上#34;
当您进行新提交时,会出现在分支机构上的优势。
让我们再次回到那个图表。我将使用略有不同的一个,具有相同的一般形状,但有一个不同的名称:
HEAD
这一次,我也用HEAD
名称撰写,以便我们知道foo
名称分支foo
,这意味着我们会{{1} }}。现在让我们做一个新的提交:
$ ... modify some files ...
$ git commit -a -m new-commit
以便我们获得新的提交D
:
...--A <-- origin/v8.2
\
B--C--D <-- HEAD->foo
名称foo
现在指向提交D
,我们可以通过运行git rev-parse foo
来判断。名称HEAD
仍然显示foo
:我们还没有改变我们所依赖的分支。我们已经更改的内容是我们添加了新的提交D
并将foo
(当前的分支名称)指向D
。
如果我们在同一个存储库中执行所有操作,我们仍然有feature-branch
和origin/feature-branch
。他们怎么了?
什么也没有!让我们画它们 - 我必须向下移动D
只是为了挤压它们:
...--A <-- origin/v8.2
\
B--C <-- feature-branch, origin/feature-branch
\
D <-- HEAD->foo
它是:那是(本地)分支的特殊属性。 (你不能得到&#34; on&#34;远程跟踪分支;如果你尝试,你得到那个&#34;分离的HEAD&#34;事情。所以这个属性是普通分支独有的。)
在解决合并问题之前,您确实需要了解上述所有内容,因为git merge
可以做的不仅仅是一件事。
合并提交是一个包含两个(或更多)父项的提交。在图表中,它看起来像M
:
...--A--E--F--M
\ /
B----C
它不是只有一个父箭头指向C
或F
,而是有两个,一个指向每个父提交。
您可能希望git merge
进行此类提交,但确实如此 - 但仅在必要时。
我们之前提到了我们的图表:
...--A <-- origin/v8.2
\
B--C <-- feature-branch, origin/feature-branch
如果我们现在&#34;在功能分支&#34; (根据需要添加HEAD->
)我们说git merge origin/v8.2
,Git将 。 (好吧,它会计算一下,说&#34;已经是最新的,没有什么可以合并&#34;,然后什么都不做。)
原因是提交A
中没有提交C
的工作。请注意,提交C
的父级为B
且B
的父级为A
:这意味着B
可能会有一些更改来自A
,而C
可能来自B
,但A
没有任何内容:他们已经在那里。更准确地说,A
是C
的祖先(在这种情况下,是祖父母)。所以没有工作可以合并而无需做任何事情。
请注意,要到达此处,git merge
首先必须在rev-parse
上运行origin/v8.2
才能发现它指向提交A
。这是理解git merge
的关键之一:无论你给出什么参数,它都会转换为某些特定提交。
好的,假设我们检查一个新的分支并指出它提交A
:
git checkout -b temp origin/v8.2
现在图纸看起来像这样:
...--A <-- origin/v8.2, HEAD->temp
\
B--C <-- feature-branch, origin/feature-branch
图完全相同,但HEAD
指向temp
,temp
指向提交A
。所以现在我们可以运行:
git merge feature-branch
或:
git merge origin/feature-branch
这次Git会将字符串转换为提交C
的原始ID,而提交C
不是A
的祖先。事实上,恰恰相反,A
是C
的祖先,正如我们所看到的那样,合并没有做任何事情。
好吧,这次git merge
做了一些事情,但它仍然没有合并提交。
相反,它可以简单地&#34;滑动分支名称&#34; (temp
)&#34;快进&#34;:
...--A <-- origin/v8.2
\
B--C <-- feature-branch, origin/feature-branch, HEAD->temp
请注意,图表中仍然没有变化。 Git所做的只是移动temp
,因此它现在指向提交C
。
让我们再来一个新分支:
git checkout -b t2 origin/v8.2
绘制图表:
...--A <-- origin/v8.2, HEAD->t2
\
B--C <-- feature-branch, origin/feature-branch, temp
现在进行新的提交,我们称之为D
:
git commit --allow-empty -m dummy
再次绘制图形:
D <-- HEAD->t2
/
...--A <-- origin/v8.2
\
B--C <-- feature-branch, origin/feature-branch, temp
(你可以开始明白为什么图形绘制程序要么复杂,要么产生不良的图形 - 我必须仔细设计这个设置以使图形足够简单来绘制!)。现在,在t2
上,让我们让git与temp
或feature-branch
或origin/feature/branch
进行新的合并。我们选择哪一个并不重要:合并行为需要相同的工作,因为它们最终都指向提交C
。
现在,既然我们在分支t2
上指向提交D
,并且我们正在合并提交C
,则git merge
必须这样做这次有些真正的工作。原因是C
不是D
的祖先,D
不是C
的祖先。但是,它们都有 common 祖先,即提交A
。
git merge
现在做的是比较提交A
和提交D
,看看我们在分支t2
上发生了什么变化,还比较了提交A
vs commit C
,看看他们是谁 - 他们&#34;他们&#34;可能会改变所有这一分支的另一面。
然后尝试使用一个对代码一无所知的简单文本比较算法来组合这些更改。如果它认为它成功地组合了这些更改,它就会生成一个新提交,一个 merge 提交,它有两个父项D
- 我们在启动时所处的提交 - {{1} },我们命名的提交。
新提交的日志消息取决于我们使用的可读名称,但图表和合并结果不是:它们仅依赖于提交C
,合并base,并提交A
和D
,本地提交和其他提交。所以我们得到了这个:
C
D------M <-- HEAD->t2
/ /
...--A <-- origin/v8.2
\ /
B--C <-- feature-branch, origin/feature-branch, temp
如果我们没有提交--no-ff
,但我们想强制合并提交怎么办?
这是D
的用武之地。使用git merge --no-ff
指令告诉git它应该弄清楚要合并的内容。如果没有任何合并,请像往常一样停止。如果有要合并的东西,并且需要定期合并,请像往常一样进行常规合并。但是 - 这是特殊情况 - 如果它可以进行快进,就像我们的分支--no-ff
一样,不应该。它应该与两个父母进行真正的合并提交。
这可能就是你想要的。或者,您可能会对快进合并感到满意。而且,所有这些都取决于提交图。我根据我做出的一系列假设绘制了这张图。他们是对的吗?我不知道;只有你(OP)可以告诉你。
1 实际上,它有很多选项,但是将名称翻译成ID至少是它原来的,仍然是最核心的功能。
2 由于各种原因,Git有一个特殊情况:你不在名为的分支上,但是你是特殊的匿名(未命名)分支,您可以通过名称temp
来引用。换句话说,HEAD
始终是您当前的分支,即使您没有当前分支。匿名分支的问题在于,当HEAD
正常分支返回分支时,它会自动消失。这个&#34;问题&#34;也是一个非常重要的功能:例如,Git使用匿名分支来实现交互式rebase。但大多数情况下你不需要知道这一点。