git rebase奇怪的行为

时间:2017-06-28 06:32:54

标签: git rebase

我对下一个例子有一些奇怪的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

为什么会发生?

1 个答案:

答案 0 :(得分:5)

我总是建议人们避免git pull,直到他们对Git非常熟悉,因为它做得太多,有点神秘,很难解释。它经常(并且相当准确地)描述为等同于运行git fetch后跟git mergegit 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_HEADmaster,如其他 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

Rebase剥离合并

git rebase master做的是复制提交。它复制的提交是您指定的提交。新副本的目的地也是您指定的目的地。

当你跑步时:

git rebase

您告诉Git要复制当前分支上的任何提交(即git rebase master ),这些提交不能从您的分支{{ 1}},这不是合并提交(你将复制提交合并,但不是合并本身)。请注意,这不是branch,而是master。因此,您要求Git复制的提交是origin/mastermasterB1B2B3A4。以某种顺序(实际的顺序有点难以预测,虽然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进行复制,例如A3git 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