A fast, searchable, self-hosted Git remote built with Durable Objects
See the codeA self-hostable git remote backed by Cloudflare Durable Objects. One DO per repo, SQLite storage, FTS5 search, delta compression via xpatch. Built in Rust with workers-rs.
Live example: git.theagents.company — deathbyknowledge's repos including agents (~1k commits) and curl (~40k commits)
git remote add origin https://your-worker.workers.dev/username/myproject
git push origin main
git push, git clone, git fetch with any git clientexamples/github-oauth/text/markdown and text/plain, with explicit actions and curl-friendly paths@author:, @message:, @path:, @ext:, @content: query prefixes/:owner/:repo/raw/:ref/*path/:owner/ owner profile — lists your repos
/:owner/:repo/ repo home — file tree, README, recent commits
/:owner/:repo/commits commit history
/:owner/:repo/commit/:sha commit detail with diff
/:owner/:repo/tree/:ref/* directory browser
/:owner/:repo/blob/:ref/* file viewer
/:owner/:repo/raw/:ref/* raw file bytes
/:owner/:repo/search-ui full-text search
/:owner/:repo/settings stats, index rebuilds, config, delete (owner only)
All endpoints under /:owner/:repo/.
| Endpoint | Description |
|---|---|
GET /refs | List branches and tags |
GET /log?ref=main&limit=50 | Commit history |
GET /commit/:hash | Single commit |
GET /tree/:hash | Directory listing |
GET /blob/:hash | File content |
GET /file?ref=main&path=src/lib.rs | File at ref + path |
GET /search?q=TODO | Code search |
GET /search?q=fix&scope=commits | Commit message search |
GET /diff/:sha | Commit diff |
GET /compare/base...head | Two-commit comparison |
GET /stats | Compression and storage stats |
Browsable pages support negotiated text mode for curl, scripts, and agents.
curl -H 'Accept: text/markdown' https://your-worker.workers.dev/alice/repo/
curl -H 'Accept: text/plain' https://your-worker.workers.dev/alice/repo/commits
curl 'https://your-worker.workers.dev/alice/repo/tree/main/src?format=md'
Accept: text/markdown returns a markdown viewAccept: text/plain returns the same content as plain text?format=md or ?format=text works when you can't keep headers attached while following linksFiles (GET paths) and spell out POST actions, fields, and requirements in an Actions sectionripgit reads identity from trusted X-Ripgit-Actor-* headers, which are only settable by an upstream auth worker via Service Binding (not from the public internet).
examples/github-oauth/ is a TypeScript Cloudflare Worker that authenticates with GitHub, issues session cookies for browsers and long-lived tokens for agents/scripts, and forwards requests to ripgit via Service Binding. Its landing page and /settings also support the same text-mode negotiation for curl-driven agents.
See examples/github-oauth/README.md for a focused deploy/setup guide.
Local dev:
cd examples/github-oauth
npm install
npm run dev:full # auth worker on :8787, ripgit as service binding
Visit http://localhost:8787 → sign in → go to /settings → create a token → push:
git remote add origin http://username:TOKEN@localhost:8787/username/myrepo
git push origin main
First-time setup:
http://localhost:8787/oauth/callbackGITHUB_CLIENT_ID in examples/github-oauth/wrangler.tomlwrangler secret put GITHUB_CLIENT_SECRETwrangler secret put SESSION_SECRETwrangler kv namespace create OAUTH_KV → fill IDs into wrangler.tomlDeploy:
wrangler deploy # ripgit worker
cd examples/github-oauth && wrangler deploy # auth worker
Update the GitHub OAuth App's callback URL to your deployed auth worker URL.
./scripts/push-test.sh -u username -t TOKEN -w https://your-worker.dev -r /path/to/repo
Prerequisites: Rust, wrangler, LLVM (for zstd-sys).
brew install llvm
git clone https://github.com/your-org/ripgit
cd ripgit
wrangler kv namespace create REGISTRY # fill ID into wrangler.toml
wrangler deploy
Without the auth worker in front, all repos are publicly readable and writable by anyone with the URL.
browser / git client / agent
│
▼
Auth Worker (examples/github-oauth — optional, recommended)
│ validates session/token, sets X-Ripgit-Actor-* headers
│ Service Binding
▼
ripgit Worker (entry, routing)
│ /:owner/:repo/* → DO named "{owner}/{repo}"
│ /:owner/ → profile page (queries REGISTRY KV)
▼
Repository Durable Object (one per repo)
├── schema.rs 11 tables + 3 FTS5 virtual tables
├── pack.rs streaming pack parser + pack generator
├── git.rs smart HTTP protocol (receive-pack, upload-pack)
├── store.rs delta compression, commit graph, FTS rebuild
├── api.rs read API
├── diff.rs tree diff + line-level diffs
└── web.rs server-rendered HTML (9 pages)
▼
SQLite (up to 10 GB per DO)
KV namespaces:
REGISTRY — "repo:{owner}/{repo}" written on first push
OAUTH_KV — tokens, sessions (auth worker only)
Cloudflare Workers has a 100 MB request body limit. Push large repos incrementally:
./scripts/push-test.sh -u username -t TOKEN -r /path/to/repo -s 200
Or manually in checkpoints:
STEP=250
for FP in $(seq $STEP $STEP $(git rev-list --first-parent --count main)); do
SHA=$(git rev-list --reverse --first-parent main | sed -n "${FP}p")
git push origin "${SHA}:refs/heads/main"
done
git push origin main
AGPL-3.0. See LICENSE.
2,679 followers · starred Mar 2026
368 followers · starred Mar 2026
235 followers · starred Mar 2026
388 followers · starred Mar 2026
A fast, searchable, self-hosted Git remote built with Durable Objects
See the codeA self-hostable git remote backed by Cloudflare Durable Objects. One DO per repo, SQLite storage, FTS5 search, delta compression via xpatch. Built in Rust with workers-rs.
Live example: git.theagents.company — deathbyknowledge's repos including agents (~1k commits) and curl (~40k commits)
git remote add origin https://your-worker.workers.dev/username/myproject
git push origin main
git push, git clone, git fetch with any git clientexamples/github-oauth/text/markdown and text/plain, with explicit actions and curl-friendly paths@author:, @message:, @path:, @ext:, @content: query prefixes/:owner/:repo/raw/:ref/*path/:owner/ owner profile — lists your repos
/:owner/:repo/ repo home — file tree, README, recent commits
/:owner/:repo/commits commit history
/:owner/:repo/commit/:sha commit detail with diff
/:owner/:repo/tree/:ref/* directory browser
/:owner/:repo/blob/:ref/* file viewer
/:owner/:repo/raw/:ref/* raw file bytes
/:owner/:repo/search-ui full-text search
/:owner/:repo/settings stats, index rebuilds, config, delete (owner only)
All endpoints under /:owner/:repo/.
| Endpoint | Description |
|---|---|
GET /refs | List branches and tags |
GET /log?ref=main&limit=50 | Commit history |
GET /commit/:hash | Single commit |
GET /tree/:hash | Directory listing |
GET /blob/:hash | File content |
GET /file?ref=main&path=src/lib.rs | File at ref + path |
GET /search?q=TODO | Code search |
GET /search?q=fix&scope=commits | Commit message search |
GET /diff/:sha | Commit diff |
GET /compare/base...head | Two-commit comparison |
GET /stats | Compression and storage stats |
Browsable pages support negotiated text mode for curl, scripts, and agents.
curl -H 'Accept: text/markdown' https://your-worker.workers.dev/alice/repo/
curl -H 'Accept: text/plain' https://your-worker.workers.dev/alice/repo/commits
curl 'https://your-worker.workers.dev/alice/repo/tree/main/src?format=md'
Accept: text/markdown returns a markdown viewAccept: text/plain returns the same content as plain text?format=md or ?format=text works when you can't keep headers attached while following linksFiles (GET paths) and spell out POST actions, fields, and requirements in an Actions sectionripgit reads identity from trusted X-Ripgit-Actor-* headers, which are only settable by an upstream auth worker via Service Binding (not from the public internet).
examples/github-oauth/ is a TypeScript Cloudflare Worker that authenticates with GitHub, issues session cookies for browsers and long-lived tokens for agents/scripts, and forwards requests to ripgit via Service Binding. Its landing page and /settings also support the same text-mode negotiation for curl-driven agents.
See examples/github-oauth/README.md for a focused deploy/setup guide.
Local dev:
cd examples/github-oauth
npm install
npm run dev:full # auth worker on :8787, ripgit as service binding
Visit http://localhost:8787 → sign in → go to /settings → create a token → push:
git remote add origin http://username:TOKEN@localhost:8787/username/myrepo
git push origin main
First-time setup:
http://localhost:8787/oauth/callbackGITHUB_CLIENT_ID in examples/github-oauth/wrangler.tomlwrangler secret put GITHUB_CLIENT_SECRETwrangler secret put SESSION_SECRETwrangler kv namespace create OAUTH_KV → fill IDs into wrangler.tomlDeploy:
wrangler deploy # ripgit worker
cd examples/github-oauth && wrangler deploy # auth worker
Update the GitHub OAuth App's callback URL to your deployed auth worker URL.
./scripts/push-test.sh -u username -t TOKEN -w https://your-worker.dev -r /path/to/repo
Prerequisites: Rust, wrangler, LLVM (for zstd-sys).
brew install llvm
git clone https://github.com/your-org/ripgit
cd ripgit
wrangler kv namespace create REGISTRY # fill ID into wrangler.toml
wrangler deploy
Without the auth worker in front, all repos are publicly readable and writable by anyone with the URL.
browser / git client / agent
│
▼
Auth Worker (examples/github-oauth — optional, recommended)
│ validates session/token, sets X-Ripgit-Actor-* headers
│ Service Binding
▼
ripgit Worker (entry, routing)
│ /:owner/:repo/* → DO named "{owner}/{repo}"
│ /:owner/ → profile page (queries REGISTRY KV)
▼
Repository Durable Object (one per repo)
├── schema.rs 11 tables + 3 FTS5 virtual tables
├── pack.rs streaming pack parser + pack generator
├── git.rs smart HTTP protocol (receive-pack, upload-pack)
├── store.rs delta compression, commit graph, FTS rebuild
├── api.rs read API
├── diff.rs tree diff + line-level diffs
└── web.rs server-rendered HTML (9 pages)
▼
SQLite (up to 10 GB per DO)
KV namespaces:
REGISTRY — "repo:{owner}/{repo}" written on first push
OAUTH_KV — tokens, sessions (auth worker only)
Cloudflare Workers has a 100 MB request body limit. Push large repos incrementally:
./scripts/push-test.sh -u username -t TOKEN -r /path/to/repo -s 200
Or manually in checkpoints:
STEP=250
for FP in $(seq $STEP $STEP $(git rev-list --first-parent --count main)); do
SHA=$(git rev-list --reverse --first-parent main | sed -n "${FP}p")
git push origin "${SHA}:refs/heads/main"
done
git push origin main
AGPL-3.0. See LICENSE.
2,679 followers · starred Mar 2026
368 followers · starred Mar 2026
235 followers · starred Mar 2026
388 followers · starred Mar 2026