Skip to content

Cloning & pushing

The host speaks git’s smart HTTP protocol. Standard clients work unmodified, with no extensions and no wrapper CLI:

Terminal window
git clone "https://$HOST/acme/web.git"

git sends HTTP Basic. The credential goes in the password field of the remote URL and the username is ignored:

Terminal window
git remote add origin "https://x:$TOKEN@$HOST/acme/web.git"

What $TOKEN is depends on the middleware the host runs.

The host from Getting Started accepts one secret, an Alchemy.Random the stack outputs. alchemy deploy prints it:

Terminal window
bun alchemy deploy
# { url: "https://…workers.dev", secret: "9f2c…" }
export TOKEN=9f2c…

The host from Part 4 verifies API keys. A signed-in user mints one; it is shown once:

Terminal window
curl -s -b jar -X POST "$HOST/api/auth/api-key/create" \
-H "Origin: $HOST" -H "Content-Type: application/json" \
-d '{"name":"laptop"}' | jq -r .key

The example app has a settings page that does the same.

The token is whatever your middleware accepts. The one Part 4 put in front of the host decodes the Basic password and asks Better Auth whose key it is:

const { password } = yield* HttpApiBuilder.securityDecode(
HttpApiSecurity.basic,
);
const key = Redacted.value(password);
if (key !== "") {
const verified = yield* auth.api.verifyApiKey({ body: { key } });
return verified.valid && verified.key
? { id: verified.key.referenceId }
: undefined;
}

A token issued by anything else is verified the same way: read it from the request, run the route or refuse. Auth has the pattern.

Leave the credential out of the URL and let a credential helper supply it. A middleware that answers an anonymous request with 401 and WWW-Authenticate makes git prompt:

Terminal window
git config credential.helper osxkeychain # or store, or your manager
git clone "https://$HOST/acme/web.git"
Password for 'https://x@…': <paste $TOKEN>

A middleware that honors public serves reads of a public repository with no credential at all; the tutorial’s does.

Capability Notes
Clone served from a precomputed bundle when one is current
Incremental fetch multi_ack_detailed with no-done
Shallow clone --depth, including deepening later
Push thin packs with offset and reference deltas
Atomic push all refs move or none do
Force push compare-and-swap on the old value
Tags lightweight and annotated, with peeled advertisement
Status report-status, report-status-v2, side-band

Every push runs a full connectivity check. The objects it references must already exist or arrive in the same pack, so a repository is never left with dangling references.

Protocol v0 only. Clients that prefer v2 negotiate down on their own.

Push size is not capped. The body streams through the Worker and into the blob store as it arrives, and the repository’s Durable Object receives rows rather than the pack. Peak memory is a fixed window, whatever the push size. Hasher has the path and Scaling the numbers.

A credential prompt in a script. Set GIT_TERMINAL_PROMPT=0 so a missing credential fails at once instead of hanging.

A push rejected with a ref message. That is the protocol working: a non-fast-forward, a stale compare-and-swap, or a read-only repository. The message names the ref.

A hung clone behind a proxy. The wire endpoints must be forwarded verbatim, with the request body streamed and the Host header kept. A proxy that buffers bodies breaks streaming pushes.