这是我的看法。
参考规范
首先,让我们揭穿所谓的“refspec”是什么,因为将来会需要它。
refspec 代表“参考规范”。引用是指向提交或另一个引用的东西。一个分支是一个引用,一个标签是一个引用;HEAD
是一个特殊的(所谓的“符号”)引用。让我们暂时忽略各种引用之间的区别。
通常将引用简称为“refs”。
refspec 具有以下形式:
[+]source[:destination]
source
指定要从中获取一系列提交的引用。
destination
,如果指定,则指定对更新的引用,其中包含来自 的一系列提交source
。
- 加号(如果包含)会强制更新发生,即使 指向的提交行
destination
未完全包含在 指向的提交行中source
。
refspecs 用于获取和推送,在这些情况下源和目标 refs 的含义是相反的,显然:当我们获取时,源 refs 在远程 repo 中,而当我们推送时,源 refs 在我们的本地回购。
对于普通的 Git(我的意思是它的参考实现),refs 只是名称相对于其存储库根目录的文件(对于普通存储库,这是“.git”目录,对于裸存储库,这是存储库根目录)。
一些引用,例如HEAD
(or ORIG_HEAD
or MERGE_HEAD
or FETCH_HEAD
) 位于存储库的根目录中,而其他一些引用,例如分支和标签以及远程分支位于名为“refs”的目录中,它们在各自的子目录下进一步排序:
- 分支位于“refs/heads”子目录中。
- 标签位于“refs/tags”中。
- 远程分支位于其相应远程的“refs/remotes/<remote>”目录中。
- 注释在“参考/注释”中。
- ...
因此,代表名为“master”的分支的 ref 的全名确实是“refs/heads/master”,从命名的远程存储库“origin”获取的名为“foo”的远程分支是“refs/remotes/origin/foo” ”。
Git 使用智能查找机制允许您在大多数情况下缩写 ref 名称,但是当您想要严格时存在任何歧义时,可能会使用完整(或“更完整”)名称。血淋淋的细节在手册git rev-parse
中。
(需要这些关于完整 ref 名称的解释来理解下面解释的简单调用是如何git fetch
工作的。)
抓取
现在回到获取。
git fetch
基本上有几种操作模式,具体取决于您提供给它的参数:
git fetch <git_url>
在通过 访问的远程存储库中获取refHEAD
指向的任何内容<git_url>
,获取其历史记录并将其提示提交的 SHA-1 名称写入.git/FETCH_HEAD
文件。
在裸存储库HEAD
中,通常指向名为“master”的分支(尽管可能会更改)。在非裸(正常)存储库HEAD
中,显然指向当前签出到工作树的内容。
git fetch <git_url> <refspec> ...
使用指定的 refspecs 从远程存储库中获取(而不是HEAD
),并且 - 对于那些包含“目标”部分的 refspecs,它还尝试使用获取的历史记录更新那些本地 refs。每个提取的 refspec 的 SHA-1 名称都会写入.git/FETCH_HEAD
文件。
展示:
git fetch git://server/repo.git master devel test
From git://server/repo.git
* branch master -> FETCH_HEAD
* branch devel -> FETCH_HEAD
* branch test -> FETCH_HEAD
请注意,在获取提交时,没有更新本地引用。
现在让我们以更复杂的方式来做:
git fetch git://server/repo.git master devel:foo test:bar
From git://server/repo.git
* branch master -> FETCH_HEAD
* [new branch] devel -> foo
* [new branch] test -> bar
如您所见,git fetch
创建了两个本地分支,“foo”和“bar”,而“master”只是被获取但没有用于在我们的本地 repo 中创建任何东西。所有三个远程引用的 SHA-1 名称仍然在.git/FETCH_HEAD
文件中。
git fetch <remote> <refspec> ...
-作为使用配置或自动创建<remote>
的命名远程存储库- 行为与具有显式的表单完全相同,但从该命名远程的配置中获取要使用的 url。git remote add
git clone
<git_url>
git fetch
git fetch <remote>
从该远程获取所有分支并尝试更新(或创建,如果它们不存在)该远程的所谓远程分支。
这对于理解这个操作过程不是魔术至关重要。当您添加一个命名远程(或git clone
为您创建一个)时,Git 会在您的存储库配置中添加几个描述该远程的变量,其中一个将remote.<name>.fetch
是一个 refspec 参数,如果没有直接传递 refspec 则使用该参数git fetch
( !)。
现在,对于名为“origin”的远程,Git 将创建参数remote.origin.fetch
设置为+refs/heads/*:refs/remotes/origin/*
. 你可以去你当地的仓库自己看看:
$ git config --get remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*
“最神奇”很简单git fetch
——它就像遥控器上面的呼叫被自动接听一样。
拉取(获取加合并)
将其拉出定义为“提取然后将提取的内容合并到当前签出的分支”。传递给git pull
(或缺少)的 refspec 直接传递给git fetch
,因此上述所有规则都适用。
为什么git pull <remote> <branch>
`git fetch 不更新匹配的远程分支?
要理解这个“奥秘”,请记住 Git 在远程分支及其远程跟踪分支的背后使用了某些方便的魔法(当 yogit fetch origin
后面跟着时git branch foo origin/foo
,“origin/foo”是一个远程分支和“foo”是一个远程跟踪分支,因为它跟踪相应的远程分支)。您应该记住的另一点是远程分支用于在上次获取时捕获其远程存储库中相应分支的状态。
现在假设您将远程跟踪分支推送到远程存储库中的同名分支(最常见的情况)。假设你这样做了:
$ git fetch origin
(this created the "origin/foo" branch)
$ git chekcout -b foo origin/foo
(this created a new local branch "foo" tracking "origin/foo")
$ git commit ...
$ git push origin foo
在推送时,Git 注意到如果您同时获取匹配的远程分支,您将在其中获得与刚刚推送的相同的提交。所以它继续并使“origin/foo”指向同一个提交“foo”指向。
现在回到git pull
...在您的情况下,git pull
完成后,Git 注意到您刚刚使用从您在本地拥有远程分支的分支获得的提交更新了远程跟踪分支,因此它继续并更新此远程分支因为这是它最后一次看到的状态。
让我们回顾一下:
- “origin/foo”是一个远程分支,它捕获远程存储库“origin”中名为“foo”的分支的状态。
- “foo”是一个跟踪“origin/foo”的本地分支。
你做
git checkout foo
git pull origin foo
Git 会更新你的 "foo" 分支,但这意味着 "foo" 现在至少有一个提交 "origin/foo" 会收到,如果你只是git fetch origin
or的话git push origin foo
,所以 Git 继续并更新 "origin/foo" 。
也就是说,在我看来,您的情况下远程分支的更新不是因为git fetch ...
已运行,而是因为本地远程跟踪分支因合并而更新。现在有意义吗?
进一步阅读
我建议您只阅读存储库中的“.git/config”文件,以了解如何配置所有这些“远程内容”。