bigywater.tech
Git · 61

upload-pack 的交互:用 GIT_TRACE_PACKET 看 want/have

git clone 和 git fetch 时客户端与服务端到底说了什么?want 和 have 是什么意思?这篇用 GIT_TRACE_PACKET 把 v0 协议的交互全部打出来,再用 upload-pack --advertise-refs 解析原始的 pkt-line 字节。

4 位十六进制
pkt-line 的长度前缀,含自己这 4 个字符;0000 是 flush-pkt
want / have / done
客户端“要什么、已有什么、说完了”的三个动词
399 字节
三个 ref 的 v0 广告(274 + 60 + 61 + 4)总字节数

一次 clone 的交互

实测 · 本机执行输出
  packet: upload-pack> <sha> HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress inc
  packet: upload-pack> <sha> refs/heads/dev
  packet: upload-pack> <sha> refs/heads/main
  packet: upload-pack> 0000
  packet: clone< <sha> HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-t
  packet: clone< <sha> refs/heads/dev
  packet: clone< <sha> refs/heads/main
  packet: clone< 0000
  packet: clone> want <sha> multi_ack_detailed side-band-64k thin-pack no-progress ofs-delta deepen-since deepen-not agent=git/2.54.0-Darwin
  packet: clone> want <sha>
  packet: clone> want <sha>
  packet: clone> 0000
  packet: clone> done
  packet: upload-pack< want <sha> multi_ack_detailed side-band-64k thin-pack no-progress ofs-delta deepen-since deepen-not agent=git/2.54.0-Darwin
  packet: upload-pack< want <sha>
  packet: upload-pack< want <sha>
  packet: upload-pack< 0000
  packet: upload-pack< done
  packet: upload-pack> NAK
  packet: clone< NAK
  packet: sideband< PACK ...
  packet: upload-pack> 0000
  packet: sideband< 0000
  (共 23 行 packet 跟踪)

服务端先开口:每个 ref 一个 pkt-line(<sha> 名字),第一个 ref 后面用 \0 接上 capabilities,最后一个 flush-pkt 0000。客户端用 want <sha> 列出要的提交(第一条带上它选择的 capabilities),0000 结束,done 表示没有更多要说的。新克隆没有共同点,服务端回 NAK,然后在 side-band 里发 pack。

pkt-line

实测 · 本机执行输出
  偏移 0: 长度 0112(274)→ '<sha> HEAD\\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress incl'
  偏移 274: 长度 003c(60)→ '<sha> refs/heads/dev\n'
  偏移 334: 长度 003d(61)→ '<sha> refs/heads/main\n'
  偏移 395: 0000 → flush-pkt
  总共 3 个非空 pkt-line(含 HEAD 一行带 capabilities,其余每个 ref 一行),总字节数 399

每个包前面是 4 位十六进制的长度(包括这 4 个字符本身),0000 是没有内容的特殊包 flush-pkt,表示一段结束。直接调用 upload-pack --advertise-refs 解析原始字节,长度之和正好等于总字节数。

have

实测 · 本机执行输出
  (只列 want / have / ACK / NAK / done 相关行)
  packet: fetch> want <sha> multi_ack_detailed side-band-64k thin-pack no-progress include-tag ofs-delta deepen-since deepen-not agent=git/2.54.0-Darwin
  packet: fetch> 0000
  packet: fetch> have <sha>
  packet: fetch> done
  packet: fetch< ACK <sha> common
  packet: fetch< ACK <sha>
  fetch 之前对象总数 6,之后 10:新增 4 个对象(1 个 commit、1 个 tree、2 个 blob)

增量 fetch 时客户端用 have 告诉服务端自己已有的提交,服务端回 ACK … common,找到共同点后只打包缺失的对象(本例新增 4 个)。

无需传输

实测 · 本机执行输出
  跟踪共 10 行;其中 want 行 0 条,have 行 0 条,done 0 条
  packet: upload-pack> <sha> HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-rela
  packet: upload-pack> <sha> refs/heads/dev
  packet: upload-pack> <sha> refs/heads/main
  packet: upload-pack> 0000
  packet: fetch< <sha> HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative n
  packet: fetch< <sha> refs/heads/dev

如果所有 ref 本地都已是最新,客户端不发 want,协议在广告之后结束。

分步演示

upload-pack 的交互
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。用 -c protocol.version=0 强制 v0 协议(本机 git 默认 v2,见下一篇);服务端是本机裸仓库(file://);跟踪输出里的 hash 做了替换;pkt-line 解析是我写的 Python,对账对象是总字节数
    • 没有验证:multi_ack_detailed 的 ready 流程在大量 have 时的多轮协商(见 fetch 协商一篇);shallow/deepen;side-band 内部的包格式(1/2/3 字节标识);smart HTTP 上同样的交互(stateless-rpc);服务端 hook(pre-receive 等)在 push 中的位置
    京ICP备2026067502号-1京公网安备11010502063487号