一个 git pull 命令会更新所有已跟踪的分支吗?

22
假设我通过Git本地跟踪了三个远程分支,分别是masterbranch1branch2。如果我当前在master分支上,那么执行git pull命令会获取并合并这三个分支的变化吗?我想我也可以自己进行实验...

这回答了您的问题吗?“git pull --all”能更新我所有的本地分支吗? - Martian2020
4个回答

32

简短的回答是“不”—或者甚至是“不和是”,如果只读您问题的标题—但这有些误导。

pull命令允许选项,其中许多具有有趣(混乱和潜在的非常不友好)的影响。

侧边栏:确保您正确理解Git文档所称的远程分支、远程跟踪分支和跟踪分支之间的差异,以充分利用以下说明。请注意,这些是Git(和git-scm.com)使用的术语。长时间以后,我最初写下这个答案,我发现这些术语只会让人们困惑——包括我自己!——而且现在,我更喜欢我的术语。我喜欢说作为另一个(远程)Git存储库上看到的分支名称,而不是远程分支;我称之为远程跟踪名称,而不是远程跟踪分支名称(例如origin/master);而我使用设置上游的分支名称这个短语来代替跟踪分支。但是要准备好Git文档使用他们的术语。

记住,git pull基本上是git fetchgit merge的简写。 1 pull命令将大多数参数直接传递到fetch步骤。 例如,如果您执行git fetch origin br,则fetch步骤将获取远程(origin)的名称和br作为“refspec”。 在Git版本1.8.4之前,这会防止git fetch更新任何远程跟踪分支,但自1.8.4以来,显式获取分支br也会更新您的origin/br(假设涉及的远程名为origin)。
如果你不传递额外的参数,git fetch将会获取远程默认的refspecs集合,通常是“所有分支”。这意味着,无论是git fetch(假设是origin)还是git fetch origin,都会更新所有与origin相关的远程跟踪分支,前提是你正在运行一个相当现代的(1.8.4或更高版本)Git。(在足够旧的Git版本中,我认为pull命令在fetch步骤期间会变得更加严格。)
但是,在潜在地获取和更新了所有origin/*远程跟踪分支之后,pull代码转移到git merge步骤。对于这部分,Git非常关心你现在所在的分支以及它将要与哪个“远程分支”合并。如果像你的示例一样,你当前在master上,并且它将要与originmaster合并,Git将使用任何新的内容来运行git merge步骤,这些内容位于origin/master下面(请注意此处的斜杠)。

技术细节: "refspec"

我在上面多次使用了“refspec”一词,但从未定义过。

“refspec”在其第二个最简单的形式下,只是一对分支名称,例如master:masterdevelop:develop等。

这对通常(但不总是)在它们之间的冒号:两侧具有相同的两个分支名称。 其中一个是您的名称-您的分支,在您的存储库中-另一个是他们的名称,也就是他们在他们的存储库中用于他们的分支的名称。

通常在推送时,您会看到这种双名称表单,例如:git push origin master:master。这意味着“打包我在我的主分支中的内容,通过互联网电话呼叫名为origin的远程主机,将包发送给他,并询问他是否将其作为他的master分支。”您还可以在左侧使用提交ID或单词HEADgit push origin HEAD:master表示“获取当前所在的提交,要求远程origin将其作为他的master分支”。
当执行git fetch时,双方会交换位置,通常你还需要在你想要的名称前面添加origin/(或你的远程名称)。也就是说,你会得到他的master,但在你的仓库中将其称为origin/master。因此,你需要执行git fetch origin master:origin/master等操作。4 如果你执行git fetch origin master:master,你可能会覆盖自己在主分支上的工作。5 有一个与fetchpull相关的奇怪事情。我说这是refspec的“第二简单”形式,最简单的形式只是像master这样的名称。这取决于你正在做什么:fetch表示“带上他的master,但不一定在我的本地存储库中为其指定任何名称,只需保存哈希ID。”对于push,它表示“给他我的master,并要求他将其称为master”。

同样,在旧版本的git中,如果您要求git fetch带来他们的主分支(以及其他分支),而没有为其提供本地名称,则它将仅在Git的FETCH_HEAD文件中保存ID(以及它们对分支的名称)。在较新(1.8.4及更高版本)的Git版本中,git使用fetch =配置项来确定要更新哪些远程跟踪分支。

不好的做法:git pull origin master branch(不要这样做)

如果你让git fetch带来多个分支,那么这很好用(至少在Git 1.8.4或更高版本中是这样)。但是,如果你告诉git pull获取多个分支,它会表现得很糟糕。

fetch步骤很好用。问题出在merge步骤。

pull命令要求git merge将多个分支合并到当前分支中。Git称之为“章鱼合并”。除非你了解高级分支操作,否则你几乎肯定不想这样做。所以不要这样做。

最重要的是

我建议先执行一个git fetch,默认情况下会带来所有分支,然后再执行单个的git mergegit rebase操作。这样你就可以看到自己在做什么了。


1如果配置或指定,则可以使用git rebase。与合并相比,变基通常更好,但这取决于具体情况。我的建议是首先使用git fetch,然后将自己的git rebasegit merge作为单独的步骤执行,至少在您还不熟悉Git时要这样做。我认为这样做实际上会更少让人困惑,尽管不可否认的是,您需要输入两个命令而不是一个。

2从技术上讲,它是该远程配置中fetch =行中的内容。对于远程origin,正常行为为fetch = +refs/heads/*:refs/remotes/origin/*,这就是Git知道如何将origin的分支重命名为您的origin/whatever分支的方式。

3技术上,Git只使用一个原始哈希ID,在名为FETCH_HEAD的文件中保存。这里带引号的“远程分支”是标准的Git术语:在通过git fetch命令将提交提供给您的Git的存储库上看到的分支名称。

4我忽略了另一件事,即前面的加号符号:fetch =行通常用于获取“他们的主分支”,您通常要忘记关于“他们的主分支”的任何先前想法,因此该行具有前导加号符号。这启用了“force”标志,即使不是快进操作也会进行更新。 fetch =行还完全拼写了所有内容,以避免如果意外命名本地分支origin/something而出现问题,并使用*字符来匹配多个分支名称。

5如果您确实覆盖了自己的工作,几乎总能找回它。 Git确实尝试至少保留所有内容30天。我们将把“如何取回它”留给其他SO条目。


从问题的表述中,我不确定OP最初是否意识到远程分支、远程跟踪分支和跟踪分支之间的区别。因此,我会添加一个链接到解释这些术语的文档。除此之外,我觉得目前21个点并不能公正地展现出这样一个全面而易于理解的答案的价值。 - Johnson
@Johnson:我已经批准了你的编辑,但是我又加了一点内容,因为在过去的五年里,我已经修改了写这些东西的方式。(此外,git pull不再是一个脚本,Git 2.29开始支持使用SHA-256而不是SHA-1。) - torek
我也意识到术语(即使在git文档中)随着时间的推移有些变化。然而,我更喜欢紧密遵循“官方”资源所创造的术语。我同意文档仍然存在一些令人困惑/模糊的部分(至少在我的理解中)。我发起了一个拉取请求,希望能够改进这一点。 - Johnson
@S.B: 我的意思就是字面上写的那样。git fetch 是更新或者没有更新远程跟踪分支名称的命令。git pull 命令会运行 git fetch,所以 git pull 有两个步骤:(1) 运行 git fetch,然后 (2) 再运行另一条 Git 命令(由你选择)。我们关心的是 git pull 中的 git fetch 步骤。如果你输入 git pull origin br,你运行的是 git fetch origin br。也许有更清晰的表述方法。 - torek
只是一个小问题:我这句话说得对吗?: git pull没有参数,会执行git fetch <origin>步骤,更新所有远程跟踪分支,然后执行git merge FETCH_HEAD吗? - S.B
1
多少而言,是的。git merge (或 git rebase)命令并不直接使用 FETCH_HEAD ,而是从中提取提交哈希值,并在最新的 Git 版本(由于它现在是一个大型 C 程序)中写入了 fetch 步骤的哈希值到 FETCH_HEAD 中(因为相同程序已经知道了它们,所以无需再次检索)。 - torek

11

3
你可以使用git pull --all拉取所有已跟踪的远程分支,或者使用git fetch --all获取它们并稍后再决定如何处理。请注意,pull通常会自动合并任何更改,这很少是你想要的。

2
这只会从所有远程获取,而不会与本地分支合并。 - Wojciech

-1

不,只将合并更改拉到当前本地分支。

根据这个答案 https://dev59.com/gG855IYBdhLWcg3weUQQ#59793998,我已经添加了一个函数到/etc/bash.bashrc来实现这个功能:

git_pull() {
    echo - pulling all tracked local branches from "origin" remote
    orig_branch=$(git branch | grep "*" | sed "s/[ *]*//") # getting name of current branch via asterisk
    git fetch | return 1
    for branch in $(git branch | sed "s/[ *]*//") ; do # remove spaces and asterisk
        git checkout $branch && git merge --ff-only FETCH_HEAD
    done
    echo - checking out to branch where function was called from 
    git checkout $orig_branch
}; export -f git_pull

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接