Cloudflare 用 Go + FUSE 做了一个按需加载 Git 仓库的文件系统

最近看到 Cloudflare 开源了一个挺有意思的项目:ArtifactFS。

它解决的问题很直接:大仓库太慢了。

比如 Chrome 源码几十 GB,Linux 内核也有好几个 GB。正常 git clone 的时候,不管你一开始到底只想看一个 README,还是只想查一个配置文件,Git 都要把 commits、trees、blobs 一起拉下来。人等十几分钟还可以去倒杯水,但是 AI Agent、CI/CD 容器、预览环境就比较惨了,它们启动后应该马上干活,而不是先坐在那里等 clone。

ArtifactFS 的思路是:仓库结构先给你,文件内容用到再下载。

它到底做了什么

ArtifactFS 是 Cloudflare 用 Go 写的一个 FUSE 文件系统。

简单理解就是,它把远程 Git 仓库挂载成本地目录。你在系统里看到的是一个普通目录,可以 lscat,也可以跑一些 Git 命令。但它背后不是传统的完整 clone,而是先做一个 blobless clone,再通过 FUSE 按需加载文件内容。

传统流程大概是:

git clone -> 下载 commits + trees + blobs -> 仓库可用

ArtifactFS 的流程更像这样:

git clone --filter=blob:none -> 先拿 commits + trees -> 目录马上可见 -> 读文件时再下载 blob

这里的关键是:

git clone --filter=blob:none

这是 Git 的部分克隆能力。它会下载提交历史、目录树和引用信息,但是不下载真正的文件内容,也就是 blob。这样一个很大的仓库,初始下载可能就从几十 GB 变成几百 MB,甚至更小。

ArtifactFS 再在这个基础上加了一层文件系统。你访问某个文件时,FUSE 拦截读取请求,然后去远程 Git 服务器把对应的 blob 拉下来,缓存到本地,再返回给操作系统。

对使用者来说,它像一个正常目录。

对底层来说,它是“先挂载,后补内容”。

快速试一下

macOS 需要 macFUSE,Linux 需要 fuse3。

# macOS
brew install --cask macfuse

# Ubuntu / Debian
apt install fuse3

# 安装 ArtifactFS
go install github.com/cloudflare/artifact-fs/cmd/artifact-fs@latest

挂载一个仓库:

export ARTIFACT_FS_ROOT=/tmp/artifact-fs-test

artifact-fs add-repo \
  --name workers-sdk \
  --remote https://github.com/cloudflare/workers-sdk.git \
  --branch main \
  --mount-root /tmp

artifact-fs daemon --root /tmp &
DAEMON_PID=$!

然后就可以像普通目录一样用了:

ls /tmp/workers-sdk/
cat /tmp/workers-sdk/README.md
git -C /tmp/workers-sdk log --oneline -5

清理的时候:

kill $DAEMON_PID

这个体验比较有意思。它不是让下载变魔法消失,而是把“全部下载完才能开始”变成了“先开始,用到哪里再下载哪里”。

它的两个阶段

ArtifactFS 大概可以分成两个阶段。

第一阶段:准备文件树

add-repo 的时候,它会做几件事:

git clone --filter=blob:none
git ls-tree -r -t -z HEAD
git cat-file --batch-check
SQLite bulk insert

这一步会拿到完整的目录结构、文件名、大小、权限等元数据,然后写入 SQLite。

注意,这时候文件内容还没下载。

也就是说,系统已经知道“这里有一个 README.md”“那里有一个 src/main.go”,但它还没真正把 README 或 main.go 的内容拉下来。

第二阶段:守护进程接管读写

artifact-fs daemon 启动后,FUSE 层开始工作。

读文件时,大概是这个流程:

用户读取 README.md
-> FUSE 拦截 read
-> 先检查本地 overlay 有没有修改
-> 再检查 blob cache 有没有缓存
-> 如果没有缓存,就通过 GitStore 拉取 blob
-> 缓存后返回内容

写文件时,它不会直接改底层 Git 仓库,而是写到 Overlay 层。

这个设计有点像 Docker 镜像的 copy-on-write:下面是一层只读快照,上面是一层可写层。你看到的是合并后的文件系统视图。

