[英]Git fetch and git pull relationship
Looking up the difference between git pull
and git fetch
, many sources say that git pull
is a superset of fetch, ie git pull
is fetch + merge.查找
git pull
和git fetch
之间的区别,许多消息来源说git pull
是 fetch 的超集,即git pull
是 fetch + merge。
However, I seem to remember many times where git pull
told me that everything was up to date, but fetch yielded new information.但是,我似乎记得很多次
git pull
告诉我一切都是最新的,但是 fetch 产生了新信息。
Can someone explain this discrepancy between theory and reality?有人可以解释理论与现实之间的这种差异吗?
Pull is indeed fetch plus merge.拉确实是提取加合并。
Except when it's not.除非它不是。
When isn't it?不是什么时候? When it's fetch plus rebase, or—very rarely—fetch plus checkout.
当它是 fetch 加 rebase 时,或者 - 很少 - fetch 加 checkout 时。 But in all three cases, it's still:
但在所有三种情况下,它仍然是:
git fetch
, followed by git fetch
,然后是 Where this gets complicated is not so much in the second command—though that second command does complicate things—but rather in the arguments passed from git pull
.这变得复杂的地方并不在于第二个命令——尽管第二个命令确实使事情复杂化——而是来自
git pull
传递的参数。 Since git pull
is running two other Git commands, and Git commands' actions depend on their options and arguments, it matters what options and arguments git pull
passes to git fetch
and to that second command, whatever it may be.由于
git pull
正在运行另外两个 Git 命令,并且 Git 命令的操作取决于它们的选项和参数,因此git pull
传递给git fetch
和第二个命令的选项和参数很重要,无论它可能是什么。
In the early days of Git, there were no "remotes" like origin
, which meant there were no "remote-tracking names" either.在 Git 的早期,没有像
origin
这样的“远程”,这意味着也没有“远程跟踪名称”。 You would run:你会运行:
git fetch git://name-of-linus-torvalds-machine/repos/foo.git
to get stuff from Linus and then run git merge FETCH_HEAD
, or something along these lines.从 Linus 获取东西,然后运行
git merge FETCH_HEAD
或类似的东西。 This was error prone (easy to have a typo in the URL) and annoying, so Git acquired a bunch of temporary methods to deal with this.这很容易出错(很容易在 URL 中出现拼写错误)并且很烦人,因此 Git 获得了一堆临时方法来处理这个问题。
Note that with no remotes, all git fetch
could do was leave a bunch of information in .git/FETCH_HEAD
so that you could figure out which branches in Linus's repos had been updated and so on.请注意,在没有遥控器的情况下,所有
git fetch
可以做的就是在.git/FETCH_HEAD
中留下一堆信息,以便您可以确定 Linus 的 repos 中的哪些分支已经更新等等。 And of course, git pull
wrapped these two commands into one, so that you didn't have to run two separate commands, and most people used git pull
.当然,
git pull
将这两个命令合二为一,这样您就不必运行两个单独的命令,而且大多数人都使用git pull
。 But something was clearly missing.但显然缺少了一些东西。 So remotes were invented:
于是发明了遥控器:
origin
that we could use instead of a URL.origin
来代替 URL。 (This got rid of the need for all the weird hacks for naming remotes that are still listed in the documentation , but they're all still in there. Look for Named file in $GIT_DIR
.) Named file in $GIT_DIR
。)origin/master
and the like) take over a job that would in the past require using a local branch name.origin/master
等)接管过去需要使用本地分支名称的工作。 But all these things are still supported and some of them are still described as "the way to do things" in some (ancient) documents, so you can still use the old crude methods.但是所有这些东西仍然受到支持,其中一些仍然在一些(古代)文档中被描述为“做事的方式”,所以你仍然可以使用旧的粗略方法。 Perhaps some do.
也许有些人会。
In any case, remote-tracking names now exist.无论如何,现在存在远程跟踪名称。 However, between Git 1.7 and Git 2.0, there were some updates to them.
但是,在 Git 1.7 和 Git 2.0 之间,对它们进行了一些更新。 Specifically, Git 1.8.4 fixed something eventually declared to be a bug.
具体来说,Git 1.8.4 修复了一些最终被宣布为错误的问题。 Some people are still using Git 1.7.x for some strange reason, so be aware that you could hit them.
出于某种奇怪的原因,有些人仍在使用 Git 1.7.x,所以请注意您可能会碰到他们。
In Git 2.11, the old git pull
shell script was formally retired.在 Git 2.11 中,旧的
git pull
shell 脚本正式退役。 While git pull
still effectively runs git fetch
followed by a second Git command, you can no longer point to the shell script and say: "See, here at this line, it runs git fetch
. Then it has these tests and then it eventually runs this other command..." The result is that it runs much faster on Windows, and is much harder to explain.虽然
git pull
仍然有效地运行git fetch
后跟第二个 Git 命令,但您不能再指向 shell 脚本并说:“看,在这一行,它运行git fetch
。然后它进行了这些测试,然后最终运行这个其他命令......” 结果是它在 Windows 上运行得更快,而且更难解释。 😀 It's also gained a feature or two since then, enough that at least a few hardcore "anti pull" people like me are now willing to actually use the thing. 😀 从那以后它也获得了一两个功能,足以让至少像我这样的一些铁杆“反拉”人现在愿意实际使用这个东西。 But that's another story.
但那是另一回事了。
git pull
git pull
The git pull
command has a lot of options. git pull
命令有很多选项。 See its documentation for the complete list, then compare these options to those for git fetch
and for git rebase
and git merge
.请参阅其文档以获取完整列表,然后将这些选项与
git fetch
和git rebase
和git merge
的选项进行比较。 Note that the pull documentation says that some options are passed to one or the other or to both, and that there's a fair bit of overlap in some options (eg, all take -q
for quiet
and -v
for verbose
).请注意,拉取文档说某些选项被传递给一个或另一个或两者,并且在某些选项中有相当多的重叠(例如,所有选项都采用
-q
表示quiet
和-v
表示verbose
)。
With or without these options, though, you can run:但是,无论有没有这些选项,您都可以运行:
git pull
or:或者:
git pull origin
or:或者:
git pull origin main
for example.例如。 If and when you do run any of these, all of these positional arguments are passed to
git fetch
.如果并且当您运行其中任何一个时,所有这些位置参数都会传递给
git fetch
。
Note that you can even run:请注意,您甚至可以运行:
git pull origin main feature
but you almost certainly should not .但你几乎肯定不应该。 We'll cover why this is later below.
我们将在后面介绍为什么会这样。
Options, if you give them, are passed as described to one or both of the fetch and second-command steps.如果您提供选项,它们将按所述传递给 fetch 和 second-command 步骤中的一个或两个。
The fetch
command is always passed one extra option, namely --update-head-ok
. fetch
命令总是传递一个额外的选项,即--update-head-ok
。 Pull needs to pass this option, but also needs to be careful because careless use of this can get your current branch, index, and working tree out of sync.拉取需要传递这个选项,但也需要小心,因为不小心使用它会使你当前的分支、索引和工作树不同步。 Do not use this option yourself unless you know exactly what you are doing.
除非您确切知道自己在做什么,否则不要自己使用此选项。
For (at least, and maybe only) historical reasons, when passed some refspec arguments, such as main
in the git fetch origin main
case, git fetch
will only update the specified refspecs and associated remote-tracking names .出于(至少,也许只是)历史原因,当传递一些 refspec 参数时,例如
git fetch origin main
main
中的 main , git fetch
只会更新指定的 refspecs 和关联的远程跟踪名称。 Since git pull
passes all the refspec arguments you supplied on to git fetch
, but no extras of its own, git fetch
gets a refspec argument if and only if you passed refspec arguments to git pull
here.由于
git pull
将您提供的所有 refspec 参数传递给git fetch
,但没有它自己的额外参数,当且仅当您在此处将 refspec 参数传递给git pull
时, git fetch
才会获得一个 refspec 参数。
(Fetch refspecs are slightly different from push refspecs: git push origin main
is equivalent to git push origin main:main
, but git fetch origin main
is equivalent to git fetch origin main:<discard>
with the side effect of also updating origin/main
. If you like, you can run git fetch origin main:main
, but this requires that you not be on that branch, except for the --update-head-ok
special case that git pull
arranges.) (Fetch refspecs 与 push refspecs 稍有不同:
git push origin main
等价于git push origin main:main
,但git fetch origin main
等价于git fetch origin main:<discard>
的副作用是也会更新origin/main
. 如果你愿意,你可以运行git fetch origin main:main
,但这要求你不在那个分支上,除了git pull
安排的--update-head-ok
特殊情况。)
The second command that git pull
runs is: git pull
运行的第二个命令是:
git merge
, by default, or git merge
,默认情况下,或git rebase
, if you've told Git to do that, or git rebase
,如果你告诉 Git 这样做,或者git checkout
, in the one special case. git checkout
,在一种特殊情况下。 Again, git pull
passes options and arguments to the second command, and here things get messy.同样,
git pull
将选项和参数传递给第二个命令,这里事情变得一团糟。 When git pull
runs git merge
, it passes:当
git pull
运行git merge
时,它通过:
-m
option with a precomputed merge message (unless you supply your own -m
);-m
选项(除非您提供自己的-m
); plus That last one is a puzzle: what does "as selected" really mean?最后一个是一个谜:“选定”的真正含义是什么? Well, let's go back to the
git pull
syntax:好吧,让我们回到
git pull
语法:
git pull
git pull origin
git pull origin main
We know that these words, if supplied ( origin
and main
), are passed through to git fetch
.我们知道,如果提供这些词(
origin
和main
),将传递给git fetch
。 They specify the remote and, if there's a second word, the branch name as seen on that remote for the git fetch
operation.他们指定遥控器,如果有第二个单词,则指定在该遥控器上看到的分支名称,用于
git fetch
操作。
If we don't supply a branch name as seen on the remote, git pull
requires that the current branch —the one we're on
, as in git status
will say on branch main
or whatever—have an upstream set.如果我们不提供远程分支上的名称,
git pull
要求当前分支——我们所在on
分支,如git status
中所说on branch main
或其他——有一个上游集。 (See also Why do I need to do `--set-upstream` all the time? ) An upstream is technically a pair: both a remote and a branch-name-as-seen-on-the-remote. (另请参阅为什么我需要一直执行 `--set-upstream`? )上游在技术上是一对:既是远程又是远程分支名称。 These are normally presented to you in the more palatable remote-tracking name format, so that the upstream of your
main
would typically be your origin/main
, ie, main
as seen over on origin
.这些通常以更可口的远程跟踪名称格式呈现给您,因此您的
main
的上游通常是您的origin/main
,即在origin
上看到的main
。
Your git pull
command will fish the branch name out of the upstream, if needed.如果需要,您的
git pull
命令将从上游提取分支名称。 It does not pass this on to git fetch
, but it does use it later during this second git merge
command.它不会将其传递给
git fetch
,但稍后会在第二个git merge
命令中使用它。 At this point git pull
will use .git/FETCH_HEAD
—which git fetch
still writes, just like it did in primeval Git before Git 1.5 was released more widely—to fish out the commit hash ID associated with main
over on origin
.此时
git pull
将使用.git/FETCH_HEAD
—— git fetch
仍然会写入,就像它在 Git 1.5 更广泛发布之前在原始 Git 中所做的那样——来找出与main
over on origin
关联的提交哈希 ID。 That's the hash ID that git pull
passes to git merge
.这是
git pull
传递给git merge
的哈希 ID。
In other words, if you're on your main
and its upstream is origin/main
and you run:换句话说,如果你在你的
main
并且它的上游是origin/main
并且你运行:
git pull
your Git will run:你的 Git 将运行:
git fetch --update-head-ok
followed by, if using git merge
:其次,如果使用
git merge
:
git merge -m "merge branch 'main' of <url>" <hash-ID>
where the URL and hash-ID are those from origin
and from .git/FETCH_HEAD
.其中 URL 和 hash-ID 来自
origin
和.git/FETCH_HEAD
。
If you, yourself, run:如果您自己运行:
git fetch
git merge
you'll get the same effect , except that you won't have a -m
option and the merge message will be the default, which will be merge branch 'origin/main'
.您将获得相同的效果,除了您没有
-m
选项并且合并消息将是默认值,即merge branch 'origin/main'
。 That is, the URL vanishes and the branch main of ...
part is phrased differently.也就是说,URL 消失并且
branch main of ...
措辞不同。
But if you run:但是如果你运行:
git pull origin main
your git pull
command will run:您的
git pull
命令将运行:
git fetch --update-head-ok origin main
git merge -m <same message as before> <same hash ID as before>
That is, the extra origin main
get passed to git fetch
, which limits what gets fetched .也就是说,额外的
origin main
被传递给git fetch
,这限制了获取的内容。
We can also now see why we should not run:我们现在还可以看到为什么我们不应该运行:
git pull origin main feature
This would run:这将运行:
git fetch --update-head-ok origin main feature
(which itself is fine), but then it will run: (这本身很好),但随后它将运行:
git merge -m <message> <hash#1> <hash#2>
That is, your git pull
will fish out, from .git/FETCH_HEAD
, two hash IDs: one corresponding to main
on origin
, and one corresponding to feature
on origin
.也就是说,您的
git pull
将从.git/FETCH_HEAD
中提取出两个哈希 ID:一个对应于origin
上的main
,一个对应于origin
上的feature
。 It then passes both hash IDs to one single git merge
command .然后它将两个哈希 ID传递给一个
git merge
命令。 This one git merge
command will do what Git calls an octopus merge .这个
git merge
命令将执行 Git 所说的octopus merge 。 1 1
(Those new to Git often seem to expect that: (那些刚接触 Git 的人似乎经常期望:
git pull origin br1 br2
should check out br1
locally, fetch-and-merge origin/br1
, then check out br2
locally, and fetch-and-merge origin/br2
, perhaps as a more efficient thing than this somewhat clumsy sequential description.应该在本地检查
br1
,获取并合并origin/br1
,然后在本地检查br2
,然后获取并合并origin/br2
,这可能比这个有点笨拙的顺序描述更有效。 That could make sense, and I believe I thought this myself at one point, but it's just not true.)这可能是有道理的,我相信我自己也曾想过这一点,但事实并非如此。)
If you tell Git to use git rebase
instead of git merge
—which you can now do in several ways, such as setting pull.rebase
to true
, in addition to providing --rebase
as an option to git pull
—Git will replace the git merge
command with a git rebase
command.如果你告诉 Git 使用
git rebase
而不是git merge
——你现在可以通过多种方式做到这一点,例如将pull.rebase
设置为true
,除了提供--rebase
作为git pull
的选项——Git 将替换git merge
命令与git rebase
命令合并。 This changes the set of options that can be passed through:这会更改可以通过的选项集:
-m
, so you cannot give one; -m
,所以你不能给一个;--ff-only
or --no-ff
, so you cannot give these. --ff-only
或--no-ff
,所以你不能给这些。 The git rebase
command has a mode called autostash where, if your status is not "clean" (as in git status
would not say working tree clean, nothing to commit
), git rebase
will run git stash push
before it starts the rebase, and git stash pop
at the end. git rebase
命令有一个名为autostash的模式,如果您的状态不是“干净”(如在git status
中不会说working tree clean, nothing to commit
), git rebase
将在启动 rebase 之前运行git stash push
,并且最后git stash pop
。 I am not a fan of git stash
in general and unless you're pretty good at dealing with conflicts, I recommend not using this feature.一般来说,我不是
git stash
的粉丝,除非你非常擅长处理冲突,否则我建议不要使用此功能。
If autostash is disabled (which is the default), the rebase will refuse to start if the status is not "clean".如果 autostash 被禁用(这是默认设置),如果状态不是“clean”,rebase 将拒绝启动。 With
git merge
as the second command, the merge will generally refuse to start in the same situation (although I recall ancient Git versions behaving differently, with the same messy side effects as for git stash pop
in some conflict cases).使用
git merge
作为第二个命令,合并通常会拒绝在相同的情况下开始(尽管我记得古代 Git 版本的行为不同,在某些冲突情况下与git stash pop
具有相同的混乱副作用)。
The last case is one that's only seen rarely.最后一种情况是很少见的。 You can have a Git repository in a special state, for which Git uses two different terms: an unborn branch or an orphan branch .
您可以拥有一个处于特殊状态的 Git 存储库,Git 对此使用两个不同的术语:未出生分支或孤立分支。 This state exists in part because a new, totally-empty repository has no commits at all on it.
这种状态的存在部分是因为一个新的、完全空的存储库根本没有提交。
A branch name , in Git, must contain the hash ID of some valid, existing commit. Git 中的分支名称必须包含某个有效的现有提交的哈希 ID。 But when you run
git init
and it creates a new, totally- empty repository, there is no commit.但是当你运行
git init
并创建一个新的、完全空的存储库时,没有提交。 With no commits, there can be no branches.没有提交,就没有分支。 And yet,
git status
will say that you're on some branch, and that there are no commits yet and you should make the first one.然而,
git status
会说你在某个分支上,并且还没有提交,你应该做第一个。
In this state —this orphan / unborn branch state—the next commit you make will be a root commit , which in a new empty repository is what you normally want: that's the first commit ever, and it starts history existing.在这种状态下——这个孤儿/未出生的分支状态——你做出的下一个提交将是一个根提交,它在一个新的空存储库中是你通常想要的:这是有史以来的第一次提交,它开始存在历史。 Now you have a commit and you can build on it.
现在你有一个提交,你可以在它的基础上进行构建。
When you run git pull
while in this unborn-branch state, though, the git pull
operation may get a bunch of commits from the remote (from origin
for instance).但是,当您在未出生分支状态下运行
git pull
时, git pull
操作可能会从远程(例如来自origin
)获得一堆提交。 The second command is supposed to combine those new commits that git pull
got, as directed by the remaining git pull
arguments, with the commits on the current branch.第二个命令应该按照剩余的
git pull
参数的指示将git pull
获得的那些新提交与当前分支上的提交结合起来。 There are no commits on the current branch (which does not exist), but zero plus something is the something, right?当前分支上没有提交(不存在),但零加上某事是某事,对吧? So
git pull
declares that the result of this pull-into-empty-repository is that you should check out the commit that's at the tip of the branch you git pull
-ed.所以
git pull
声明这个 pull-into-empty-repository 的结果是你应该检查你git pull
-ed 分支顶端的提交。 That is:那是:
git init
git remote add origin <url>
git pull origin main
should have your Git reach out to the given URL, find their main
, get commits from their Git, create your origin/main
, and then create your own main
that is an exact match for your origin/main
that your Git just created based on their main
.应该让你的 Git 访问给定的 URL,找到他们的
main
,从他们的 Git 获取提交,创建你的origin/main
,然后创建你自己的main
,它与你的 Git 刚刚创建的origin/main
完全匹配他们的main
。
The thing that does this last step is a branch-creating git checkout -b
or git switch -c
, so that's what git pull
will do here.最后一步的事情是创建分支
git checkout -b
或git switch -c
,这就是git pull
将在这里做的事情。 (There was a bug, back in Git 1.5 or 1.6 or so, where if your working tree was non-empty, this git pull
command would wipe it out entirely . This bug bit me at least once and is at least some of the reason I learned to avoid git pull
. This bug has been long fixed, but I generally like to fetch, inspect , and merge-or-rebase, and I need to run git log
to do the inspecting, between the fetch and the second—or rather, third—command. So I still use git pull
only sparingly at best. But it now has pull.ff only
as a configuration item, and that covers my most common case, so I am slowly warming up to it.) (有一个错误,在 Git 1.5 或 1.6 左右,如果你的工作树是非空的,这个
git pull
命令会完全清除它。这个错误至少咬了我一次,至少是部分原因我学会了避免git pull
。这个 bug 已经修复很久了,但我通常喜欢 fetch、 inspect和 merge-or-rebase,我需要在 fetch 和第二个之间运行git log
来进行检查——或者相反,第三个命令。所以我仍然充其量仅少量使用git pull
。但它现在pull.ff only
作为配置项,这涵盖了我最常见的情况,所以我正在慢慢适应它。)
1 For more on octopus merges, see the git merge
documentation . 1有关章鱼合并的更多信息,请参阅
git merge
文档。 Note that if the two hash IDs are identical, the effect of this octopus merge is largely the same as that of a regular merge, except that octopus merges cannot handle conflicts.请注意,如果两个哈希 ID 相同,则此章鱼合并的效果与常规合并的效果基本相同,只是章鱼合并无法处理冲突。 At least, not yet: Junio Hamano was musing a bit on whether the new
merge-ort
might be able to tackle this.至少,现在还没有: Junio Hamano 正在思考新的
merge-ort
是否能够解决这个问题。
It's not clear to me that this is a good idea.我不清楚这是一个好主意。 In fact, it's somewhat clear to me that having octopus merge be weaker , and not able to handle merge conflicts, is a good thing.
事实上,我有点清楚章鱼合并较弱,并且无法处理合并冲突,是一件好事。
However, I seem to remember many times where git pull told me that everything was up to date, but fetch yielded new information.
但是,我似乎记得很多次 git pull 告诉我一切都是最新的,但是 fetch 产生了新信息。
If you run git pull origin main
and get the up-to-date message, your current branch has origin/main
merged in and there's nothing to do here.如果您运行
git pull origin main
并获取最新消息,则您当前的分支已合并origin/main
,此处无事可做。 But if you then run git fetch origin
(or just git fetch
), you'll fetch all their branch names, updating all your remote-tracking names.但是,如果您随后运行
git fetch origin
(或只是git fetch
),您将获取他们所有的分支名称,并更新您所有的远程跟踪名称。
If the upstream of the current branch is origin/main
, you can run:如果当前分支的上游是
origin/main
,则可以运行:
git pull
instead of:代替:
git pull origin main
and the git fetch
that git pull
runs won't be limited to fetching only their main
.并且
git pull
运行的git fetch
将不仅限于获取它们的main
。
However, I seem to remember many times where git pull told me that everything was up to date, but fetch yielded new information.
但是,我似乎记得很多次 git pull 告诉我一切都是最新的,但是 fetch 产生了新信息。
That's merely because of how Git reports what happened.这仅仅是因为 Git 如何报告发生的事情。
git fetch
updates all remote tracking branches and reports on that, so any new commits at the remote site will yield some sort of output. git fetch
更新所有远程跟踪分支并对此进行报告,因此远程站点上的任何新提交都会产生某种输出。
But git pull
, though it also does a git fetch
, only reports what happened on the current local branch, which might well be nothing even if the fetch did bring lots of commits into the remote tracking branches.但是
git pull
,虽然它也执行git fetch
,但只报告当前本地分支上发生的事情,即使 fetch 确实将大量提交带入远程跟踪分支,这也可能什么都没有。 (Another good reason not to use pull
!) (另一个不使用
pull
的好理由!)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.