这是“TL;DR”版本(掩盖了许多特殊情况):git fetch
总是更新FETCH_HEAD
,在各种情况下不止一行。它有时会更新“远程分支”,即全名以refs/remotes/
. 其余的主要是关于“有时”,这取决于给定的参数数量git fetch
和 git 版本。
我有机会对此进行测试。让我们区分三种情况,所有这些都假设在git fetch
没有额外选项的情况下运行,-a
甚至--all
. 让我们也排除更奇怪的变体git fetch
,比如直接使用 URL,或者insteadOf
条目,或者列在.git/remotes
or中的文件.git/branches
。(我承认我只是在猜测,但我认为这些是[remote "name"]
条目进入 git 配置文件前几天的遗留物。编辑,2019:事实证明这是正确的。)
git fetch
,并且没有其他论点。
Git 确定您当前的分支(以通常的方式,通过阅读,但您当然可以通过orHEAD
看到它是什么)。然后,它会为该分支查找一个配置条目,并将其命名为. 例如,假设您在分支上并且具有(在其他条目中):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
对HEAD
ref 的处理并不完全一致(我已经看到它的行为很奇怪),但通常在这里它只是被丢弃了。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 remote
git 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
可以找到它们。(我没有测试过非分支引用会发生什么。)