git clone 和 git fetch 时客户端与服务端到底说了什么?want 和 have 是什么意思?这篇用 GIT_TRACE_PACKET 把 v0 协议的交互全部打出来,再用 upload-pack --advertise-refs 解析原始的 pkt-line 字节。
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。
偏移 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 解析原始字节,长度之和正好等于总字节数。
(只列 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,协议在广告之后结束。