这里是“TL;DR”版本(掩盖了很多特殊情况):git fetch总是更新FETCH_HEAD,不止一行在各种情况下。它有时会更新“远程分支”,即全名以refs/remotes/ 开头的引用。其余的主要是关于“有时”,这取决于提供给git fetch 的参数数量和 git 版本。
我有机会对此进行测试。让我们区分三种情况,所有这些情况都假定运行git fetch 没有额外的选项,如-a 甚至--all。让我们也排除git fetch 的奇怪变体,例如直接使用URL,或insteadOf 条目,或.git/remotes 或.git/branches 中列出的文件。 (我承认我只是在猜测,但我认为这些是 [remote "name"] 条目进入 git 配置文件前几天的遗留物。编辑,2019:事实证明这是正确的。)
-
git fetch,没有其他参数。
Git 确定您当前的分支(以通常的方式,通过读取HEAD,但您当然可以通过git branch 或git status 查看它是什么)。然后它会为该分支查找一个配置条目,命名为remote。例如,假设您在分支 dummy 和 .git/config 有(以及其他条目):
[branch "dummy"]
remote = remote-X
在这种情况下,git fetch 等效于 git fetch remote-X。之后,这相当于情况2,即:
-
git fetch <em>remote</em>(除此之外没有更多参数)。
Git 这次不查看您当前的分支。要使用的遥控器是命令行中给出的遥控器。它确实为给定的遥控器寻找配置部分。假设您正在使用remote-X:在这种情况下,它会查找:
[remote "remote-X"]
url = ...
如果该部分不存在,或者没有 url = 条目,则会出现错误:fatal: 'remote-X' does not appear to be a git repository。1 否则会给出 URL,git fetch 将尝试连接到那里。假设它可以连接...
通常还有至少一个配置条目,可能更多,内容如下:
fetch = +refs/heads/*:refs/remotes/remote-X/*
(遥控器的名称在这里是硬编码的)。假设有...
接下来,git fetch 询问远程它有哪些 refs(主要是分支和标签,虽然你可以获得所有 refs,但大多数人只关心分支和标签)。您可以使用git ls-remote remote-X 自己做同样的事情,它会溢出如下内容:
676699a0e0cdfd97521f3524c763222f1c30a094 HEAD
222c4dd303570d096f0346c3cd1dff6ea2c84f83 refs/heads/branch
676699a0e0cdfd97521f3524c763222f1c30a094 refs/heads/master
HEAD ref 的处理并不完全一致(我看到它的行为很奇怪),但通常在这里它只是被丢弃了。2 其余分支根据fetch = 参考规范。 (如果有多个fetch = refspecs,它们会根据它们全部重命名和更新。这主要用于引入refs/notes/ 或在refs/rtags/ 下创建自己的“远程标签”命名空间,例如.)
在这种情况下,fetch 将带来两个分支branch 和master 所需的任何对象,并根据需要更新(本地)“远程分支”名称refs/remotes/remote-X/branch 和refs/remotes/remote-X/master。对于每一个更新,fetch 打印如下一行:
22b38d1..676699a master -> remote-X/master
如果缺少fetch = 行,您会得到完全不同的结果。输出将显示:
* branch HEAD -> FETCH_HEAD
在这种情况下,就好像(缺失的)fetch = 行在那里并且包含 fetch = HEAD。
-
git fetch <em>remote refspec</em>(refspec 部分实际上是一个或多个参考规范,如下所述)。
这与案例 2 类似,只是这一次,“refspecs”是在命令行上提供的,而不是来自远程的 fetch = 配置条目。但是,这里的 fetch 行为完全不同。
在这种特殊情况下,让我们暂停一下并正确描述 refspec。 (git push 也会出现 Refspecs,但与 git 一样,实现细节会泄漏出来,并且它们在那里的工作方式略有不同。) refspec 有一个可选的前导加号(+),我将在这里忽略它;3 然后是两部分,用冒号 (:) 分隔。两者通常都只是一个分支名称,但您可以(和fetch = 行一样)拼出“完整”引用名称,refs/heads/<em>branch</em> 在分支名称的情况下。
对于获取操作,左侧的名称是远程本身的名称(例如git ls-remote 所示)。右侧的名称是要在本地 git 存储库中存储/更新的名称。作为一种特殊情况,您可以在斜线后面添加一个星号 (*) 作为最后一个组件,例如 refs/heads/*,在这种情况下,左侧匹配的部分将在右侧替换。因此refs/heads/*:refs/remotes/remote-X/* 是导致refs/heads/master(在远程看到,git ls-remote)变成refs/remotes/remote-X/master(在你的本地存储库中看到,更短的形式,在-> 行的右侧) git fetch 打印)。
不过,如果你不放:,git fetch 就没有好地方放“那边的分支”的副本。假设它将带入遥控器的refs/heads/master(遥控器上的master 分支)。而不是更新你的refs/heads/master——如果你在分支master中有自己的提交显然会很糟糕——它只是将更新转储到FETCH_HEAD。
这就是事情变得特别怪异的地方。假设您运行git fetch remote-X master branch,即至少给出一个,也许是几个,refspecs,但都没有冒号。
-
如果您的 git 版本早于 1.8.4,则更新仅进入FETCH_HEAD。如果您提供了两个无冒号的 refspec,FETCH_HEAD 现在包含 两个 行:
676699a0e0cdfd97521f3524c763222f1c30a094 branch 'master' of ...
222c4dd303570d096f0346c3cd1dff6ea2c84f83 branch 'branch' of ...
-
如果您的 git 版本是 1.8.4 或更高版本,则更新将在那里进行——这部分没有改变——但也是,获取会借此机会永久地记录这些分支 在它们适当的远程分支中,如远程的fetch = 行所给出的。
不管出于什么原因,git fetch 只为实际更新的远程分支打印出更新-> 行。由于它总是记录FETCH_HEAD中的所有更新,它总是在此处打印分支名称。
(除了需要 git 1.8.4 或更高版本之外,更新远程分支的另一个问题是那些 fetch = 行必须存在。如果它们不存在,则没有映射知道 fetch 知道重命名 @ 987654401@到refs/remotes/remote-X/*。)
换句话说,git 1.8.4 和更新版本确实会“机会性地更新”所有远程分支。老版本的git在git push上做,所以之前一直不一致。即使在 git 1.8.4 中,它仍然与git pull 不一致,我认为(尽管我使用git pull 不足以注意到:-));这应该在 git 1.9 中修复。
现在让我们回到git fetch <em>remote</em> 和git fetch <em>remote refspec ...</em> 之间的区别。
-
如果您运行 git fetch <em>remote</em>,即省略所有 refspec,则 fetch 会像往常一样退回到 fetch = 行。 fetch 操作带来了来自fetch 行的所有引用。 所有这些都进入FETCH_HEAD,但这次它们被标记为“不可合并”(带有标签,我将其更改为一个空格以更好地适应网页):
676699a0e0cdfd97521f3524c763222f1c30a094 not-for-merge branch ...
不是分支的 Refs,例如,refs/notes/ 带来的 refs,改为读取:
f07cf14302eab6ca614612591e55f7340708a61b not-for-merge 'refs/notes/commits' ...
同时,如果需要,远程分支 refs 会更新,并通过消息告诉您哪些已更新:
22b38d1..676699a master -> remote-X/master
再次,所有内容都被转储到FETCH_HEAD,但只有“需要更新”的参考文献被更新和打印。新分支打印“新分支”,旧分支打印其缩写的 old-and-new SHA-1,如上面的master -> remote-X/master。
-
另一方面,如果您运行git fetch <em>remote refspec ...</em>,则获取仅指定的参考规范。这些全部像往常一样进入FETCH_HEAD,6但是这次每一个都被打印出来。然后,如果您的 git 是 1.8.4 或更高版本,任何可以映射(通过合理的 fetch = 行)和需要更新的参考更新都将更新和打印也 :
* branch master -> FETCH_HEAD
* branch branch -> FETCH_HEAD
22b38d1..676699a master -> remote-X/master
如果您的 git 版本早于 1.8.4,则在这种情况下不会发生 remote-X/master 的更新——或者更确切地说,除非您的命令行参考规范之一是 refs/heads/master:refs/remotes/remote-X/master 或 @,否则不会发生更新987654424@,或者前面加号的变体。
1这不是一个很好的错误消息。 remote-X 参数从来不应该是一个“存储库”,它应该是一个“远程”!如果 git 在这里说一些更丰富的信息可能会很好。
2git 远程协议有一个缺陷:HEAD 通常是间接引用,因为它是远程上的当前分支,所以它应该以“ref: refs/heads/master”的形式出现例如,但它作为完全解析的 SHA-1 出现。至少一个 git 命令 (git clone) 尝试通过将此 SHA-1 与每个分支头的 SHA-1 进行比较来“猜测”远程上的当前分支。例如,在上面,很明显远程是“在分支主机上”,因为HEAD 和refs/heads/master 具有相同的 SHA-1。但是,如果多个分支名称指向同一个提交,并且HEAD 与该提交ID 匹配,则无法判断HEAD 处于哪个分支(如果有)。遥控器也可能处于“分离 HEAD”状态,在这种情况下,它不在 any 分支上,无论 SHA-1 值如何。
2019 年编辑:此错误已在 Git 版本 1.8.4.3 中修复。只要两个 Git 版本(在您要克隆的机器上和您自己的机器上)都是 1.8.4.3 或更高版本,Git 就不必再猜测了。
3加号表示“接受强制更新”,即接受将被分支的“只是快进”4 规则拒绝的更新,或“从不更改标签”5 用于标签。
4当提交有向无环图中的旧 SHA-1 是祖先时,可以对标签进行“快进”,将其从旧的 SHA-1 更改为新的新的 SHA-1。
5“从不更改标签”规则是 git 1.8.2 中的新内容。如果你的 git 比这更旧,git 也使用标签的分支规则,允许快速转发而无需“强制更新”。
6但这次没有not-for-merge。基本上,当您提供无冒号的 refspec 时,git fetch 假定它们是“用于合并”并将它们放入 FETCH_HEAD 以便 git merge FETCH_HEAD 可以找到它们。 (我还没有测试过非分支引用会发生什么。)