简体   繁体   English

git fetch 和 git pull 关系

[英]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 pullgit 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:但在所有三种情况下,它仍然是:

  1. git fetch , followed by git fetch ,然后是
  2. some second Git command to do something with the fetched commits.第二个 Git 命令对获取的提交做一些事情。

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和第二个命令选项和参数很重要,无论它可能是什么。

Aside: a look into history旁白:回顾历史

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:于是发明了遥控器

  • We now had a short simple name like 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 。)
  • We now had a way for Git to save the hash IDs associated with Linus's latest versions, so that we didn't need to create lots of branches locally.我们现在有一种方法让 Git保存与 Linus 最新版本相关的哈希 ID,这样我们就不需要在本地创建大量分支。 The remote-tracking names ( 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.但那是另一回事了。

How you run 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 fetchgit rebasegit 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特殊情况。)

Adding in the second command添加第二条命令

The second command that git pull runs is: git pull运行的第二个命令是:

  1. git merge , by default, or git merge ,默认情况下,或
  2. git rebase , if you've told Git to do that, or git rebase ,如果你告诉 Git 这样做,或者
  3. 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时,它​​通过:

  • merge options that the documentation describes as passed-through;文档描述为传递的合并选项; plus
  • a -m option with a precomputed merge message (unless you supply your own -m );带有预先计算的合并消息的-m选项(除非您提供自己的-m ); plus
  • the commit hash ID of the commit that is the branch tip of the branch name(s) on the remote, as selected.提交的提交哈希 ID,它是远程分支名称的分支提示,如所选。

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 .我们知道,如果提供这些词( originmain ),将传递给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:这会更改可以通过的选项集:

  • rebase does not accept -m , so you cannot give one; rebase 不接受-m ,所以你不能给一个;
  • rebase does not accept --ff-only or --no-ff , so you cannot give these. rebase 不接受--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 -bgit 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.事实上,我有点清楚章鱼合并较弱,并且无法处理合并冲突,是一件好事。


What about ...关于什么 ...

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM