关于git pull的合并/ rebase步骤的混淆

时间:2015-12-29 01:22:30

标签: git

来自Loeliger 2ed的Git版本控制,关于git pull中的合并或重组步骤:

  

在拉动操作的第二步中,Git执行a   合并(默认)或rebase操作。

关于git pull中的合并步骤:

  

在这个例子中,Git合并了内容   远程跟踪分支,origin / master,进入您的本地跟踪   branch,master ,使用一种称为a的特殊类型的合并   快进

     

但Git是如何知道合并这些特定分支的呢?答案   来自配置文件:

[branch "master"]
        remote = origin
        merge = refs/heads/master
     

转述,这给了Git两个关键信息:当掌握时   是当前的签出分支,使用origin作为默认远程   在获取(或拉取)期间从中获取更新。   此外,在git pull的合并步骤中,使用refs / heads / master   从远程作为默认分支合并到此,主人   分支。

  • 一般来说,合并步骤是否将远程跟踪分支合并到master或当前分支?我的猜测是当前分支,即由HEAD指向的分支,并且不一定是master

    请注意我的猜测来自https://git-scm.com/docs/git-pull

      

    将来自远程存储库的更改合并到当前   分支即可。在默认模式下,git pull是git fetch的简写   然后是git merge FETCH_HEAD。

         

    更确切地说,git pull使用给定的参数运行git fetch   调用git merge将检索到的分支头合并到当前   分支

  • git pull是否始终将远程跟踪分支合并到当前分支中? (我的猜测是肯定的)。 如果没有,是否有git pull的参数在合并步骤中指定目标分支? 如果我是正确的,refspec的{​​{1}}参数不会指定合并步骤的目标分支。

  • 为什么合并是“快进合并”?

关于git pull中的rebase步骤:

  

命令git pull --rebase将导致Git重组(而不是   合并)您的本地跟踪分支到远程跟踪分支上   只是在拉动期间。

  • git pull的rebase步骤将当前分支(即git pull指向的分支)重新绑定到远程跟踪分支上是否正确? 如果是,为什么报价会说“你的本地跟踪分支”而不是当前的分支?

4 个答案:

答案 0 :(得分:2)

  

一般来说,合并步骤是否将远程跟踪分支合并为主分支或当前分支?

这两个是相同的:master是你当前的分支。远程跟踪分支是origin/master

(其中origin只是用于远程的默认名称,而不是任何特殊的Git"关键字",而master是默认分支。实际名称可能在实际上有所不同场景。)

rebasemerge操作的目标(结果更改的目标)是当前分支。

如果你试图像checkout origin/master那样做一些愚蠢的事情来试图让追踪分支成为现实,那么Git会打你。

因此,合并的目标是master。问题是,什么被合并?如果您提取一些新的上游材料,最终可能会出现(例如)这种情况:

     YOURS (master)             
             *
              \      UPSTREAM (origin/master)    
                *     *
                 \   /
                   *      <--- "git merge-base master origin/master"
                   |
                   *

masterorigin/master分歧,分别有2次和1次新提交。

使用git rebaseYOURS下的两个本地提交将重写在UPSTREAM之上:

      YOURS (master)             
              *       <-- rewritten: SHA changes
               \          
ghost of YOURS  *       <-- rewritten: SHA changes
      * ---- *    \
              \     * UPSTREAM (origin/master)
               \  /
                 *
                |

现在情况是&#34;本地分支master领先于origin/master 2次提交&#34;。您已准备好(尝试)git push

