【问题标题】:Confusions about the merge/rebase step of git pull关于 git pull 的合并/变基步骤的困惑
【发布时间】:2016-04-02 20:55:03
【问题描述】:

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

在拉取操作的第二步,Git 执行一个 合并(默认)或变基操作。

关于 git pull 中的合并步骤:

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

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

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

释义,这为 Git 提供了两个关键信息:当 master 是当前签出的分支,使用 origin 作为默认远程 在获取(或拉取)期间从中获取更新。 此外,在 git pull 的合并步骤中,使用 refs/heads/master 从远程作为默认分支合并到这个,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 是否有参数指定合并步骤中的目标分支? 如果我是正确的,git pullrefspec 参数没有指定合并步骤的目标分支。

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

关于 git pull 中的 rebase 步骤:

命令 git pull --rebase 会导致 Git 变基(而不是 合并)你的本地跟踪分支到远程跟踪分支 仅在此拉动期间。

  • git pull 的变基步骤将当前分支(即HEAD 指向的分支)变基到远程跟踪分支上是否正确? 如果是,为什么引用说“您的本地跟踪分支”而不是当前分支?

【问题讨论】:

    标签: git


    【解决方案1】:

    一般来说,merge步骤是将远程跟踪分支合并到master还是当前分支?

    这两个是相同的: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 下的两个本地提交会在上游重写:

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

    现在的情况是“本地分支master 领先origin/master 2 个提交”。您已准备好(尝试)git push

    您的原始未重写提交仍然存在(在 ASCII 图中用“您的幽灵”表示)。它们在git reflog 中被引用。当它们从那里过期或被手动清除时,它们就会变成垃圾,最终被垃圾收集。

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

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

    现在您的 masterorigin/master 领先 1 个提交。如果您现在成功推送,远程存储库的master 也将呈现上述形状,双父提交沿一条路径进行更改,而先前的上游提交沿另一条路径提交。没有“你的幽灵”:你的更改从未被重写。

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

    快进合并或变基发生在两种情况下。一是origin/master没有任何并行变化:

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

    在上述情况下,无需做任何事情:一切都是最新的,并且您领先 origin/master 两次提交。因此,这不是“快进任何东西”; git mergegit rebase 根本不会做任何事情。如果您对这些提交感到满意,可以尝试git push

    真正的快进场景发生在您没有任何本地更改时:

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

    在这种情况下,如果您执行git rebasegit merge,那么默认情况下,您的master HEAD 指针只是“向前滑动”以指向与origin/master 相同的提交,这张幻灯片是“快进”:

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

    “快进”术语可能是指只有HEAD 指针移动(以及重写本地文件系统树以匹配)的想法。无需编写或重写新的提交。

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

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

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

    你会得到这样的东西:

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

    想法是,在历史的主线路径中,两个变化似乎浓缩为一个步骤。可以关注另一个父级以查看具有多个提交的详细沿袭。

    为什么引用说“您的本地跟踪分支”而不是当前分支?

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

    您工作的分支不称为跟踪分支;它不跟踪任何东西。

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

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

    一般情况下,纯本地操作不会改变origin/master:它会跟踪远程repo中对应分支的状态。

    【讨论】:

      【解决方案2】:

      正如我最近的另一条评论一样,我想完全避免使用“本地跟踪分支”这个词,并且在这里只关心本地分支是否有上游集,如果有,上游设置为什么(正如您引用的书,它分为两部分,“远程”和“合并”字符串)。

      第一个问题:

      一般来说,merge步骤是将远程跟踪分支合并到master还是当前分支?

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

      合并的内容有点棘手。同样,这是您引用的一点;我只是移动重点:

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

      一旦您对“始终合并到当前分支”感到满意,剩下的关键问题是:检索到了哪些分支头?

      答案有点复杂,因为它取决于您传递给git pull 的参数。一般形式是git pull <em>remote</em> [<em>options</em>] <em>refspec</em> [<em>refspec</em> ...],即第二个单词是文字串pull,第三个单词(解析任意选项后)是遥控器的名字,第四个单词是“refspecs”。

      您可以指定一个 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 中,并将 origin/B 合并到 B 中,但它不会。相反,它会带来两个分支头,然后将“章鱼合并”运行到当前分支(无论当前分支是什么)。

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

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

      (A) 不,或者至少,不能保证:它合并检索到的分支头git fetch 记录在FETCH_HEAD 中;如果git fetch 检索到额外的头,则有时会这样做,它会将那些not-for-merge 标记为从合并步骤中“隐藏”它们),这可能与设置为当前分支上游的远程跟踪分支相同,也可能不同。根据我的经验,当它不是当前分支的上游时,大多数用户都会感到惊讶。这通常不是他们想要的。

      (B) 是:您指定的“refspecs”(如果有)确定检索哪些分支头。 Fetch refspecs 与 push refspecs 有细微的不同,我不会在这里详细介绍(有关详细信息,请参阅 this answer),但简短的版本是,如果您省略冒号 :,这些是存放的头进入FETCH_HEAD 进行合并。如果需要,pull 代码会提供默认的 refspec,以便 FETCH_HEAD 中包含一些内容。

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

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

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

      其次,“快进合并”有点用词不当,或者至少是一个掩盖更深层次事实的捷径。 “快进”实际上是 标签移动 的属性,而不是合并的属性。当不需要实际合并并且可以进行简单的标签移动时,Git 将合并称为“快进”。

      请记住,在 git 中,“分支”一词至少有两种含义:分支 name,如 masterfeaturebug2.71828 或其他任何东西,然后是底层提交图数据结构。数据结构是永久的,但名称是短暂的,可以更改。3 当您要求 git 将名为 X 的分支合并到当前分支中时,它首先解析名称 X 到提交 ID。然后它检查当前的HEAD 提交(当前分支的尖端)是否是目标提交 ID 的祖先。如果是这样,则不需要实际合并。4

      在“不需要实际合并”中,git 通常会用简单的标签移动替换合并操作:如果HEAD 是对分支名称的符号引用(即,如果你在“在一个分支上” git status 术语),分支名称(标签)只是“向前移动”以指向目标提交。 (如果您处于“分离 HEAD”模式,git 会执行相同的操作,但会将新 ID 写入 HEAD 本身,而不是写入当前分支的文件。)

      --no-ff 标志,如果你给它,即使快进是可能的,也会强制git merge 进行实际的合并提交。 (以相同但相反的方式,--ff-only 强制 git not 进行合并,但如果需要真正的合并,not 的唯一方法一种是失败,所以--ff-only 在这种情况下会失败,而--no-ff 在“需要合并”和“不需要合并”两种情况下都会成功。)

      这个答案已经足够长(或太长:-))所以我要说的关于添加--rebasefetch 步骤的工作原理相同,但pull 代码然后使用git rebase(使用--onto,以及最新版本的 git,使用“分叉点”的一些额外复杂性)重新定位到获取的头部(如果您获取多个头部,则必须出错,因为您不能重新定位到多个头部) .


      1对于当前分支的上游有一个文字点. 作为其“远程”名称的情况有一个特殊的例外。在这种情况下,“合并”设置指的是另一个本地分支,而不是远程跟踪分支,git pull 跳过了git fetch 步骤,因为不需要从你自己那里获取。

      2这假设您拥有 git 版本 1.8.4 或更高版本。在旧版本的 git 中,git fetch 步骤带来了分支头,但未能更新 origin/Aorigin/B,原因是故意但结果不好(因此在 1.8.4 中进行了更改)。

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

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

      【讨论】:

        【解决方案3】:

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

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

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

        是的,你是对的,git pull 不会签出不同的分支,并且对此没有任何论据。拉取后,手动使用checkout命令切换到不同的分支。

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

        “快进”合并是主题分支的分支点是原始基本分支的尖端的合并。 (master 没有 target 无法访问的提交)

        这是一个重要的概念,所以请看this article。 简而言之,FF 合并不会创建合并提交。 (在各种情况下可能需要也可能不需要)。

        git pull 的 rebase 步骤 rebase 当前是否正确 分支(即 HEAD 指向的分支)到远程跟踪 分支?如果是,为什么引用说“您的本地跟踪分支” 而不是当前分支?

        git pull rebase 将使用 branch that it is tracking 重新定位 HEAD。

        【讨论】:

          【解决方案4】:

          关于git pull --rebase 的另一个混淆来源是:在合并快进的情况下进行变基。

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

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

          这里的变基不需要发生。您不能“在”origin/master 之上重播master,因为master 已包含在origin/master 的祖先中。
          git pull --rebase 在这里应该做的就是快进合并:

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

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

          commit 33b842a(2016 年 6 月 29 日)Junio C Hamano (gitster)(2016 年 12 月 19 日在 commit 2fb11ec 中由 Junio C Hamano -- gitster -- 合并)

          pull:快进“pull --rebase=true

          "git pull --rebase" 总是运行 "git rebase" 在获取 承诺作为新基地,即使新基地是 当前 HEAD 的后代,即我们还没有做任何工作。

          在这种情况下,我们可以改为快进到新基地,而无需 调用变基进程

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-04-08
            • 2015-02-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-10-26
            • 2014-03-21
            相关资源
            最近更新 更多