这是“TL;DR”版本(掩盖了许多特殊情况):git fetch 总是更新FETCH_HEAD,在各种情况下不止一行。它有时会更新“远程分支”,即全名以refs/remotes/. 其余的主要是关于“有时”,这取决于给定的参数数量git fetch和 git 版本。
我有机会对此进行测试。让我们区分三种情况,所有这些都假设在git fetch没有额外选项的情况下运行,-a甚至--all. 让我们也排除更奇怪的变体git fetch,比如直接使用 URL,或者insteadOf条目,或者列在.git/remotesor中的文件.git/branches。(我承认我只是在猜测,但我认为这些是[remote "name"]条目进入 git 配置文件前几天的遗留物。编辑,2019:事实证明这是正确的。)
- git fetch,并且没有其他论点。
 - Git 确定您当前的分支(以通常的方式,通过阅读,但您当然可以通过or- HEAD看到它是什么)。然后,它会为该分支查找一个配置条目,并将其命名为. 例如,假设您在分支上并且具有(在其他条目中):- git branch- git status- remote- dummy- .git/config
 - [branch "dummy"]
    remote = remote-X
 - 在这种情况下- git fetch相当于- git fetch remote-X。之后,这相当于案例 2,即:
 
- git fetch remote(除此之外没有更多的争论)。
 - 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
 - 对- HEADref 的处理并不完全一致(我已经看到它的行为很奇怪),但通常在这里它只是被丢弃了。2  其余分支根据- fetch =refspec 进行重命名和更新。(如果有多个- fetch =refspecs,它们会根据它们全部重命名和更新。例如,这主要用于在 下引入- refs/notes/或创建自己的“远程标签”命名空间- refs/rtags/。)
 - branch在这种情况下, fetch 将带来两个分支和所需的任何对象- 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 remote refspec(该- refspec部分实际上是一个或多个参考规范,如下所述)。
 - 这类似于案例 2,只是这一次,“refspecs”是在命令行上提供的,而不是来自- fetch =远程的配置条目。但是,这里的 fetch 行为完全不同。
 
在这种特殊情况下,让我们暂停一下并正确描述 refspec。(Refspecs 也会出现,git push但是,与 git 一样,实现细节会泄漏出来,它们在那里的工作方式略有不同。) refspec 有一个可选的前导加号(+),我将在这里忽略它;3然后是两部分,用冒号 ( :) 分隔。两者通常都只是一个分支名称,但在分支名称的情况下,您可以(并且fetch =行可以)拼出“完整”引用名称。refs/heads/branch
对于获取操作,左侧的名称是遥控器本身的名称(如示例所示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 借此机会将这些分支永久记录在其适当的远程分支中,如远程行所给出的那样- fetch =。
 - 但是,无论出于何种原因,- git fetch只为实际更新的远程分支打印一条更新- ->行。由于它总是在 中记录所有更新- FETCH_HEAD,所以它总是在此处打印分支名称。
 - (另一个问题,除了需要 git 1.8.4 或更高版本之外,更新远程分支是这些- fetch =行必须存在。如果它们不存在,则 fetch 没有映射知道要重命名- refs/heads/*为- refs/remotes/remote-X/*.)
 
换句话说,git 1.8.4 和更新版本确实“机会性地更新”了所有远程分支。旧版本的 git do it on git push,所以之前一直不一致。即使在 git 1.8.4 中,它仍然与 不一致git pull,我认为(尽管我没有git pull足够注意:-));这应该在 git 1.9 中修复。
现在让我们回到和之间的区别。git fetch remotegit fetch remote refspec ...
- 如果您运行,即省略所有 refspecs,则 fetch 会像往常一样回到行。fetch 操作从行中获取所有 refs 。  所有这些都进入,但这次它们被标记为“不可合并”(带有标签,我将其更改为一个空格以更好地适应网页):- git fetch remote- 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中,但只有“需要更新”的 refs 被更新和打印。新分支打印“新分支”,旧分支打印其缩写的新旧 SHA-1,- master -> remote-X/master如上所述。
 
- 另一方面,如果您运行,则 fetch仅带来指定的 refspecs。这些都像往常一样进入, 6但这次每一个都被打印出来。然后,如果您的 git 是 1.8.4 或更高版本,任何可以映射(通过合理的行)和需要更新的参考更新也会更新和打印:- git fetch remote refspec ...- FETCH_HEAD- 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- refs/heads/*:refs/remotes/remote-X/*
 
1这不是一个很好的错误消息。这个remote-X论点不应该是一个“存储库”,它应该是一个“远程”!如果 git 在这里说更多信息可能会很好。
2 git 远程协议存在缺陷:HEAD 通常是间接引用,因为它是远程上的当前分支,因此它应该以“ref: refs/heads/master”的形式出现,但它却作为完全解析 SHA-1。至少有一个 git 命令 ( git clone) 尝试通过将此 SHA-1 与每个分支头的 SHA-1 进行比较来“猜测”远程上的当前分支。例如,在上面,很明显远程是“在分支主机上”,HEAD并且refs/heads/master具有相同的 SHA-1。但是,如果多个分支名称指向同一个提交,并且HEAD与该提交 ID 匹配,则无法判断哪个分支(如果有)HEAD处于打开状态。遥控器也可能处于“分离 HEAD”状态,在这种情况下它'
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这个时间。基本上,当您提供无冒号的 refspecs 时,git fetch假定它们是“用于合并”并将它们放入FETCH_HEAD以便git merge FETCH_HEAD可以找到它们。(我没有测试过非分支引用会发生什么。)