你原来的unrewritten提交仍然存在(用#34表示;你的幽灵和#34;在ASCII图中)。它们在git reflog中引用。当它们从那里到期或被手动清除时,它们变成垃圾,最终被垃圾收集。

使用git merge,您可以执行其他操作:您的更改将与origin/master合并,并创建一个包含两个父项的新提交:

                  * YOURS-merged (master)
                /  \
     YOURS    /      \          
             *        |
              \       |  UPSTREAM (origin/master)    
                *     *
                 \   /
                   *      <--- "git merge-base master origin/master"
                   |
                   *

现在,您的master提前origin/master提交1次。如果您现在成功推送,远程存储库的master也将采用上述形式,双父提交沿着一条路径进行更改,而前一个上游提交沿另一条路径进行。没有你自己的幽灵&#34;:你的改变从未改写过。

  

为什么合并是&#34;快进合并&#34;?

快速合并或重组在两种情况下发生。一个是origin/master没有任何平行变化:

     YOURS (master)             
             *
              \          
                *
                 \  
                   *    UPSTREAM (origin/master)
                   |
                   *

在上面的例子中,没有任何事情要做:一切都是最新的,你提前两次提交origin/master。因此,这不是一个快进的东西&#34 ;; git mergegit rebase根本不会做任何事情。如果您对这些提交感到满意,可以尝试git push

如果您没有任何本地更改,则会发生真正的快进场景:

                     * UPSTREAM (origin/master) 
                    /
    YOURS (master) *
                   |
                   *

在这种情况下,如果您执行git rebasegit merge,则默认情况下,master HEAD指针只会向前滑动&#34;指向与origin/master相同的提交,此幻灯片是&#34;快进&#34;:

        YOURS (master) *  UPSTREAM (origin/master)  
                     /
                    *  <- master slid forward from here
                    |
                    *

&#34;快进&#34;术语可能指的是只有HEAD指针移动的想法(以及重写本地文件系统树以匹配)。不得写入或重写新的提交。

在分歧场景中不可能进行快进(除非你丢弃本地更改)。必须生成新的合并提交,否则rebase必须重写一些更改。在进行这些操作之前,不存在master可以向前滑动的任何提交。

有些人以某种方式使用Git,他们希望将所有本地工作提交合并。当上游没有新工作要合并时,你可以强制Git用git merge --no-ff进行合并提交(没有快进)。从这开始:

     YOURS (master)             
             *
              \          
                *
                 \  
                   *    UPSTREAM (origin/master)
                   |
                   *

你得到这样的东西:

               ----* YOURS-merge   (master)
     YOURS    /    |  
             *     |
              \    |     
                *  |
                 \ | 
                   *    UPSTREAM (origin/master)
                   |
                   *

这个想法是,在历史的主线路径中,这两个变化似乎凝聚成一步。可以按照另一个父级查看具有多次提交的详细谱系。

  

为什么报价会说&#34;您的本地跟踪分支&#34;而是当前的分支?

这是一个错误。涉及的所有分支都是本地的(在您的存储库中)。有工作分支(如master)及其远程跟踪分支(如origin/master)。两者都是本地的。

你工作的分支不称为跟踪分支;它没有跟踪任何事情。

某些本地分支机构未与远程跟踪分支机构配对。没有远程的repo中的所有分支(例如由git init新创建的东西)都是纯粹本地的。除非推送到远程仓库,否则任何本地创建的分支都是纯本地的。

远程跟踪分支是一个类似分支的对象,它与本地分支配对。它跟踪上游正在做什么。每次执行git fetch时,都可能会重写它以指向某个不同的提交。本地分支不受影响。这两者可能会分歧,这可以通过rebase或merge来解决。当您执行git push时,本地分支会更新上游存储库中的实际远程。当此操作成功(远程端未拒绝)时,本地跟踪分支也会更新为指向同一个提交,因此它们保持同步。

在正常情况下,没有纯粹的本地操作会改变origin/master:它会跟踪远程仓库中相应分支的状态。

答案 1 :(得分:1)

正如我最近的其他评论一样,我想避免使用术语&#34;本地跟踪分支&#34;完全并且只关注本地分支是否有上游集合,如果是这样,上游设置的内容(作为您引用笔记的书,分为两部分,&#34;远程&#34;和&#34;合并&#34;字符串)。

关于你的第一个问题:

  

一般来说,合并步骤是否将远程跟踪分支合并为主分支或当前分支?

总是当前分支。 git pull命令(以前是一个脚本但最近在C中重写)实际运行git merge,而git merge始终以当前分支(和当前工作树)开始。< / p>

合并的内容有点棘手。再次,这是你引用的一点;我将强调重点:

  

... git pull ...调用git merge检索到的分支头合并到当前分支中。

一旦你习惯了&#34;总是合并到当前分支&#34;,这里剩下的关键问题是:检索了哪些分支头?

答案有点复杂,因为它取决于您传递给git pull的参数。一般形式是git pull remote [options] refspec [refspec ...],即第二个单词是文字字符串pull,第三个单词(解析任何选项后)是遥控器的名称,第四个和更多的单词是&#34; refspecs&#34;

您可以指定refspec或多个refspec,在这种情况下git fetch使用这些来引入某些特定的分支头或头,这些是合并的。或者,您可以将它们排除在外,在这种情况下,git pull会指示git fetch将当前分支机构带到上游。 1

这导致了一个常见的错误。如果您的本地分支AB分别设置为跟踪origin/Aorigin/B,则有人会输入命令git pull A B,认为这会更新origin/Aorigin/B - 它会! 2 - 然后还认为git会将origin/A合并到A中,并{{1进入origin/B,它就赢了。相反,它带来了两个分支头,然后运行一个章鱼合并&#34;进入当前分支(无论当前分支是什么)。

这个多部分问题的答案是:

  

git pull总是将远程跟踪分支合并到当前分支中吗? (我的猜测是肯定的)。如果没有,是否有一个参数git pull指定合并步骤中的目标分支?如果我是正确的,git pull的refspec参数不会为合并步骤指定目标分支。

(A)不,或者至少,它不能得到保证:它合并了检索到的分支头B中记录的git fetch;以及FETCH_HEAD检索了额外的头部,有时它会检查git fetch到&#34;隐藏&#34;它们是否来自合并步骤),它可能与远程相同或不同跟踪分支设置为当前分支的上游。根据我的经验,当大多数用户不是当前分支机构的上游时,他们会感到惊讶;这通常不是他们想要的。

(B)是:&#34; refspecs&#34;指定(如果有)确定检索哪些分支头。获取refspecs与push refspecs略有不同,我不会在这里详细介绍(详见this answer),但简短版本是如果你省略冒号not-for-merge ,这些是存入:进行合并的头部。如果需要,FETCH_HEAD代码会提供默认的refspec,以便pull中有一些内容。

这个问题的答案也有点复杂,因为这个问题有两个假设可能是错误的:

  

为什么合并是&#34;快进合并&#34;?

首先,它不一定是快进合并。

第二,&#34;快进合并&#34;是一种用词不当,或至少是一个简短的名称,掩盖了更深层的真相。 &#34;快进&#34;实际上是标签移动的属性,而不是合并的属性。 Git称合并为&#34;快进&#34;当不需要实际合并时,可以改为执行简单的标签移动。

请记住,在git中,单词&#34; branch&#34;至少有两个含义:分支名称,如FETCH_HEADmasterfeature或其他,然后就是这样底层提交图数据结构。数据结构是永久性的,但名称是短暂的,可以更改。 3 当您要求git将名为 bug2.71828 的分支合并到当前分支中时,它首先将名称​​ X 解析为提交ID。然后,它检查当前X提交(当前分支的提示)是否是目标提交ID的祖先。如果是这样,则不需要实际合并。 4

在&#34;不需要实际合并&#34; git通常会使用简单的标签移动替换合并操作:如果HEAD是对分支名称的符号引用(即,如果您在分支上&#34;&#34;在{{}; HEAD 1}} term),分支名称 - 标签 - 简单地#34;向前移动&#34;指向目标提交。 (如果你在&#34;分离的HEAD&#34;模式git做同样的事情,但是将新ID写入git status本身,而不是写入当前分支的文件。)< / p>

如果您提供HEAD标志,即使可以进行快速转发,也会强制--no-ff进行实际的合并提交。 (以相同但相反的方式,git merge强制git 而不是进行合并,但如果需要真正的合并,则不使一个失败,因此--ff-only在这种情况下将失败,而--ff-only在&#34;合并所需&#34;以及&#34;不需要合并&#34;案例中成功。 )

这个答案已经足够长(或太长了:-))所以我要说的关于添加--no-ff的所有内容是--rebase步骤的工作方式相同,但是fetch代码然后使用pull(使用git rebase,并使用最近版本的git,使用&#34; fork points&#34;)进行重新绑定到引出的头部的一些额外复杂性(如果你获取,则必须输出错误)多个头,因为你不能在不止一个头上转折。)

1 对于当前分支的上游有一个文字点--onto作为其&#34;远程&#34;的情况,有一个特殊的例外情况。名称。在这种情况下,&#34;合并&#34;设置是指另一个本地分支,而不是远程跟踪分支,而.会跳过git pull步骤,因为无需从您自己获取。< / p>

2 这假设你有git版本1.8.4或更新版本。在较旧版本的git中,git fetch步骤会带来分支头,但无法更新git fetchorigin/A,原因是故意但结果不好(因此1.8中的更改) 0.4)。

3 据我所知,git在这方面是独一无二的。其他VCS,包括Mercurial,不会以这种方式实现分支。分支仍然可以重命名,但名称更深入嵌入,无法完全删除,就像它们在git中一样。

4 或者,我们可以说git首先计算merge-base,如果merge-base是当前提交,则不需要实际的合并。

答案 2 :(得分:1)

  

一般来说,合并步骤是否合并了远程跟踪   分支成主或当前分支?我的猜测是当前的   分支,即HEAD指向的分支,并不一定   主

是的,pull操作不会更改分支,您将在同一分支上结束(可能指向不同的提交)。

  

git pull将远程跟踪分支始终合并到当前   科? (我的猜测是肯定的)。如果没有,是否存在git pull的争论   在合并步骤中指定目标分支?如果我是对的,   git pull的refspec参数没有指定目标分支   合并步骤。

是的,你是对的,git pull没有签出不同的分支,并且没有争论。拉动后,手动使用checkout命令切换到另一个分支。

  

为什么合并是&#34;快进合并&#34;?

A&#34;快进&#34; merge是主题分支的fork-point是原始基本分支的tip。 (master没有target

无法访问的提交

这是一个重要的概念,请查看this article。 In-a-nut-shell FF合并不会创建合并提交。 (在各种情况下可能或可能不合适)。

  

git的rebase步骤是否正确使当前变为正确   分支(即HEAD指向的分支)到远程跟踪上   科?如果是,为什么报价会说&#34;您的本地跟踪分支&#34;   而是当前的分支?

git pull rebase会使用branch that it is tracking来修改HEAD。

答案 3 :(得分:0)

关于git pull --rebase还有另一个混淆的原因:做一个合并快进的基础。

git fetch之后(pull --rebase的一部分)

-x--x (master)
     \
      o--o--o (origin/master, local tracking branch now up-to-date)

这里的rebase不需要发生。您无法在“master之上重播origin/master”,因为master的祖先中已包含origin/master。 这里所有git pull --rebase应该是一个快进合并:

-x--x--o--o--o (master, origin/master)

这就是Git 2.12(2017年第一季度)提出的建议:

commit 33b842a查看Junio C Hamano (gitster)(2016年6月29日)。 Junio C Hamano -- gitster --合并于commit 2fb11ec,2016年12月19日)

  

pull:快进“pull --rebase=true

     

git pull --rebase”在获取后始终运行“git rebase”   承诺作为新基地,即使新基地是一个   当前HEAD的后代,即我们没有做任何工作。

     

在这种情况下,我们可以在没有的情况下快进到新基地   调用rebase过程