Skip to content

Serving clones and pushes

This wires all three request types from the overview into one route, the shape you’d drop into Next.js, Remix, Hono, a Cloudflare Worker, or any framework with a Fetch-API route handler.

  1. Advertise refs. Auth/authorization is entirely the caller’s job — these handlers assume it already happened.

    import { handleInfoRefs } from 'git-fs-s3/http';
    export async function infoRefs(repo: Repo, service: 'git-upload-pack' | 'git-receive-pack') {
    const { status, headers, body } = await handleInfoRefs(repo, { service });
    return new Response(body, { status, headers });
    }
  2. Serve a clone or fetch.

    import { handleUploadPack } from 'git-fs-s3/http';
    export async function uploadPack(repo: Repo, request: Request) {
    const body = new Uint8Array(await request.arrayBuffer());
    const { status, headers, body: respBody } = await handleUploadPack(repo, body);
    return new Response(respBody, { status, headers });
    }
  3. Accept a push.

    import { applyReceivePack, parseReceivePackBody, receivePackResponse } from 'git-fs-s3/http';
    export async function receivePack(repo: Repo, request: Request) {
    const body = new Uint8Array(await request.arrayBuffer());
    const { results, stalePackPaths } = await applyReceivePack(
    repo,
    parseReceivePackBody(body),
    { repack: { threshold: 4 } } // or false to never auto-consolidate
    );
    // stalePackPaths were removed locally by the repack — delete them from
    // any secondary storage this repo also lives in, same as you'd
    // invalidate a cache. See "Repacking" for the full story.
    for (const path of stalePackPaths) {
    await deleteFromSecondaryStorage(repo, path);
    }
    const { status, headers, body: respBody } = receivePackResponse(results);
    return new Response(respBody, { status, headers });
    }

Wiring loose-object detection into upload-pack

Section titled “Wiring loose-object detection into upload-pack”

handleUploadPack’s second argument accepts a beforeWalk hook — fire it right before the reachability walk so a fully packed repo’s clone doesn’t pay a doomed loose-object probe per object (see the production stack guide):

await handleUploadPack(
repo,
body,
{ beforeWalk: () => (repo.fs as GitFs).detectLooseObjects(repo.gitdir) },
hooks
);

None of these handlers care how you route to them — they only need repo (a { fs, gitdir, cache? }, however you resolve it per-request) and the raw Request. A typical catch-all route parses the URL (.../<owner>/<repo>.git/info/refs, .../git-upload-pack, .../git-receive-pack), resolves repo for that owner/repo pair, checks auth, and dispatches to the matching handler above.