Fetch and clone
The read path: materialize, advertise, stream a pack back.
Reading is simpler than pushing, with one rule: before git touches a repository, the cache is brought up to date with the log.
// GitService.advertiseRefs and uploadPack
const repoDirectory = await this.materializer.open(repository);apps/api/src/services/git/materializer/repository-materializer.service.ts › open is the one door to a repository on disk: it returns the cache directory, current with the log. Everything that hands a repository to git (the transport, tree and blob browsing, diffs, merges) goes through it. How it catches up is the Materialization lesson in the Storage chapter; for now, treat it as "make the cache match the bucket, cheaply if it already does".
The two requests
GET info/refs?service=git-upload-pack→GitService.advertiseRefsmaterializes, then streamsgit upload-pack --advertise-refsoutput with a# service=pkt-line header.Cache-Control: no-cache, since refs move.POST git-upload-pack→GitService.uploadPackmaterializes again (usually a no-op) and pipes the client's wants and haves intogit upload-pack --stateless-rpc, streaming the pack back. Nothing is buffered.
Anonymous reads
A public repository is readable with no credentials: the auth middleware passes actor: null and decideAccess allows read on public repositories. The web app's clone button and git clone without a key both rely on this. Over SSH there is no anonymous access (next lesson).
Large clones and v2
With protocol v2 (git config protocol.version 2, the default in modern git), the client lists only the refs it wants, so refs/pull/* do not inflate every git pull. See the previous lessons for how Ghost passes v2 through safely.