'git pull <remote> <branchname>' 是如何工作的?

How does 'git pull <remote> <branchname>' work?

我对 git-pull 文档的这一部分有点困惑:

$ git pull origin next

This leaves a copy of next temporarily in FETCH_HEAD, but does not update any remote-tracking branches.

这种形式的 git pull 是否真的从远程存储库中获取新提交到本地存储库?

TL;DR

非常简短的回答是肯定的:git fetch 获取他们的提交并将它们放入您的存储库中。但是 git pull 文档在这里是错误的,虽然很小但很重要。

我建议避免 git pull,至少在您非常熟悉这两个命令的内部工作之前 运行s。相反,使用两个单独的命令:git fetch,然后——假设你打算合并——git merge。然而,这确实值得一些解释。特别是,声称 this

does not update any remote-tracking branches

通常是错误的,尽管在某些特定情况下它可能是正确的。对于 Git 1.8.2.

之前的 Git 版本, 是正确的

如文档所述,git pull 运行s git fetch 然后是第二个 Git 命令,通常是 git merge。第一个(fetch)命令获取您传递给 git pull 的大部分选项和参数,尽管一些选项被带走并传递给 git mergegit rebase,甚至直接使用由 git pull 本身。

在特定情况下:

git pull origin next

这个运行s:

git fetch origin next

git fetch 指向:

  • git config --get remote.origin.url的结果调用另一个Git;
  • 当那个Git列出它的所有分支和标签时,只从它的next(分支或标签,通常是分支)中获取;和
  • 禁止更新 大多数 远程跟踪名称(Git 倾向于称这些 远程跟踪分支名称 但自它们在一个重要意义上不是分支,我喜欢使用较短的短语,远程跟踪名称)。

此处的文档指的是这三项中的最后一项。但是,自从 Git 版本 1.8.2 以来,Git 现在执行 Git 文档中调用的 "opportunistic updates":如果 Git 带来了 originnext、Git 更新您自己的 origin/next,即使它不更新任何其他远程跟踪名称.1 在所有情况下,git fetch 将哈希 ID 和它引入的分支或标签的名称写入 .git/FETCH_HEAD

git pull 运行 的后续 git merge 使用此 FETCH_HEAD 文件中的散列 ID,以及行上的消息 标记为 not-for-merge。请参阅下面有关 FETCH_HEAD 文件内容的部分。


1这种机会更新的机制特别曲折:Git读取git config --get remote.origin.fetch的值,找出如何重命名分支在将 other Git 上的名称翻译成 your Git 存储库中的远程跟踪名称时。如果您将其设置为正常默认值以外的其他设置,或者没有设置,即使在 Git 版本 1.8.2 或更高版本中,文档的声明也可能正确。

如果您使用 URL 而不是远程名称,当然,Git 无法知道要适时更新哪些名称。所以在这种情况下——当你运行:

git fetch https://example.com/repo.git

例如——没有要更新的远程跟踪名称,无论您是否向命令添加额外的 refspecs。


提交散列 ID,或者 git fetch 带来的内容

git fetch 真正重要的部分是它带来了 提交 。具体来说,它带来了哈希 ID their Git 在其存储库中的提交,而 your Git 没有在您的 存储库中。 Git 根据您告诉 git fetch 获取的名称,使用一些图论来确定哪些提交以及哪些其他对象需要从他们的存储库带到您的存储库。正常的默认是所有分支名称和一些标签名称.

这些对象——主要是提交和随附的文件——都是由这些哈希 ID 标识的。 Git 在这里关注的是哈希 ID。您要么拥有具有哈希 ID 的东西,要么没有;如果你不这样做,git fetch 把它带过来。

获得对象后,只要 something 具有对象的 name,您的 Git 就会保留它们,或者该对象 可从 具有名称的对象访问。这样做的过程类似于许多现代语言中的垃圾收集,例如 Java、Python 和 Go(以及更古老的语言,例如 Lisp 及其大部分后代)。本质上,给定一组起点,Git 遍历通过读取提交2 形成的图表以获取它们的父哈希 ID。在此图形遍历期间到达的任何提交或其他对象都被引用并保留;未达到 的任何提交或其他对象都未被引用并且符合删除条件。

FETCH_HEAD 文件的内容在这里计数:该文件中各行上的任何哈希 ID 都是引用对象。因此,即使 origin/next 从未更新过(Git 1.8.2 之前的版本,或脚注 1 中的其他特殊但不太可能出现的情况之一),此处哈希 ID 的存在也会保留对象。


2此描述省略了 带注释的标签 对象。这些也包含哈希 ID 并导致引用目标对象(如果它是标记、提交或树对象,则遍历)。请注意,每个提交都包含一个树对象的哈希 ID。垃圾回收代码必须遍历这个包含更多对象 ID 的树对象,以将这些对象标记为已引用,当然还必须递归遍历树中的任何子树。幸运的是,trees 只能引用 tree 和 blob 对象,或者偶尔的特殊 gitlink,它是从子模块中获取的哈希 ID,但这里没有遍历。


关于FETCH_HEAD

查看 FETCH_HEAD 文件——它是纯文本,易于阅读——你会发现类似这样的内容:

3e5524907b43337e82a24afbc822078daf7a868f                branch 'master' of [url]
fc54c1af3ec09bab8b8ea09768c2da4069b7f53e        not-for-merge   branch 'maint' of [url]
61856ae69a2ceb241a90e47953e18f218e4d5f2f        not-for-merge   branch 'next' of [url]
fc16284eae5b5a7c4786612ba2c254f3f23b1086        not-for-merge   branch 'pu' of [url]
9125ddae1445fd35a9e52a21f926a2785a2583b8        not-for-merge   branch 'todo' of [url]

(哈希 ID 和名称等当然会有所不同)。这是来自 git fetch 没有任何限制;一个专门针对 next 并打算合并的人,改为:

61856ae69a2ceb241a90e47953e18f218e4d5f2f                branch 'next' of [url]

由于您 运行 的 下一个 git fetch 覆盖 FETCH_HEAD 文件,如果没有远程跟踪名称记住此哈希 ID,则未来 git fetch 可以使对象 61856a...d5f2f 符合垃圾收集条件。但是如果你已经合并到你自己的分支,你的分支名称指向一个提交,当跟随提交图时,最终指向到 61856a...d5f2f,它通过引用它来保护来自 Grim Collector 的提交。

(注意有一个选项,-a--append,到 git fetch 告诉它 appendFETCH_HEAD 文件。但是,这可能会混淆 git pull 运行 的 git merge,因为现在可能有多行 not 标记为 not-for-merge。所以将 -agit pull 一起使用很少是个好主意——你真的需要确切地知道你在这里做什么。)