首页 > 解决方案 > 'git pull 是如何 ' 工作?

问题描述

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

$ git pull origin next

next这会暂时留下 的副本FETCH_HEAD,但不会更新任何远程跟踪分支。

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

标签: gitgit-pull

解决方案


TL;博士

非常简短的回答是肯定的:git fetch获取他们的提交并将它们放入您的存储库。但是git pull这里的文档是错误的,以一种小而重要的方式。

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

不更新任何远程跟踪分支

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

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

在以下具体情况下:

git pull origin next

这运行:

git fetch origin next

它指向git fetch

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

这里的文档是指这三项中的最后一项。然而,从 Git 版本 1.8.2 开始,Git 现在执行 Git 文档所称的“机会性更新”:如果 Git 带来了origin's next,Git 会更新您自己的origin/next即使它没有更新任何其他远程跟踪名称1 在所有情况下,git fetch写入其引入.git/FETCH_HEAD的分支或标签的哈希 ID 和名称。

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


1这种机会性更新的机制特别曲折:Git 读取 的值git config --get remote.origin.fetch以了解如何在将另一个Git 上的分支名称转换为您的Git 存储库中的远程跟踪名称时重命名它们。如果您将此设置为正常默认值以外的值,或者没有设置,则即使在 Git 版本 1.8.2 或更高版本中,文档的声明也可能正确。

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

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

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


提交哈希 ID,或者git fetch带来什么

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

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

一旦你有了对象,你的 Git 就会保留它们,只要有对象的名字,或者对象可以从有名字的东西中访问这样做的过程类似于许多现代语言中的垃圾收集,例如 Java、Python 和 Go(以及更古老的语言,例如 Lisp 及其大多数后代)。本质上,给定一些起点,Git 会遍历通过读取提交2形成的图来获取它们的父哈希 ID。在此图形遍历期间到达的任何提交或其他对象都将被引用并保留;任何达到的提交或其他对象都未被引用并且有资格被删除。

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


2此描述省略了带注释的标记对象。这些也包含哈希 ID 并导致目标对象被引用(如果它是标记、提交或树对象,则遍历)。请注意,每个提交都包含一个树对象的哈希 ID。垃圾收集代码必须遍历这个包含更多对象 ID 的树对象,以将这些对象标记为已引用,并且当然递归地遍历树中的任何子树。幸运的是,除了 tree 和 blob 对象,或者偶尔特殊的 gitlink 之外,trees 不能引用任何东西,它是从子模块中获取的哈希 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 收集器的影响。

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


推荐阅读