GitWave
一个跑在 Cloudflare 边缘网络上的 GitHub 式代码托管平台。
GitWave 把完整的 Git 服务搬到了 Cloudflare Workers 上:HTTP 服务由 Workers 提供,元数据放在 D1(SQLite),
Git 对象放在 R2 对象存储里。没有服务器、没有 SSH、没有 CI 配置——wrangler deploy 之后你就有了一个
可以 git clone / git push 的代码托管站点。
浏览器 ─┐
├─► Cloudflare Workers ─┬─► D1 (用户 / 仓库 / 议题 / 评论 / 星标)
git CLI ┘ └─► R2 (Git 对象:blob / tree / commit)
功能一览
| 类别 | 能力 |
|---|---|
| Git 传输 | 智能 HTTP 协议 v0,完整支持 clone / fetch / pull / push,含增量协商(ACK/NAK)、side-band-64k、ofs-delta / ref-delta 解包 |
| 仓库 | 公开 / 私有仓库、分支与标签、文件浏览、原始文件下载、提交历史、单提交详情与文件变更 |
| 协作 | 议题(issue)、拉取请求(pull request)、评论、关闭 / 重开、快进合并 |
| 社区 | 加星、关注、复刻(fork)、用户主页、活动流、仓库与用户搜索 |
| 账号 | 注册 / 登录 / 登出、会话 Cookie、HTTP Basic 认证(给 git 用)、Bearer 令牌(给 API 用) |
| API | /api/v1 下的 REST 接口:用户、仓库、分支、提交、文件内容、议题、评论、星标、事件流、统计 |
| 界面 | 服务端渲染,无需前端构建;响应式布局、浅色 / 深色主题、无外部字体与 CDN 依赖 |
快速开始
1. 环境要求
- Node.js 20+(开发时使用 22)
- 一个 Cloudflare 账号
npm install -g wrangler或直接使用项目里的 wrangler
2. 安装依赖
npm install
3. 创建 Cloudflare 资源
npx wrangler login # 登录 Cloudflare
npx wrangler d1 create gitwave
# 记下返回的 database_id,稍后填入 wrangler.toml
npx wrangler r2 bucket create gitwave-objects
把 wrangler.toml 里的 database_id 换成上一步拿到的真实 ID:
[[d1_databases]]
binding = "DB"
database_name = "gitwave"
database_id = "你的-database-id"
4. 本地开发
npm run db:migrate # 应用 D1 迁移(本地模拟数据库)
npm run dev # 启动 http://localhost:8787
本地开发时 wrangler dev 会自动使用 .wrangler/state/ 下的本地 D1 与 R2 模拟器,
不需要任何云端资源。
5. 部署上线
npm run db:migrate:remote # 把迁移应用到线上 D1
npm run deploy # 发布 Worker
或者直接用一键脚本,它会依次完成资源创建检查、迁移和部署:
./scripts/deploy.sh
部署完成后,把 gitwave.<你的子域>.workers.dev(或你绑定的自定义域名)分享出去即可。
使用方式
网页端
打开站点 → 注册账号 → 新建仓库 → 在仓库页复制 clone 地址。
命令行
# 克隆
git clone https://gitwave.example.com/alice/demo.git
# 推送(首次会提示输入用户名和密码,即你的 GitWave 账号)
git push origin main
推送时使用 HTTP Basic 认证,填 GitWave 的用户名和密码即可。
私有仓库在未认证时会返回 401 并带上 WWW-Authenticate: Basic 质询,git 客户端会自动带上凭据重试。
REST API
注册接口会直接返回一个令牌:
curl -X POST https://gitwave.example.com/api/v1/users \
-H 'Content-Type: application/json' \
-d '{"login":"alice","email":"alice@example.com","password":"supersecret123","name":"Alice"}'
之后用 Authorization: Bearer <令牌> 访问需要认证的接口:
curl -H "Authorization: Bearer $TOKEN" \
https://gitwave.example.com/api/v1/user/repos
常用端点:
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /api/v1/users | 注册(返回令牌) |
GET | /api/v1/users、/api/v1/users/:login | 用户列表 / 用户详情 |
GET | /api/v1/user、/api/v1/user/repos | 当前用户 / 其仓库 |
GET POST | /api/v1/repos、/api/v1/user/repos | 仓库列表 / 创建仓库 |
GET | /api/v1/repos/:owner/:repo | 仓库详情 |
GET | /api/v1/repos/:owner/:repo/branches | 分支列表 |
GET | /api/v1/repos/:owner/:repo/commits | 提交历史 |
GET | /api/v1/repos/:owner/:repo/contents/:path | 文件内容 |
GET POST | /api/v1/repos/:owner/:repo/issues | 议题列表 / 创建(?kind=issue|pull|all、?state=open|closed|all) |
POST | /api/v1/repos/:owner/:repo/issues/:number/comments | 添加评论 |
GET | /api/v1/repos/:owner/:repo/stargazers | 星标用户 |
GET | /api/v1/events、/api/v1/stats | 活动流 / 站点统计 |
GET | /api/v1/health | 健康检查:真实读写一次 D1 与 R2,并自检 Git 引擎 |
GET | /api/v1/selftest?key=<密钥> | 端到端自检:在 Worker 内部跑一遍注册 → push → clone |
项目结构
gitwave/
├── migrations/
│ └── 0001_gitwave_init.sql # D1 表结构
├── public/
│ ├── style.css # 全站样式(浅色 / 深色主题、响应式)
│ └── guide.html # 由 scripts/build-docs.mjs 从本文件生成的手册
├── scripts/
│ ├── build-docs.mjs # README.md → public/guide.html + README-BOM.md
│ ├── deploy.sh # 一键部署脚本
│ └── smoke-test.sh # 端到端冒烟测试脚本
├── src/
│ ├── index.tsx # 应用入口:挂载 API、Git 传输、网页
│ ├── types.ts # 前后端共享的数据结构
│ ├── auth/ # 密码哈希、会话、请求认证
│ ├── db/ # D1 数据访问层
│ ├── git/ # Git 引擎(见下)
│ ├── lib/html.ts # JSX → HTML 渲染
│ ├── routes/ # web.tsx(网页)/ api.ts(REST)/ git.ts(Git 传输)
│ ├── selftest.ts # 生产环境端到端自检(注册 → push → clone)
│ ├── services/ # 仓库服务(分支、提交、文件、README)
│ ├── util/encoding.ts # 字符集判定、base64 等编码工具
│ └── views/ # 服务端渲染的页面组件
└── test/ # Node 原生测试
Git 引擎
src/git/ 下是一套自己实现的 Git 引擎,没有依赖任何第三方 git 库:
| 模块 | 职责 |
|---|---|
sha1.ts | 纯 TypeScript 实现的 SHA-1 |
inflate.ts | 手写 DEFLATE 解码器,精确跟踪消费的字节数(这是顺序解析 packfile 的前提,Web DecompressionStream 做不到) |
zlib.ts | 基于 CompressionStream 的 zlib 压缩,以及基于上面解码器的解压 |
objects.ts | blob / tree / commit 的编码、解析与哈希 |
tree.ts | 树遍历、路径解析、目录列表 |
pack.ts | pack v2 的生成与解析,支持 ofs-delta / ref-delta |
revwalk.ts | 可达对象遍历、提交历史、树差异 |
protocol.ts | pkt-line 组帧、ref 广播、upload-pack / receive-pack 协议 |
store.ts | 对象存储抽象(R2 / 内存 / 带缓存) |
Git 对象按内容寻址存放在 R2 的 objects/ 前缀下,因此复刻仓库共享同一份对象,
fork 出来的仓库几乎不占额外空间。
字符集与「乱码」
中文乱码绝大多数不是数据坏了,而是接收端猜错了字符集——文本响应没写charset 时,浏览器和各类文本查看器在中文环境里会按 GBK 去解 UTF-8 的字节。
GitWave 里统一按下面几条规则处理:
| 场景 | 处理方式 |
|---|---|
| HTML 页面 | 响应头 text/html; charset=UTF-8,页面内再写一遍 <meta charset="utf-8"> |
| REST API | 响应头 application/json; charset=utf-8 |
| 仓库里的原始文件 | 文本返回 text/plain; charset=utf-8,二进制返回 application/octet-stream + nosniff |
| 文件预览 | 只有能以 fatal 模式完整解码成 UTF-8 的内容才按文本展示,其余按二进制处理(避免出现 U+FFFD 替换字符) |
| 纯文本 / Markdown 文件 | 额外提供 README-BOM.md(带 UTF-8 BOM),让记事本等编辑器明确按 UTF-8 解码 |
src/util/encoding.ts 集中实现文本 / 二进制判定与 base64 编码。
生成在线手册
public/guide.html 是由本文件自动生成的手册页(站点上的 /guide),带目录、
深浅色主题和移动端适配:
npm run build:docs
改完 README 后重新执行一次即可,生成物同时会写出带 BOM 的 README-BOM.md。
测试
npm test # 61 个用例:Git 引擎、协议、视图层、编码
npm run typecheck
测试覆盖了真实 git 的行为:
- 手写 SHA-1 与
git hash-object结果一致 - 生成的 pack 能被真实的
git unpack-objects接受 - 真实的
git gc产出的 pack(含 ofs-delta / ref-delta)能被正确解析,哈希与git cat-file一致 - pkt-line 组帧、ref 广播、upload-pack 的能力协商
- 页面上不会出现
undefined/[object Object],Markdown 会转义并拦截脚本 - 文本 / 二进制判定、GBK 字节序列识别、base64 编码大文件
端到端冒烟测试
对任何已经跑起来的实例(本地或线上)都可以跑一遍完整的端到端验证:
./scripts/smoke-test.sh # 默认测本地
BASE_URL=https://gitwave.example.com ./scripts/smoke-test.sh
它会依次验证:注册 → 建仓库 → 克隆空仓库 → 提交推送 → 二次克隆并 git fsck →
增量推送与拉取 → 推送分支与标签 → 创建议题 → 创建拉取请求并合并。
安全说明
- 密码使用 PBKDF2-HMAC-SHA256(100000 次迭代)加盐哈希存储
—— 迭代次数取的是 Cloudflare Workers WebCrypto 的上限:超过 100000 会直接抛
NotSupportedError,而本地 miniflare 不做限制,这个坑只有部署到线上才会暴露
- 会话令牌保存在 D1,浏览器侧为 httpOnly + SameSite=Lax 的 Cookie
- 私有仓库的读、写都需要认证;未认证访问返回
401质询 - README 等用户内容在渲染前经过转义与白名单过滤,
javascript:链接与<script>会被拦截 - SQL 全部使用参数绑定,没有字符串拼接
生产环境自检
线上部署之后,有两个不依赖外部网络的检查入口:
# 健康检查:真实读写一次 D1 与 R2,并自检 Git 引擎的哈希
curl https://<你的域名>/api/v1/health
# => {"status":"ok","checks":{"d1":{"ok":true,...},"r2":{"ok":true,...},"git":{"ok":true,...}}}
# 端到端自检:在 Worker 内部跑一遍「注册 → 建仓库 → push → clone」
npx wrangler secret put SELFTEST_KEY # 先设置密钥
curl "https://<你的域名>/api/v1/selftest?key=<你设置的值>"
端到端自检走的是真实的路由处理器、真实的 D1 与真实的 R2,结束后会清理自己创建的
账号、仓库与 Git 对象。未设置 SELFTEST_KEY 时该端点直接返回 404,等于关闭。
这两个端点存在的意义:本地 miniflare 和线上 Workers 的运行时限制并不完全一致
(比如 PBKDF2 的迭代次数上限),只靠本地测试发现不了。
已知限制
- 合并只支持快进(fast-forward)合并,不做三方合并,也不会自动生成合并提交
- 不支持 Git LFS、子模块、
git push --force的保护(force push 会直接覆盖) - 协议只实现了 v0,没有实现 protocol v2
- 大仓库的首次克隆会一次性生成完整 pack,没有做增量打包优化
- 没有 CI/CD、Release、Wiki、代码评审的行内评论
- Webhook 尚未实现
许可证
MIT