git fetch 究竟是做什么的?

6
现在我想将远程分支命名为origin/branch1的代码合并到我的本地分支branch1中,因为我的合作伙伴在我们上次合并后向分支1推送了一个新的提交,而我自那以后没有提交过,并且想要从这个提交中获取更新。我使用了以下命令:
$ git fetch origin branch1
Compressing...
*branch branch1 ->FETCH_HEAD
$ git merge origin/branch1
Already up-to-date

这不是我想要的结果。在进行这个操作之前,我的想法是使用 fetch 获取我的合作伙伴添加的内容,并让远程分支 origin/branch1 被更新。然而,“Already up-to-date” 意味着我未能获取本地 branch1 中的更新。然后我通过以下方式检查了 origin/branch1 的 sha1 值:

$ git ls-remote origin

我们发现在上次合并后,它仍保留着旧提交的陈旧值。这表明 git fetch origin branch1 无法更新 origin/branch1。我进行了另一个实验,在该实验中,我的合作伙伴在他的端口创建了另一个名为“branch2”的分支,并将其推送到远程源中。然后我仍然使用了

$ git fetch origin branch2
Compressing...
$ git merge origin/branch2
No branch named "origin/branch2"

"压缩"命令告诉我,第一个命令成功从origin中下载了位于branch2的某些内容,然而第二个命令告诉我没有名为origin/branch2的分支!因此我得出结论:既不能通过git fetch origin branchname在本地更新origin/branchname,也不能在不存在的情况下创建远程分支。

当我把git fetch origin branch#替换成git fetch origin时,所有的git merge命令都按照我的预期工作了。

然而,我经常看到这种组合

$ git fetch remote branch-name
$ git merge remote/branch-name

我的问题是git fetch remotegit fetch remote branch-name有什么区别?在哪些情况下,这种组合可以按照我的意愿成功运行?

