3 天 1531 star:walgit 把 Git 服务器变成一个二进制,仓库直接扔进 S3

你第一次自建 Git 托管时,会踩同一个坑:GitLab 装完一个虚拟机,内存先吃 4G;Gitea 轻一点,但身后照样拖着 SQLite + 一个要盯的进程;真到了上百人的团队,你开始聊主从、聊备份、聊"那个节点挂了怎么办"。

全世界的自建 Git 都是这么长大的——直到有个东西说:为什么服务器要有状态?

walgit,三天 1531 star。它把整个 Git 服务器压缩成一个 Rust 二进制,后端只挂一个 S3/GCS 桶。没有数据库、没有主节点、没有任何"丢不起"的本地状态。桶就是仓库,磁盘只是缓存,每一台跑它的机器都可以随手扔掉。

一句话看懂它在反什么

所有自建 Git 都默认一个前提:仓库必须住在服务器上。为了这份"住",你得配数据库、做主从、管备份、应付扩容。

walgit 把前提反过来:仓库住在对象存储里,服务器只是个"读档员"。你要做的就三件事——起一个二进制,指向一个桶,完事。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# walgit.toml
[server]
listen = "0.0.0.0:8080"
public_url = "https://git.example.com"
auto_create_on_push = true

[server.auth]
mode = "token"
tokens = [{ principal = "me", token_env = "WALGIT_TOKEN_ME", write = true }]

[store]
backend = "s3"
bucket = "my-walgit"
[store.s3]
endpoint = "https://s3.us-east-1.amazonaws.com"
region = "us-east-1"

跑起来,然后 push 一个新名字——仓库自动创建。就这样。

它凭什么敢说"没有状态"?核心是 WAL + CAS

这才是值得掰开看的地方。你可能会想:把 git 仓库放对象存储,这不就是"把仓库扔网盘上"吗?那早有人试过,全死了——因为 git 的 packfile 是几 GB 的随机读,走网络文件系统(NFS)延迟直接爆炸,GitHub 当年就是被这个教训逼得上了 Spokes(本地 NVMe + 三阶段复制 + 一堆"宠物服务器")。

walgit 抄的是 Cursor 那篇 Git at any scale(内部叫 Continuity)的架构,但改成了能在比仓库还小的机器上跑。核心就两点:

1. 写入提前日志(WAL)进对象存储,而且不可变。

一次 push 不是直接改仓库文件,而是写成一个不可变对象扔进桶里。只有当一份极小的 manifest 被用 compare-and-swap(CAS)重写后,这次 push 才"可见"。

2. 这个 CAS 就是共识,没有选举、没有 quorum。

任何一台实例都能接收 push,但两个同时 push 的实例不可能都赢——manifest 的 CAS 保证只有一个成功。这比传统的主从简单太多:不需要选主,不需要同步协议,对象存储自带的"条件写"就是所有协调。

读的时候,每台实例先做一次"条件 GET"问桶"有没有变化"(通常返回 304),有变化才拉,没有就用本地缓存。所以读一致性不需要任何协调——每个读都先问一下源头。这就是它说的"没有 eventually,没有最终一致"。

一个反常识的点:仓库可以比机器大

传统 git 托管里,服务器必须能装下仓库。walgit 打破了这条:它做了个 remote reader——refs 和 commit/tree 留在本地,blob 留在桶里,用 HTTP range 请求按需拉。再加 history pack(小机器的 commit 历史打包)+ bundle-uri(把克隆流量整个甩给 CDN 当静态文件发)。

结果是:一台 2G 内存的小机器,可以托管一个本身有 50G pack 的 monorepo。克隆时新用户直接从 CDN 拿 bundle,服务器只补差额。这是传统方案给不了的能力——过去你要么上大机器,要么拆仓库。

和主流方案横向对比

方案 后端状态 数据库 主从 多实例 仓库 > 机器
GitLab 全在服务器 PostgreSQL + Redis 麻烦 不可能
Gitea 服务器 + SQLite/MySQL 麻烦 不可能
GitHub Enterprise 本地 NVMe + 三阶段复制 要(Spokes) 复杂 不可能
walgit 对象存储 加一台机器就行 可以

你发现区别没:别人用数据库管"仓库在哪台机器上",walgit 干脆让仓库不存在于任何一台机器上。“元数据在哪"这个问题,被它直接删掉了。

上手(真实可跑)

1
2
3
4
5
6
# 1. 起一个 MinIO(本地模拟 S3),或直接用你的云厂商桶
# 2. 写上面的 walgit.toml
# 3. 跑
walgit serve --config walgit.toml
# 4. push 一个名字,仓库自动创建
git push https://git.example.com/me/myrepo main

它还带了个一键装机脚本——install.sh 会在你开发者机器上装好 token 和 git 凭证助手,一条命令,幂等。

丑话说前头

这套设计不是没有代价,得诚实说:

  • 对象存储是单点:桶挂了全挂。它把可靠性的锅全部甩给了你的存储厂商——AWS/GCS 靠得住,但 MinIO 自托管那部分责任又回到你身上。
  • 延迟敏感:每个读都要先"条件 GET"问桶,网络不好时第一个字节会慢。它自己也专门写了 ROUNDTRIPS 文档讲怎么减往返。
  • 很新:仓库刚 3 天,193 到 1531 star 只用了 72 小时,但只有 1 个 commit 起步、还是 tobi 一个人在做。上生产要谨慎,PR 欢迎。
  • 生态为零:没有 CI 集成、没有 MR/PR 审批流(只有基础的 push policy/webhook)——它定位是"Git 托管”,不是"GitHub 全家桶"。

值得关注的点

walgit 最值得抄的不是 git 本身,而是**“把状态从服务端挪到对象存储 + 用 CAS 当共识”**这个思路。S3 的条件写(conditional put)21 个月前才发布,之后整个行业在往"解耦存储"狂奔——PicoMQ、Celld、SlateDB 都是这个方向。

而 git 托管,可能是被这个范式改造得最彻底的一个:当你的"服务器"只剩一层可丢弃的缓存,扩容、容灾、备份这些问题,一夜之间全都消失了。

Git 本来就是分布式的。walgit 只是把这份分布式,贯彻到了托管它的服务器上。


搬砖程序员带你飞,专注 Golang / AI / 后端。每天一篇,讲清楚一个技术真相。

0%