警告:长。 TL;DR 版本:您正在查看git fetch 输出,而git fetch 根本不会影响您的master,而是git pull 的git merge 部分会影响您的master。但是,您的 git fetch 正在更新远程跟踪分支 origin/br1,在一种情况下更新甚至创建本地分支 br1。
git pull 是一个方便的脚本
永远记住git pull 只是一个方便的脚本,它为你运行另外两个 git 命令:首先,git pull 将你的参数传递给git fetch。完成后,git pull 运行 git merge(或者,如果有指示,git rebase),但您原始问题中引用的所有操作都纯粹发生在 git fetch 中。 (“更新”部分有一点来自git merge,我稍后会讲到。)
如果您不向git pull 提供远程 参数,pull 脚本会从您当前分支的配置中提取一个。在这种情况下,它提取的显然是origin。因此,如果您运行 git pull 而不指定 origin,实际上您正在运行 git pull origin。
如果您不向 git pull 提供 refspec 参数,pull 脚本会从您当前分支的配置中提取一个非常简单的配置 - 在这种情况下,无论您从 git config --get branch.master.merge 中看到什么,显然是br1。所以这意味着如果你运行git pull origin,你实际上是在运行git pull origin br1。1
同样,所有这些都只是传递给git fetch,因此无论您运行git pull、git pull origin 还是git pull origin br1,所有这些最终都会调用:
git fetch origin br1
(您也可以手动完成,您会看到上面的内容)。
我们稍后会在下面联系git fetch origin br1:br1。
可能的误解的背景
让我们再次简要看一下您的设置语句:
假设远程仓库有一个远程分支br1 checkout,本地仓库有一个master 分支。
当前在远程签出的分支(如果有)与fetch 无关大部分。 fetch 做的第一件事(或第一件事)是连接到远程并要求它提供所有引用的列表及其相应的 SHA-1(您可以通过运行 git ls-remote 来查看 git fetch 可以看到的内容) .遥控器的 HEAD 包含在该列表中,这允许您指示您的 fetch 使用它,但如果您不这样做,您的 fetch 会忽略它(遥控器的 @ 987654368@ 主要仅用于控制初始git clone 上的默认初始分支。
local 仓库中的当前分支 很重要,但原因有两个:
- 如果您不向
git pull 提供额外的参数,它会根据您当前的分支找到它们;和
-
fetch 成功后,git pull 运行 git merge 或 git rebase,这将使用您当前的分支。
同样,您当前的分支是master,所以pull 将使用branch.master.remote 和branch.master.merge 作为默认的remote 和refspec 参数。 2 这就是我们如何从原始输出中推断出它们分别是origin 和br1。
开启git fetch
回到git fetch,它的作用是与远程 git 服务器进行一些协商,以找出可用的引用(主要是分支和标签)以及它们对应的 SHA-1 值是什么。一旦它有了这些信息,它就会查看你要求它带来的参考资料。如果您列出了一个特定的参考,例如 br1,那么它将带来一个参考。
当然,除了每个引用,它还必须带来任何新对象(提交本身,及其关联的树和文件,以及任何父提交及其所需的树和文件),以便您获得所有从那个特定的点倒退的历史。当然,您已经拥有的任何历史记录都可以跳过。3
作为VonC already noted,git 在git fetch <em>remote</em> <em>refspec</em> 上的行为在git 1.8.4 及更高版本中发生了变化。过去的情况是,如果您运行 git fetch <em>remote</em> <em>refspec</em>,您的 refspec 覆盖该远程的 git 配置条目中的规则,但现在它只是 从中选择。默认情况下,名为origin 的遥控器的规则集是+refs/heads/*:refs/remotes/origin/*,因此您的br1 参考规范会从该规则集中选择一个项目。
让我们暂停一下,看看如果您只使用 三个 参数运行 git fetch 会发生什么,如下所示:
$ git fetch origin
在这里,您正在指示您的本地 git 连接到远程,找出它有什么,并带来 所有 分支。它这样做的方式(和原因)如上所述:它连接,获取一个列表,然后查阅git config --get-all remote.origin.fetch 的输出。4 这是一个“refspecs”列表,一个每git config --get-all 行。
由于remote.origin.fetch 的标准行(单行)是+refs/heads/*:refs/remotes/origin/*,因此您的本地git 将采用与refs/heads/* 匹配的每个引用名称。也就是说,它将占用origin 上的所有分支,因为分支只是“名称以refs/heads/ 开头的引用”。它对这些分支的作用 由该 refspec 的右侧确定:它将 refs/heads/ 替换为 refs/remotes/origin/。
结果是一个“远程跟踪分支”。如果远程有一个分支master,您的本地 git 会将其转换为origin/master。如果遥控器有一个br1,你的本地 git 会将它翻译成origin/br1。对于远程上的每个分支,您都会获得一个(本地)远程跟踪分支,其名称以 origin/ 开头。5
回到 git fetch origin br1 的例子,我们现在可以看到发生了什么:我们本地的 git 带来了 br1,它原来是一个分支,所以它的全名是 refs/heads/br1。因此,它匹配标准的remote.origin.fetch 行并且refs/heads/br1 被转换为refs/remotes/origin/br1,这导致git 打印出origin/br1:
9188a5d..97d4825 br1 -> origin/br1
左侧名称br1是遥控器上引用的简称,右侧名称origin/br1是git fetch更新的引用简称。
在过去,您会看到类似的东西——您仍然可能会看到它:
* branch name -> FETCH_HEAD
这表明git fetch 在远程找到了一个名为name 的分支(即refs/heads/name 形式的引用)并将其带到您的本地repo 并放入FETCH_HEAD。 FETCH_HEAD 是什么?这是一个特殊文件,几乎只存在于git pull 脚本中。 (它的工作原理很像参考,但它有一种特殊的格式,可能包含多个 SHA-1。)
现在我们(终于)准备好处理br1:br1 案子了。在这里,您要告诉您当地的git fetch 带来参考br1。它照常进行——调用遥控器,发现br1 确实是refs/heads/br1,并带来引用和任何需要的对象——但这一次,除了查阅remote.origin.fetch 行之外,它还编写了新的SHA -1 进入指定的参考你。
在这种情况下,您指定了没有限定条件的 br1:不是refs/heads/br1,不是refs/remotes/origin/br1,只是br1。在这种情况下,git 看到它是远程上的refs/heads/ 引用,这意味着它是一个分支;所以 git 也会在你的末尾添加refs/heads/,并创建或更新你自己的refs/heads/br1。
换句话说,这将创建或更新您的本地分支br1。
另外,git 仍然应用remote.origin.fetch 行,它仍然是+refs/heads/*:refs/remotes/origin/*,所以它仍然会更新你的远程跟踪分支origin/br1(全名refs/remotes/origin/br1)。 这就是你得到命令 1 的输出的原因。
开启git merge
FETCH_HEAD 呢?好吧,这就是git pull 的其余部分返回的地方:在执行git fetch 步骤之后,pull 脚本运行git merge 或git rebase。它合并(或变基)是 git fetch 留在 FETCH_HEAD 文件中的任何内容(带有一些特殊的大小写和其他注意事项,我不会在这里讨论)。
当您当前的分支是master,但您指示git pull 拉动origin br1,是git merge 步骤使master 与br1 保持同步。 更多确切地说,合并让您在git fetch 完成时更新您的origin/br1 副本——有可能在您的git fetch 完成之后,其他人做了一个git push 更新br1你的遥控器。
如果可能的话,合并是一个“快进”合并,但在这里我不会再详细说明。我只想指出这是可能的,所以它完成了;这是更新中的Fast-forward 行。
在任何情况下,合并都将自当前分支的合并基础和目标提交(git fetch 留在 @987654464 中的原始 SHA-1 @ 文件,这也是 origin/br1 的新 SHA-1,在一种情况下,新的或更新的本地分支 br1 的新 SHA-1。
在 1.8.4 之前的 git 版本中,origin/br1 远程跟踪分支没有更新。尽管如此,在FETCH_HEAD 文件中一切仍然有效,而且它甚至比在较新的 gits 中更多令人困惑,我们可以说你现在与 origin/br1 保持同步不必对“br1”非常严格和挑剔,因为它在您运行 git fetch 时位于遥控器上。
那个加号是什么?
眼尖的读者会注意到+refs/heads/*:refs/remotes/origin/* 中的+。这个+ 符号表示“强制更新”。通常,在更新分支引用时——任何以refs/heads/ 开头的引用——git 不会允许更新,除非它是“快进”标签更新。在 refspec 中设置 force 标志允许该特定更新。在命令行上使用--force 也允许该更新以及所有其他参考更新。换句话说,加号只是--force 的更具针对性(单一参考规范)的版本。
1这是夸大其词:有时它使用三个参数git fetch。
2remote 位始终为 true,但 refspec 位可能为空,pull 脚本确定要使用哪个 refspec在git fetch 完成后稍后申请。
3默认情况下,fetch 操作还将带来与它带来的任何提交 ID 匹配的任何标记名引用。如果你自己运行git fetch,你可以改变fetch处理这些的方式,但是如果你让git pull运行git fetch你会得到这个默认行为。请注意,做出这些决定的是您的本地 git:远程 git 只是将所有内容显示到您的本地 git,然后您的 git 决定是否向您的存储库添加标签。
4从技术上讲,git fetch 只是调用执行此操作的 C 代码,而不是实际运行 git config --get-all。在任何情况下,如果remote.origin.fetch 有多个配置条目,git fetch origin 确实会应用所有这些条目。不过,它的实现方式有点复杂,这里我将跳过血淋淋的细节。
5远程跟踪分支实际上只是名称以refs/remotes/ 开头的引用,就像本地分支是名称以refs/heads/ 开头的引用一样。这是 git 中的一般情况:您的标签是名称以 refs/tags 开头的引用。 git stash 脚本使用单个特殊引用 refs/stash。 Git 的“注释”存储在refs/notes/ 下,您可以发明自己的引用:只需选择不同的起始字符串,并希望将来没有其他人为新的 git 功能选择相同的字符串。 :-)