git 协议 v2 到底改进了什么?为什么 2.26 起默认启用?这篇用 GIT_TRACE_PACKET 对比 v0 和 v2:一次 clone 的交互、ls-refs 与 fetch 两个命令、有 5000 个 tag 的仓库里 fetch 一个分支的开销,以及什么也没变的 fetch 的开销。
$ git config --get protocol.version || echo "protocol.version 未设置(2.26 起默认 v2)" protocol.version 未设置(2.26 起默认 v2) packet: upload-pack> version 2 packet: upload-pack> agent=git/2.54.0-Darwin packet: upload-pack> ls-refs=unborn packet: upload-pack> fetch=shallow wait-for-done packet: upload-pack> server-option packet: upload-pack> object-format=sha1 packet: upload-pack> 0000 packet: clone< version 2 packet: clone< agent=git/2.54.0-Darwin packet: clone< ls-refs=unborn packet: clone< fetch=shallow wait-for-done packet: clone< server-option packet: clone< object-format=sha1 packet: clone< 0000 packet: clone> command=ls-refs packet: clone> agent=git/2.54.0-Darwin packet: upload-pack< command=ls-refs packet: clone> object-format=sha1 packet: upload-pack< agent=git/2.54.0-Darwin packet: clone> 0001 packet: clone> peel packet: upload-pack< object-format=sha1 packet: clone> symrefs packet: upload-pack< 0001 packet: clone> unborn packet: upload-pack< peel packet: clone> ref-prefix refs/heads/ packet: upload-pack< symrefs packet: clone> ref-prefix refs/tags/ packet: upload-pack< unborn packet: clone> ref-prefix HEAD packet: clone> 0000 packet: upload-pack< ref-prefix refs/heads/ packet: upload-pack< ref-prefix refs/tags/ packet: upload-pack< ref-prefix HEAD packet: upload-pack< 0000 packet: upload-pack> <sha> HEAD symref-target:refs/heads/main packet: upload-pack> <sha> refs/heads/main (共 69 行 packet 跟踪)
v0 服务端一连上就把所有 ref 倒出来,客户端要的也许只是一个分支。仓库有几千个 tag 或 PR 分支时,每次 fetch 都要先收这一大堆,哪怕什么都没变。
packet: upload-pack> ls-refs=unborn packet: upload-pack> 0000 packet: fetch< ls-refs=unborn packet: fetch< 0000 packet: fetch> command=ls-refs packet: upload-pack< command=ls-refs packet: fetch> agent=git/2.54.0-Darwin packet: fetch> object-format=sha1 packet: fetch> 0001 packet: fetch> peel packet: fetch> symrefs packet: fetch> unborn packet: fetch> ref-prefix main packet: upload-pack< 0001 packet: fetch> ref-prefix refs/main packet: fetch> ref-prefix refs/tags/main packet: fetch> ref-prefix refs/heads/main packet: fetch> ref-prefix refs/remotes/main packet: fetch> ref-prefix refs/remotes/main/HEAD packet: fetch> ref-prefix refs/tags/ packet: upload-pack< ref-prefix main packet: fetch> 0000 packet: upload-pack< ref-prefix refs/main packet: upload-pack< ref-prefix refs/tags/main packet: upload-pack< ref-prefix refs/heads/main packet: upload-pack< ref-prefix refs/remotes/main packet: upload-pack< ref-prefix refs/remotes/main/HEAD packet: upload-pack< ref-prefix refs/tags/ packet: upload-pack< 0000 packet: upload-pack> 0000 packet: fetch< 0000 packet: fetch> command=fetch packet: fetch> agent=git/2.54.0-Darwin packet: fetch> object-format=sha1 packet: fetch> 0001 packet: fetch> thin-pack packet: fetch> no-progress packet: fetch> ofs-delta packet: fetch> want <sha> packet: fetch> have <sha> packet: fetch> 0000 packet: upload-pack< command=fetch packet: upload-pack< 0001 packet: upload-pack< 0000 packet: upload-pack> 0001 packet: fetch< acknowledgments packet: fetch< ACK <sha> packet: fetch< ready packet: fetch< 0001 packet: fetch< packfile packet: upload-pack> 0000 packet: sideband< 0000
v2 先广告能力(没有 ref),客户端再发 command=ls-refs,并用 ref-prefix 说明只要哪些前缀(git fetch origin main 会展开成 main、refs/heads/main、refs/tags/main 等候选),服务端只返回这些。
v0:服务端发出(upload-pack>)5007 行,其中 ref 行 5002 行;客户端收到(fetch<)5006 行 v2:服务端发出(upload-pack>)15 行,其中 ref 行 1 行;客户端收到(fetch<)14 行
取对象由 command=fetch 完成:参数里是 want、have(和 v0 一样),服务端回 acknowledgments / ready,然后是 packfile。命令之间用 0001(delim-pkt)分隔参数,0000 结束。
v0:跟踪 10008 行;客户端发出的 want 0 条;v2 命令:ls-refs 0 次、fetch 0 次 v2:跟踪 48 行;客户端发出的 want 0 条;v2 命令:ls-refs 1 次、fetch 0 次
5000 个 tag 的远程上 fetch 一个分支:v0 服务端发出 5007 行(5002 个 ref),v2 只发 15 行(1 个 ref);无变化的 fetch 跟踪行数从 10008 降到 48。
version 2 agent=git/2.54.0-Darwin ls-refs=unborn fetch=shallow wait-for-done server-option object-format=sha1 0000 (flush)
v2 服务端的广告很短:version、agent、ls-refs=unborn、fetch=shallow wait-for-done、server-option、object-format=sha1,然后是 flush。