bigywater.tech
Git · 65

fetch 的协商:怎么只下载缺的对象

git fetch 怎么知道只需要下载哪些对象?客户端的 have 要发多少个?本地分支多的时候协商会不会很慢?这篇在 1000 个提交的历史上实测 consecutive、skipping、noop 三种协商算法,以及 thin pack 如何让小改动几乎零传输。

1008 vs 16
客户端有 500 个本地独有提交时,默认(consecutive)与 skipping 算法发出的 have 数
350 KB
noop 算法不协商,对象库多增加的大小(其它算法只增加 15 KB)
308 vs 136,125
213 KB 文件改一行后,thin pack 与非 thin pack 的字节数

have 和 want

实测 · 本机执行输出
  客户端发出的 have: 16 个;fetch 命令 1 次;want 1 个
  $ git fetch -q 2>&1; git rev-list --count origin/main..origin/main; git log --oneline origin/main | wc -l | sed "s/^ */origin\/main 现在的提交数: /"
  0
  origin/main 现在的提交数: 1050

客户端用 want 说想要什么,用 have 说已经有什么;服务端回 ACK … common,找到共同点就只打包缺的对象。纯落后的客户端第一批发 16 个 have,一轮就结束。

三种算法

实测 · 本机执行输出
  fetch.negotiationAlgorithm=(未设置)         客户端发出 have 1008 个,fetch 命令 6 轮;对象库增加 15 KB
  fetch.negotiationAlgorithm=consecutive   客户端发出 have 1008 个,fetch 命令 6 轮;对象库增加 15 KB
  fetch.negotiationAlgorithm=skipping      客户端发出 have   16 个,fetch 命令 1 轮;对象库增加 15 KB
  fetch.negotiationAlgorithm=noop          客户端发出 have    0 个,fetch 命令 1 轮;对象库增加 350 KB
  (consecutive:沿着本地历史一个个往前发 have;skipping:跳着发,间隔越来越大,更快找到共同点;noop:一个 have 都不发——只适合 partial clone 之类的场合,会下载更多)

fetch.negotiationAlgorithm:默认(consecutive)沿着本地历史一个一个往前发 have,本地独有的提交越多越慢(500 个独有提交时发出 1008 个 have、6 轮);skipping 跳跃式发送,16 个 have、1 轮就找到共同点;noop 完全不发 have,适合 partial clone 等不需要协商的场景,代价是服务端不知道你有什么,会多发很多(本例多下载 350 KB)。

--negotiation-tip

实测 · 本机执行输出
  git fetch --negotiation-tip=origin/main:客户端发出 have 16 个,fetch 命令 1 轮

限制协商只从指定的提交出发,本地的其它分支(哪怕有几百个独有提交)不参与,协商变成 16 个 have、1 轮。

thin pack

实测 · 本机执行输出
  服务端打包的摘要: remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
  模拟服务端要发的 pack(只含 v2 这一个提交的新对象):--thin(可以引用客户端已有的 v1 blob 做 delta)308 字节;不用 thin(对象必须自包含)136,125 字节;整个文件压缩后约 135,860 字节

服务端发的 pack 可以是 thin 的——引用客户端已有的对象做 delta。对 213 KB 文件改一行,thin pack 只有 308 字节,自包含的 pack 要 136,125 字节。客户端收到后会把 thin pack 补全。

分步演示

fetch 的协商
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。服务端是本机裸仓库(file://);协商用 GIT_TRACE_PACKET 数 have 与 command=fetch 的行数;本地独有的 500 个提交的时间比服务端任何提交都新(协商按时间从新到旧走,这样才会暴露 consecutive 的代价);thin pack 的字节数是手动调用 git pack-objects --thin 得到的模拟值,不是线上抓的包
    • 没有验证:真实网络上多轮协商的往返延迟;fetch.negotiationRestrict 与 negotiation 的其它调节项;push 时的协商(receive-pack 的 have 来自服务端);服务端 hook;分支数以万计时的 skipping 表现
    京ICP备2026067502号-1京公网安备11010502063487号