TL;DR 总结:“视情况而定”。
答案是“两者都不是”,真的。或“视情况而定”。或者类似的东西!
首先,需要考虑两个基本操作:fetch 和 push。 (pull 操作只是在fetch 之上构建的一个shell 脚本,所以一旦你知道它是如何工作的,我们就可以正确解释pull。)
fetch 和 push 都可以访问整个存储库。但一般来说,它们不能通过网络(或其他通信渠道)发送整个存储库来工作。它们基于引用。
获取和推送操作通常采用“refspecs”,它们是引用对(分别为remote:local 和local:remote)加上一个可选的“force”标志前缀+。但是,它们可以只给出一个简单的引用,并且可以使用-f 或--force 指定强制标志。
这两个命令已经存在了很长时间,并且积累了很多“老东西”。使用远程存储库的“现代”方式是通过称为“远程”的东西,使用git remote add 创建它们(并且git clone 默认创建一个名为origin 的东西)。这些变成.git/config 文件中的条目:
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = ssh://...
url = 行给出了 fetch 和 push 的 URL — 尽管如果需要可以有一个额外的 pushurl = 行,以使推送转到其他地方。 (有一些“旧方法”可以直接运行 fetch 和 push 并提供 URL 等等,但我们忽略所有这些......远程更好!)这也提供了 refspecs——嗯,一个 refspec,在这种情况下——给git fetch。
git ls-remote
除此之外,让我们完全从另一个命令开始,git ls-remote。这就像fetch,但实际上并没有获取任何东西:
$ git ls-remote origin
676699a0e0cdfd97521f3524c763222f1c30a094 HEAD
222c4dd303570d096f0346c3cd1dff6ea2c84f83 refs/heads/branch
676699a0e0cdfd97521f3524c763222f1c30a094 refs/heads/master
d41117433d7b4431a188c0eddec878646bf399c3 refs/tags/tag-foo
这告诉我们名为origin 的远程有三个引用名称。两个是分支,一个是标签。 (特殊的HEAD ref 与refs/heads/master 具有相同的SHA-1,因此git 会猜测远程是“在分支master”上,正如git status 可能所说的那样。远程协议中存在各种错误: git 应该能够说“HEAD 是一个符号引用,指向refs/heads/master”,这样你就不必猜测了。这将解决两个分支与HEAD 具有相同 SHA-1 的情况.)
git 获取
当您运行git fetch origin 时,提取操作以相同的ls-remote 开始,或多或少,因此会看到所有分支和标签。如果你使用--tags,它也会带来所有的标签,否则它会做一些相当复杂的事情1 会带来所有的分支和一些标签。它还可以查看所有 other 引用,但默认情况下,它不会将这些引用带过来:例如,远程可能有 refs/notes/commits,git notes 使用它,但那个没有过来。
但是,当您更改提供给 git fetch 的 refspecs 时,您会更改所带来的内容。默认是.git/config、fetch = +refs/heads/*:refs/remotes/origin/* 中的那个。这个 refspec 说要引入所有 refs/heads/* 引用 - 所有分支 - 并将它们本地存储在 refs/remotes/origin/ 下,使用与远程分支名称相同的名称。使用--tags 会增加一个额外的参考规范:refs/tags/*:refs/tags/*。这就是 git 带来所有标签的方式:所有匹配 refs/tags/* 的东西,也就是所有标签,都以匹配的名称进入你本地的 refs/tags/。
(您可以添加更多fetch = 行并带来更多内容。有关示例,请参见“远程标签”中的this answer。)
现在,仅引入引用 name 不会有多大好处,除非 git 还引入任何所需的底层 objects,2 为由他们的 SHA-1 识别。假设您已经拥有676699a...,但还没有222c4dd...。 (你在master 上是最新的,但在branch 上不是。也许你甚至还没有有 分支branch。)获取操作需要确保提交该提交.该提交可能需要各种文件,以及以前的提交,等等。因此,您的git fetch 与正在查看另一个 git 存储库的远程对象进行通信,并且他们进行了一些对话,每个人都告诉对方他们现在拥有哪些 SHA-1,以及他们仍然需要哪些 SHA-1。如果你需要222c4dd...,它会询问另一端“我还需要使用 222c4dd...”,检查它是否有这些,如果没有,将它们添加到它的列表中,检查那些添加后更详细,依此类推。
在最终同意交换什么后,他们的 git 将对象发送给您——如果可能,通常以“精简包”的形式发送(细节取决于传输方式)——然后您的 git 会根据需要解包和/或重新打包它们,然后更新任何新分支、标签或其他引用的本地引用。 (默认情况下,你的 git 只是将他们的分支存储在你的“远程分支”中——你的“我上次与他们交谈时他们拥有的内容”的副本——但会更新 你的标签。也就是说,有没有“远程标签”,只有“远程分支”。)
一个重要的 git fetch 特例
作为一种特殊情况,如果您给git fetch 提供任何超出远程名称的参数,例如:
git fetch origin master
例如——这些 refspecs 会覆盖配置文件中的那些,和(在 1.8.4 之前的 git 版本中)阻止更新“远程分支”。这通常会限制获取的内容,有时会限制很多。 (在 1.8.4 及更高版本中,它们仍然限制 fetch,但无论如何远程分支都会更新,这更有意义。)这里,缺少冒号的 refspec - 就像上面的那样 - 是 not 被视为两边都具有相同的名称。相反,“他们的”分支像往常一样被收集起来,但 SHA-1 和分支名称被写入.git/FETCH_HEAD。
(有一个很好的理由:如果git fetch origin master 更新了您的master,您将丢失您所做的所有新提交!所以您希望它只更新origin/master 和/或FETCH_HEAD。 )
git 推送
push 操作与fetch 非常相似。虽然它不是完全对称的:你不会推送到“远程分支”,一般来说,你只是直接推送到“分支”。例如,当推送您的分支master 时,您的本地引用是refs/heads/master,并且它们的本地引用也 refs/heads/master。肯定不是refs/remotes/yoursystem/master。所以用于 push 的 refspecs 通常要简单一些。
如果你只是运行git push(或git push origin),这仍然需要提供一些参考规范。
在 git 配置文件 push.default 中有一个(某种新的)控制旋钮,允许您配置 git 推送的引用。在当前版本的 git 中,它默认为 matching。在 git 2.0 中,它将更改为 simple。总共有五种可能的设置:
-
nothing:产生错误
-
current:将你所在的分支推送到同名分支
-
upstream:将您所在的分支推送到其上游名称
-
simple: 和上游一样,但要求上游名称与本地名称匹配
-
matching:推送所有同名分支
其中一些需要进一步解释。 “上游名称”是另一端的分支名称。假设您有一个名为origin/feature 的远程分支,并且为它创建了一个本地跟踪分支,但将其命名为feature2,因为您已经在另一个feature 分支上工作(尚未在origin 上创建)。所以你本地的feature2 有remote/origin 作为它的上游(而你的feature 根本没有上游)。推送到upstream 将遵循映射,并将您的feature2 推送到他们的feature。使用 simple 推送将拒绝该尝试。
因此,如果您 git push 没有 refspec,git 将查找默认配置3 并基于此构造一个 refspec。对于matching 案例,它会将您和他们都拥有的每个分支推送(因此,如果您拥有master 和branch,请将您的master 推送到他们的master,并将您的branch 推送到他们的branch),但对只有你们一个人拥有的分支不做任何事情。
如果你给出一些明确的 refspec(s),所有这一切都变得毫无意义:推送操作会推送你给它的 refspec。此外,不带冒号的 refspec 意味着“两端使用相同的名称”,因此master 是编写完整长版本refs/heads/master:refs/heads/master 的简写方式。
与 fetch 一样,您的 git 和他们的 git 通信以确定需要发送哪些存储库对象(如果有)来完成推送。
git 拉
git pull 操作运行git fetch 的四字形式。
它的第一步是弄清楚要使用什么遥控器。如果你说出一个:
git pull origin master
它需要你给它的名字;否则它会查看您所在的分支(比如说master),然后查看.git/config 以找到branch.master.remote(可能是origin)。
然后,它会确定使用哪个分支。如果您命名一个,它会使用它;否则,它使用branch.master.merge,这是另一端分支的名称(通常只是master)。然后它使用这些参数运行git fetch。
这意味着提取只会带来“有趣”的分支,在本例中为 master,并将 SHA-1 放入 FETCH_HEAD。 (如果你有 git 1.8.4 或更高版本,它也会更新origin/master。)
最后,pull 运行 merge 或 rebase,再次取决于配置条目以及您是否使用 --rebase 运行它。您将合并或变基的提交是其 SHA-1 现在存储在 FETCH_HEAD 中的提交。
请注意,这只会合并或重新设置您当前的分支。
1如手册中所述,fetch 默认使用“标签跟踪”技巧:它查看标签中的 SHA-1,并查看它们是否在或将在您的存储库中。对于那些现在或将要成为的人,它带来了那个标签。您可以使用 --no-tags 关闭此功能。
2对象是存储库实际存储的东西:“blob”(文件)、树(包含文件或更多目录的目录)、提交和“带注释的标签”。每个都有一个唯一的 SHA-1 名称。
3但是,您可以使用每个分支配置 branch.<em>name</em>.pushremote 和 remote.<em>name</em>.push 来覆盖它。您可以通过转动大量配置旋钮来制作大量难以理解的效果。