为什么 AI Agent 会需要它

这个项目最吸引我的地方,不是“它能省磁盘”,而是它刚好踩中了 AI Agent 的工作方式。

Agent 处理一个仓库时,通常不是一上来就读完整个项目。它可能先看 README、package.json、go.mod、入口文件、几个相关模块,然后再慢慢扩大范围。

如果每次启动沙箱都要完整 clone 一个大仓库,就会浪费很多时间。

传统方式:

启动容器 -> git clone 等几分钟 -> Agent 开始分析

ArtifactFS 方式:

启动容器 -> 秒级挂载仓库 -> Agent 立刻读取关键文件 -> 后台继续按需加载

这对 Claude Code、Codex、Cursor 这类工具都挺有价值。尤其是临时沙箱、远程预览环境、CI 任务,它们的生命周期本来就短,启动时间越长越亏。

它还做了优先级预取

ArtifactFS 不是完全被动地等你读文件。

它有一个 Hydrator,会按优先级预取文件:

这个策略很实用。

因为对 Agent 或 IDE 来说,最早要看的大概率是代码和配置,而不是一堆截图、视频、构建产物。先把最有用的文件拉下来,体验会好很多。

并发也可以调:

artifact-fs daemon --root /tmp --hydration-concurrency 8

并发越高,预取越快,但内存和 Git 进程开销也会更高。

Go 实现里比较值得看的点

从 Go 项目的角度看,ArtifactFS 也很适合拿来学习系统编程。

FUSE 文件系统

它使用 github.com/jacobsa/fuse 来实现用户态文件系统。

FUSE 的好处是不用写内核模块,就可以拦截 openreadwritestatreaddir 这些文件系统操作。这样 Go 程序就能在用户态里决定一个文件到底从哪里来。

SQLite 不依赖 CGO

它使用的是 modernc.org/sqlite,这是纯 Go SQLite 驱动。

这个选择对工具类项目挺友好:

SQLite 在这里主要存两类数据:

Git 进程池

读取 blob 时,它不是每次都启动一个新的 Git 命令,而是维护一组持久的 git cat-file --batch 进程。

大概意思是:

发送 object id -> Git 返回对象内容 -> 继续等下一个 object id

这样能减少反复创建进程的开销,对大量小文件读取会友好很多。

依赖很克制

它直接依赖不多,核心就是:

github.com/jacobsa/fuse
github.com/urfave/cli
modernc.org/sqlite

其他很多事情靠 Git 命令和 Go 标准库完成。这种工具我个人会更喜欢一点,依赖少,边界比较清楚。

和普通 git clone 的区别

如果只是一个小仓库,ArtifactFS 可能没什么必要。直接 clone 最简单。

但如果是大仓库,区别就出来了:

方式 初始等待 文件内容 适合场景
git clone 全量下载 小仓库、长期开发目录
git clone --filter=blob:none 快一些 Git 按需取 blob 想用 Git 部分克隆,但不需要文件系统挂载
ArtifactFS 秒级挂载 FUSE 按需加载 + 缓存 Agent 沙箱、CI、预览环境、大仓库浏览

它不是替代所有 clone 场景,而是适合“我要马上看到仓库,并且只读一部分内容”的场景。

需要注意的地方

这个项目还比较年轻,不能一上来就当生产核心依赖。

几个注意点:

如果是个人学习、Agent 沙箱实验、CI 加速探索,我觉得很值得试。

最后

ArtifactFS 最有价值的地方,是把 Git 大仓库的等待时间拆开了。

以前是“全部 clone 完,才能开始”。

现在变成“目录先可用,内容用到再拉”。

这个思路对 AI Agent 特别合适,因为 Agent 很多时候只需要先读几个关键文件就能开始判断。对人来说,少等几分钟是体验优化;对自动化系统来说,少等几分钟可能就是成本和效率。

如果你也在折腾 Go、CI/CD、AI Agent 沙箱,或者经常被大仓库 clone 折磨,可以看看这个项目。

项目地址:github.com/cloudflare/artifact-fs

参考来源:微信公众号文章