我对下一个例子有一些奇怪的git rebase行为。 假设我有本地树:
∨
b1--b2--b3
/
a1--a2--a3
和远程树
b1--b2--b3
/
a1--a2--a3--a4--a5--a6
分支a是主人
目前,我正处于b3。 我打电话给下一个命令:
git pull origin master master // fixed typo
git rebase master
在这个动作之后,我有了树,看起来像:
a4'--a5'--a6'--b1--b2--b3
/
a1--a2--a3--a4--a5--a6
代替
b1--b2--b3
/
a1--a2--a3--a4--a5--a6
为什么会发生?
答案 0 :(得分:5)
我总是建议人们避免git pull
,直到他们对Git非常熟悉,因为它做得太多,有点神秘,很难解释。它经常(并且相当准确地)描述为等同于运行git fetch
后跟git merge
或git rebase
,但棘手的部分是它传递给{{1的参数或git merge
不是您所期望的。
那就是说,让我们打破这些,仔细观察每一步。我还会以不同的方式绘制您的提交图。 (你称之为"树"上面,这不是最好的术语,原因有二。首先,提交图是图,特别是 D 已经 A 循环 G raph或DAG。所有树都是DAG,但并非所有DAG都是树。其次,Git使用术语树来引用"树对象&#34 ;;每个提交都带有一个树对象。)
我认为,您的初始提交图表看起来像这样:
git rebase
请注意,您的本地分支名称 B1--B2--B3 <-- branch (HEAD)
/
A1--A2--A3 <-- master, origin/master
指向提交master
。您的远程跟踪分支名称A3
也指向提交origin/master
。但是,您当前的分支名为A3
(我必须创建此名称)并指向提交branch
。
您现在运行B3
,或者git pull origin master master
(这与您使用哪些相关,而您的评论与您的问题相矛盾)。此git pull origin mastert master
命令使用多个参数运行git pull
。
git fetch
步骤获取新提交每当您运行git fetch
时,包括使用git fetch
运行git pull
时,您的Git会调用另一个Git,可能是存储在您的远程名称git fetch
下的网址。这里 - 由origin
或者git pul origin master master
运行 - 这个其他Git已经提交了几个新的提交。你的Git将这些新提交存储在你的存储库中,如果你的Git至少是1.8.2版,你的Git会更新你的git pull origin mastert master
(也许是你的origin/master
)以指向这些新的提交。
您的Git还会在特殊文件origin/mastert
中存储新提交的ID以及从远程获取的分支头的名称。因此,我们可以将其添加到图形绘图中:
FETCH_HEAD
B1--B2--B3 <-- branch (HEAD)
/
A1--A2--A3 <-- master
\
A4--A5--A6 <-- origin/master, FETCH_HEAD
中的内容有点棘手。此文件的内容包括原始名称,即FETCH_HEAD
和master
,如其他 Git所示,没有任何mastert
重命名你的Git通常使用。标签也可能有一些行。其中一些行也可以使用origin/
进行注释。所有行都包含特定Git对象(提交,有时是标记)的哈希ID。 not-for-merge
程序专门为git fetch
程序留下这些行进行检查。
接下来,您的git pull
命令从git pull
中提取新获取的提交的哈希ID。也就是说,它会在FETCH_HEAD
中搜索不标记为FETCH_HEAD
的提交的哈希ID。 (我假设你在做一个合并样式拉取而不是一个rebase样式拉。)然后它运行带有这些哈希ID的not-for-merge
。
git merge
步骤进行合并提交同样,现在重要的是您是git merge
还是git pull origin master master
。如果您运行后者,并且有一个名为git pull origin mastert master
的分支,则会有两个匹配行,可能还有两个不同的哈希ID。在这种情况下,您的mastert
命令将执行这两个哈希的章鱼合并。
如果没有,您将获得一个哈希的正常合并。我假设你得到一个正常的合并。结果如下:
git pull
请注意,您的分支名称 B1--B2--B3------M <-- branch (HEAD)
/ /
A1--A2--A3 <-- master /
\ /
A4--A5-----A6 <-- origin/master, FETCH_HEAD
仍然指向提交master
,而不是提交A3
。
您现在运行A6
。
git rebase master
做的是复制提交。它复制的提交是您指定的提交。新副本的目的地也是您指定的目的地。
当你跑步时:
git rebase
您告诉Git要复制当前分支上的任何提交(即git rebase master
),这些提交不能从您的分支{{ 1}},这不是合并提交(你将复制提交从合并,但不是合并本身)。请注意,这不是branch
,而是master
。因此,您要求Git复制的提交是origin/master
,master
,B1
,B2
,B3
和A4
。以某种顺序(实际的顺序有点难以预测,虽然A和B组将被复制在一起,因为Git在收集要复制的提交的ID时使用A5
。
您要求Git复制它们的地方是在A6
&#34;的提示之后,即在--topo-order
之后。
如果Git首先复制master
,它通常会保持A3
不变,因为如果可以,B1
会这样做。如果它首先复制B1
,它也可以尝试保持git rebase
不变,原因相同;但实现这一目标的机制被这一特定的变基所挫败(见下面的评论)。 Git将A4
复制到A4
后,必须将A4
复制到其父作为A6
B1
副本的新提交中(此提交与原始A6'
不同,因为A6
的父级为B1
)。
(但是,您可以强行B1
进行复制,例如A3
或git rebase
。
您的结果表明您的Git首先选择复制--no-ff
。这给你:
--force
如果没有A4
或 A4'-A5'-A6'-B1'-B2'-B3' <-- branch
/
A1--A2--A3 <-- master
\
A4--A5--A6 <-- origin/master
,如果--force
稍微聪明一点,它可以(但不会)产生:
--no-ff
或(可以,有时确实)产生:
git rebase