1
这些内容都在git help fetch中涵盖了,您是否有一些文档没有解答的具体问题? - Andrew C
1
@AndrewC 我已经逐字逐句地阅读了 git help fetch,特别是涉及到 git fetch [<repository> [<refspec>...]] 的部分。然而,由于我对 git 的知识有限,我认为目前它无法解释我上述描述的情况。你能否告诉我 help 中的哪一部分可以帮助我?非常感谢。 - troore
2
git fetch remote branch 会将分支获取到 FETCH_HEAD。通常你想要使用 git fetch remote branch:branch,这样可以将其获取到同名的本地分支。当你没有指定时,它会回退到你的配置中,这个配置可能会指定(在 .git/config 中查找带有 fetch 的行)。 - Andrew C
@AndrewC 谢谢,安德鲁。我知道这一点。但我想知道的是 git fetch origin branch1 是否会更新本地的远程分支 origin/branch1。我以为会,但在我的情况下并没有。每当我只想更新一个特定的远程分支时,我都必须使用 git fetch origin 来更新所有远程分支。 - troore
@AndrewC 我的fetch配置是:fetch = +refs/heads/*:refs/remotes/origin/*。感谢你的建议和git fetch的帮助,我现在理解了git fetch remote branch:branch的用法。但是当我没有指定本地分支时,“回退”的行为意义仍然不清楚。 - troore
显示剩余5条评论
2个回答

5
如果您想知道git fetch具体是在做什么或其他任何git命令,只需在其前面加上GIT_TRACE=1,它会给您输出跟踪信息以及调用的任何其他命令,例如:
$ GIT_TRACE=1 git fetch origin master
03:08:15.704945 git.c:348               trace: built-in: git 'fetch' 'origin' 'master'
03:08:15.706183 run-command.c:347       trace: run_command: 'ssh' 'git@github.com' 'git-upload-pack '\''FOO/BAR.git'\'''
03:08:16.006394 run-command.c:347       trace: run_command: 'rev-list' '--objects' '--stdin' '--not' '--all' '--quiet'
03:08:16.013096 run-command.c:347       trace: run_command: 'rev-list' '--objects' '--stdin' '--not' '--all'
03:08:16.013625 exec_cmd.c:129          trace: exec: 'git' 'rev-list' '--objects' '--stdin' '--not' '--all'
03:08:16.016617 git.c:348               trace: built-in: git 'rev-list' '--objects' '--stdin' '--not' '--all'
From github.com:FOO/BAR
 * branch            master     -> FETCH_HEAD
03:08:16.153070 run-command.c:347       trace: run_command: 'gc' '--auto'
03:08:16.153748 exec_cmd.c:129          trace: exec: 'git' 'gc' '--auto'
03:08:16.157704 git.c:348               trace: built-in: git 'gc' '--auto'

基本上是通过ssh连接到远程主机,运行git-upload-pack将打包的对象发送回来,然后由git-fetch-pack接收另一个仓库中缺失的对象。在man git-upload-pack中可以读到:“被git fetch-pack调用,了解另一侧缺少哪些对象,并在打包后发送它们。”而在man git-fetch-pack中可以读到:“在可能的远程仓库上调用git-upload-pack,并请求其发送此仓库缺少的对象以更新指定的分支。扫描本地refs/层次结构以找出可用的提交列表,并将其发送到在另一端运行的git-upload-pack。”
答案是,git fetch remotegit fetch remote branch-name之间的区别在于,当您不指定<refspec>参数(如分支)时,它会从一个或多个其他仓库中获取所有分支和/或标记(refs,参见:git ls-refs),以及完成其历史记录所需的对象。默认情况下,仅下载可由获取的对象到达的标记(例如,您最终只得到指向您感兴趣的分支的标记)。而当您使用明确的分支和/或标记运行时,git首先确定需要获取什么,然后只获取相关的引用(分支或标记)。例如,git fetch origin master将仅获取主分支。

-1
运行 git-upload-pack,将打包的对象发送回git-fetch-pack,从另一个存储库接收缺失的对象。
您可以在 Git 2.19(2018年第三季度)中了解更多关于git fetch-pack的信息,因为"git fetch-pack --all"过去会在看到指向提交以外的对象的注释标签时不必要地失败。
请看 提交 c12c9df(2018年6月13日)作者是Kirill Smelkov(navytux
得到Junio C Hamano(gitster的帮助。
请看 提交 e9502c0(2018年6月11日)作者是Jeff King(peff
得到Junio C Hamano(gitster的帮助。
(由Junio C Hamano -- gitster --提交 0079732中合并,2018年6月28日)

Fetch-pack --all 在处理不寻常的标签时出现问题,详见5f0fc64fetch-pack: eliminate spurious error messages, 2012-09-09, Git v1.8.0), 并且直到最近才在e9502c0中得到修复(fetch-pack: don't try to fetch peel values with --all, 2018-06-11)。

fetch-pack: 不要尝试使用--all获取剥离值

当“fetch-pack --all”在远程看到一个tag-to-blob时,它会尝试获取标签本身(“refs/tags/foo”)和远程广告的剥离值(“refs/tags/foo^{}”)。请求后者所指向的对象可能会导致upload-pack抱怨“not our ref”,因为它不会用OUR_REF标记剥离对象(除非它们是其他引用的顶部)。
可以说upload-pack应该标记这些剥离对象。但过去从未这样做过,因为客户端通常只会请求标签并期望同时获得剥离值。这就是"git fetch"以及旧版本的"fetch-pack --all"的工作方式。 让我们明确地测试所有相关的4个标签对象的情况 指向:
- 一个blob, - 一个tree, - 一个commit,以及 - 另一个标签对象。
引用的标签对象本身是从常规refs/tags/*命名空间下引用的。
在e9502c0(Git 2.19)之前,fetch-pack --all失败了,例如:

在 Git 2.25(2020 年第一季度)中,fetch-pack 包括trace2 注释

请参见提交 9e5afdf(2019 年 11 月 19 日),由Erik Chen(erikchen撰写。
(由Junio C Hamano -- gitster --提交 76c6824中合并,2019 年 12 月 5 日)

fetch:添加trace2工具

签名作者:Erik Chen

fetch-pack.c中添加trace2区域,以更好地跟踪抓取过程中各个阶段所花费的时间:

  • 解析远程引用并查找截止点
  • 标记本地引用为完成
  • 标记完成的远程引用为公共引用

对于具有许多引用的存储库,所有阶段都可能潜在缓慢。


例如:(然后您可以了解git fetch的操作)

D:\git\git>set GIT_TRACE2_EVENT=1

D:\git\git>git fetch
23:58:43.225088 exec-cmd.c:237          trace: resolved executable dir: D:/newenv/prgs/gits/PortableGit-2.24.0.2-64-bit.7z/mingw64/bin

{"event":"version","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.234089Z","file":"common-main.c","line":48,"evt":"2","exe":"2.24.0.windows.2"}
{"event":"start","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.234089Z","file":"common-main.c","line":49,"t_abs":0.011593,"argv":["git.exe","fetch"]}
{"event":"data_json","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.260090Z","file":"compat/win32/trace2_win32_process_info.c","line":118,"repo":0,"t_abs":0.037686,"t_rel":0.037686,"nesting":1,"category":"process","key":"windows/ancestry","value":["git.exe","cmd.exe","explorer.exe"]}
{"event":"def_repo","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.261089Z","file":"repository.c","line":130,"repo":1,"worktree":"D:/git/git"}

23:58:43.262092 git.c:439               trace: built-in: git fetch
{"event":"cmd_name","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.262092Z","file":"git.c","line":440,"name":"fetch","hierarchy":"fetch"}
{"event":"region_enter","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.281090Z","file":"builtin/fetch.c","line":1361,"repo":1,"nesting":1,"category":"fetch","label":"remote_refs"}
{"event":"child_start","sid":"20200111T225843.226090Z-H91a50d96-P00007560","thread":"main","time":"2020-01-11T22:58:43.284088Z","file":"run-command.c","line":735,"child_id":0,"child_class":"remote-https","use_shell":false,"argv":["git","remote-https","origin","https://github.com/git/git"]}
23:58:43.284088 run-command.c:663       trace: run_command: GIT_DIR=.git git remote-https origin https://github.com/git/git

从Git 2.31(2021年第一季度)开始,ls-refs协议操作(也被git fetch使用)已经进行了优化,以缩小在生成响应时遍历的refs/子层次结构。
这进一步说明了git fetch的工作原理。

请参见提交记录 b3970c7, 提交记录 16b1985 (2021年1月20日),作者为Taylor Blau (ttaylorr)
请参见提交记录 83befd3 (2021年1月20日),作者为Jacob Vosmaer (jacobvosmaer)
(该提交记录已被Junio C Hamano -- gitster --合并于提交记录 6254fa1,2021年2月5日)

ls-refs.c:遍历不相交的“ref-prefix”集合的前缀

原始补丁由Jacob Vosmaer提供
签署者:Taylor Blau

ls-refs 对整个 ref 命名空间执行单个修订遍历,并将与给定的 ref 前缀之一匹配的内容发送给用户。

如果总共有很多 refs,这可能会很昂贵,但是与给定前缀覆盖的部分相比,它们很少。

为了尝试减少遍历的 refs 数量和发送的 refs 数量之间的差异,只遍历在给定前缀的最长公共前缀中的引用。
这非常类似于 b31e268 中采取的方法(“ref-filter.c:查找不相交的模式前缀”,2019-06-26,Git v2.23.0-rc0 -- merge 列在 batch #6 中),该方法对多模式 'git for-each-ref'(man) 调用进行类似的操作。

回调函数 'send_ref' 可以忽略任何不以指定前缀之一开头的参数,从而可以忽略额外的模式。

同样,b31e268 中引入的代码可以在元字符处停止,但是我们仅传递严格的前缀。
最坏的情况下,我们会返回太多结果,但是 send_ref 执行的双重检查将丢弃任何不以前缀列表中的内容开头的内容。

最后,如果没有提供前缀,则隐式添加空字符串(它将匹配所有引用),因为这符合现有行为(请参见 "ls-refs.c:ref_match()" 中的“无限制”注释)。


随着 Git 2.38 (2022 年第三季度) 的发布,在 "git fetch"(man) 会话期间进行的共同祖先协商交换现在会留下跟踪日志

这有助于查看 git fetch 的操作内容。

请查看提交记录 a29263c(2022年8月2日),作者为Josh Steadmon(steadmon
(由Junio C Hamano -- gitster --提交记录 098b7bf中合并,日期为2022年8月25日)

fetch-pack:添加协商轮次的跟踪

签名作者:Josh Steadmon
确认者:Jeff Hostetler

目前,V0/V1/V2获取的协商过程中有覆盖整个协商过程的trace2区域。然而,我们需要额外的数据,例如每轮协商的时间或每轮中“haves”的数量。此外,“独立协商”(也称为推送协商)根本没有追踪。拥有这些数据将使我们能够比较各种协商实现的性能,并调试意外缓慢的获取和推送会话。
  • 为所有协商实现(V0+V1、V2和独立协商)添加每轮trace2区域,以及独立协商的总体区域。
  • 为每轮的“haves”数量和“徒劳”的对象以及协商完成后的总轮数添加trace2数据记录。
GIT_TRACE2_EVENT="$(pwd)/trace2" 
git -C myclient fetch --progress origin main 
...
"key":"total_rounds","value":"6"
...

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