bigywater.tech
Git · 64

部分克隆 --filter=blob:none

部分克隆(--filter=blob:none)让你先拿到完整历史的结构、需要某个文件内容时再按需下载。这篇看它省了多少、留下什么记号、缺失的对象是怎么被悄悄取回的,以及哪些操作会触发大量逐个 fetch。

67 vs 5932 KB
blob:none 克隆与完整克隆的对象库大小(1.1%)
1 次 vs 260 次
checkout 批量取 blob 的 fetch 次数,与 git log -p 逐个取的次数
.promisor
pack 目录里标记承诺 pack 的文件,与 remote.origin.promisor 配置一起使用

过滤

实测 · 本机执行输出
  $ git count-objects -vH | grep -E "^(in-pack|size-pack)"; git config --get uploadpack.allowFilter
  in-pack: 1200
  size-pack: 5.83 MiB
  true

--filter=blob:none 不下载任何 blob,blob:limit=n 只下载小于 n 的,tree:0 连 tree 也不要。服务端需要 uploadpack.allowFilter=true。

承诺者

实测 · 本机执行输出
  克隆方式                           本地对象数       对象库大小       耗时
  完整                             1,200     5932 KB     89ms
  --filter=blob:none               900       67 KB     44ms
  --filter=blob:limit=1k           900       67 KB     44ms
  --filter=tree:0                  300       37 KB     48ms

克隆时 git 把远程标成 promisor(remote.origin.promisor=true,记录过滤规则),拿到的 pack 带 .promisor 文件。对象库里缺失的对象因此“有来处”:需要时可以向它要。git rev-list --objects --missing=print 用 ? 列出缺失的对象。

按需下载

实测 · 本机执行输出
  $ git config --get-regexp "promisor|partialclone|extensions"; ls .git/objects/pack | sed -E "s/pack-[0-9a-f]{40}/pack-<hash>/"
  remote.origin.promisor true
  remote.origin.partialclonefilter blob:none
  pack-<hash>.idx
  pack-<hash>.pack
  pack-<hash>.promisor
  pack-<hash>.rev
  $ git rev-list --objects --missing=print HEAD | grep -c "^?"; git rev-list --objects HEAD --missing=allow-any | grep -vc "^?" | sed "s/^ */非缺失对象(commit + tree)的个数: /"
  300
  非缺失对象(commit + tree)的个数: 900

任何需要缺失 blob 的操作(cat-file、checkout、diff、log -p)都会在后台启动 git fetch --filter=blob:none --stdin 把需要的对象取回。用户感觉不到,只是第一次访问慢一点。

批量与逐个

实测 · 本机执行输出
  访问一个缺失的 blob(git cat-file -s)时的子进程:
    git.c:502               trace: built-in: git cat-file -s <sha>
    run-command.c:672       trace: run_command: git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags --no-write-fetch-head --recurse-submodules=n
    git.c:502               trace: built-in: git fetch origin --no-tags --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin
    run-command.c:672       trace: run_command: unset GIT_CONFIG_PARAMETERS GIT_PREFIX; GIT_PROTOCOL=version=2 'git-upload-pack '\''/work/l60src'\'''
    git.c:502               trace: built-in: git upload-pack /work/l60src
  对象数 900 → 901(多了 1 个 blob,下载自 origin)

checkout 一次知道需要哪些 blob,批量 fetch(本例 39 个);git log -p 沿着历史一个个访问,每个缺失的 blob 触发一次 fetch(本例 260 次)。批量扫描历史的命令在 partial clone 里可能比完整克隆慢得多。

什么时候用

实测 · 本机执行输出
  $ git checkout -q main
  checkout 之后对象数 940(HEAD 的 40 个文件的 blob 一次性下载)
  git log -p(历史上全部 300 个 blob 的补丁):触发 260 次后台 fetch;完成后对象数 1200
  现在还缺失的对象数: 0

仓库里有很多大文件、历史很长,而你只需要最新版本和结构时,partial clone 能把克隆缩小到约 1%。如果需要全部历史内容,还是完整克隆更省事。

分步演示

部分克隆
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。源仓库是脚本生成的 300 个提交(40 个 20 KB 的随机二进制文件轮流修改);服务端是本机的 file:// 路径,配置 uploadpack.allowFilter 与 allowAnySHA1InWant;对象数 = count + in-pack;“260 次后台 fetch”来自 GIT_TRACE 里 run_command: git … fetch 的行数
    • 没有验证:真实的 HTTP 服务器(GitHub 等)上 partial clone 的行为与限制;--filter=sparse:oid;partial clone 与 sparse-checkout 结合(见 sparse-checkout 一篇);fetch.negotiationAlgorithm=noop 的含义;部分克隆之后 git gc 对缺失对象的处理;promisor 远程不可达时的报错
    京ICP备2026067502号-1京公网安备11010502063487号