git pull 真的等同于 git fetch + git merge 吗?

4
我在以下沙盒中练习git:https://learngitbranching.js.org/?NODEMO 我在两个独立的会话中运行了两组命令。第一组按顺序如下:
git clone
git checkout -b feature
git push
git fakeTeamwork main 1
git fakeTeamwork feature 1
git pull

第二组命令类似,但是我在最后使用git fetch + git merge

git clone
git checkout -b feature
git push
git fakeTeamwork main 1
git fakeTeamwork feature 1
git fetch
git merge o/feature

如果git pull = git fetch + git merge,为什么这两个命令的结果不同?似乎git pull没有更新所有远程跟踪分支。这只是沙盒的缺陷还是Git中实际发生的事情?
注意:命令git clonegit fakeTeamwork只是为沙盒构建的命令。
谢谢!

这些结果有何不同? - lucidbrot
似乎git pull命令不能更新所有远程跟踪分支。 - Jason Li
你是怎么验证的?如果你愿意,我可以在本地测试同样的东西。 - lucidbrot
3个回答

6
看起来git pull并没有更新所有的远程跟踪分支。
是的,这种情况确实可能发生。例如,当git pull运行git fetch origin master时,仅会更新origin/master。
此外,在Git 1.8.4之前的版本中,某些git fetch操作根本不会更新任何远程跟踪名称。在这种情况下,git fetch origin master对origin/master没有影响。
除了这些情况外,还有一些其他特殊情况:
- 如果git pull被配置或告知运行git rebase,则它使用的第二个命令是git rebase而不是git merge。显然的替代方法是git fetch后跟git rebase。但是某些具体情况更加依赖于Git的版本。 - 如果创建一个空仓库,添加远程仓库,并运行git pull,则不存在任何现有分支。(可以稍后使用孤立分支触发此情况。)在这种情况下,git pull将运行一个专门的git checkout命令,而不是合并或变基。
分支的上游设置在这里很重要,具体取决于您向git pull或fetch和第二个命令传递的参数。通常情况下,它们的工作方式大致相同,但是请注意有关某些远程跟踪名称有时无法更新的警告。

1
为什么一个简单的 git pull 命令没有附加参数时,实际上会运行 git fetch origin master 而不是只运行 git fetch?我理解文档是使用给定的参数。 - lucidbrot
2
在旧版本的 git pull 脚本中,调用 git fetch 的方式是(在删除了 pull 特定选项后)git fetch "$@"。因此,选项会按照指定的方式精确传递。在重写的 C 语言版本中,这可能会有所不同。(C 语言重写是 Git 2.6.0 中的新功能。) - torek
3
我个人不使用C版本,所以不确定它是否具备这个功能。此外,由于其发布时间晚于Git 2.0版本,因此可能并不那么重要,因为1.8.4版本修复了机会主义更新的问题。但是,通过在命令行上传递额外参数的一个目标是限制git fetch,使其运行更快。 因此,git pull origin master 运行了 git fetch origin master,它不会(至今仍然不会)获取任何其他分支。 - torek
2
如果我们在上游R/X的分支X上,运行git fetch R X可以加快速度。它是否会这样做?我需要进行实验,然后才能得到Git一个特定版本的答案:也许其他版本的答案不同。如果你自己运行git fetch提供参数,所以你会得到你指定的任何行为。 - torek
1
通常情况下,文档并不会这么细致入微,所以我们只能接受“任何人选择的内容,可能在下一个版本中发生变化”。 :-) - torek
此外,当远程仓库为空(或远程仓库上不存在该分支)时,git pullgit fetch && git merge会产生不同的错误。 - undefined

2
“git pull” 真的等同于 “git fetch” + “git merge” 吗?
简而言之,是的。
但值得指出的是,在没有任何参数的情况下调用 “git fetch” 时,更新哪些 远程跟踪分支是由 remote.<repository>.fetch 配置变量决定的。
如果在一个真实的 Git 仓库中运行 “git config remote.origin.fetch”,您应该会看到以下内容:
+refs/heads/*:refs/remotes/origin/*

这被称为 refspec* 告诉 Git 获取远程仓库 origin 中的所有分支(冒号右侧),并将它们放入本地仓库(冒号左侧)。如果您运行没有任何参数的git pullgit fetch,则会更新所有新的和现有的远程跟踪分支,感谢这个设置。

来自文档

当运行git fetch命令而未在命令行上指定要获取哪些分支和/或标签时,例如 git fetch origingit fetch,则会使用remote.<repository>.fetch值作为refspecs——它们指定要获取哪些引用以及要更新哪些本地引用。上面的示例将获取存在于origin中的所有分支(即与值的左侧匹配的任何引用,refs/heads/*),并更新相应的远程跟踪分支在refs/remotes/origin/*层次结构中。
一旦您开始将分支名称传递给git pullgit fetch,此行为就不再适用,正如@torek他的回答中指出的那样。

1
你可以使用环境变量GIT_TRACE=1来大致查看git的操作。例如,使用我的git 2.42版本:
$ GIT_TRACE=1 git pull
# ... shortened ....
trace: run_command: git fetch --update-head-ok
trace: run_command: git merge FETCH_HEAD

当然,git除了这些命令之外还有其他功能(例如根据本地状态做出决策,发出错误提示)。注意:你可能会看到其他的run_command:跟踪,它们是fetchmerge的一部分。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接