HIP-1232: Git — Repository Hosting
Abstract
git is Git hosting for an org: create repos, clone, push, browse, and see
what they cost. It mounts the /v1/git surface of the cloud binary — bare
repositories on disk, the smart-HTTP and SSH transports git clients speak
natively, imports, pulls, mirroring and the browse pages
(apps/git/git.go:1-45). It is implemented in hanzoai/cloud at apps/git.
This HIP states the target surface: everything under /v1/git, with the
host-gated root spellings leaving the binary for the standalone forge that
serves that host in production.
Motivation
The capability claimed four top-level prefixes for one product: its own
/v1/git, an un-host-gated browse tree at /git, and root routes — /,
/explore, /:org/:repo — answered only when the request's Host is the
dedicated git host. That host is served in production by the standalone
forge, a separate process, so the root claims published addresses this
binary never deliverably answers; the manifest walk already refuses a
parameter-first prefix.
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
The stores
The capability owns per-org stores, not one shared file: each org's repo
metadata is its own git.db opened through the org-store seam
(apps/git/git.go:222), beside the repositories themselves — bare git
repos under {DataDir}/git, with an S3-backed storage seam
(apps/git/storage.go) — and one system ssh_keys registry
(apps/git/keystore.go:68), global because an SSH connection presents a
key before any org is known, so auth is a single fingerprint lookup.
The addresses
Every route is under /v1/git: repos CRUD and usage; the smart-HTTP
protocol at /v1/git/:org/:repo/... and, for project-scoped repos,
/v1/git/:org/:project/:repo/... — the path is the only channel a git
client can carry a project in, since it sends no headers; and the browse
pages, folded from /git to /v1/git/explore and
/v1/git/:org/:repo{,/tree,/blob,/commits}, where literal segments outrank
the :org parameter so nothing collides. The router today still carries the
/git tree and the three host-gated root spellings; each pair is a line in
cloud's openapi/misfiled.txt. The browse tree closes by fold; the root
spellings close by deletion — git.hanzo.ai is served in production by the
standalone forge, so removing them changes nothing live and only retires
the single-binary dev forge origin, whose clone address remains
/v1/git/:org/:repo.
Repos, usage, pulls and control operations are typed. The smart-HTTP
operations are declared prose, and cannot be otherwise: their wire is git's
own protocol — pkt-line framing, side-band multiplexing, gzip request
bodies — not JSON, and the document says so where it matters, telling the
consumer this is not an API call to make by hand
(apps/git/smart_http.go:38-45). The browse pages answer server-rendered
HTML and are declared the same way.
Tenancy
The org is the gateway-minted, IAM-validated claim (HIP-0026), with an
optional project sub-scope; every query is scoped by it, so one org can
never read, clone, push to or delete another's repos. On the pack routes the
path also names an org, and it MUST equal the authenticated one
(apps/git/smart_http.go:284). A push is never anonymous. The one read
concession is a repository marked public: fetch-side operations answer
without an authenticated org, push-side never
(apps/git/smart_http.go:292). Over SSH, the presented public key's
fingerprint resolves through the global registry to the org whose scope the
pack machinery then runs under — the same boundary the HTTP path derives
from the header (apps/git/ssh.go:27-33).
Free, in those words
The capability is free: the plugin declares Price: cloud.Free
(plugin/git/main.go:21), and no handler gates or meters spend. Usage is
measured, not charged: every repo's size is re-measured on create and after
each push, exposed per-repo and in total at /v1/git/usage, and each
measurement emits a git.usage log line a metering consumer can bill on
(apps/git/git.go:696).
Events, observability, stage
The capability publishes no events on the bus, so a customer's webhooks
receive nothing from it. A landed receive-pack emits push.landed on the
in-process lifecycle stream, and the notification reactor delivers
push.landed, deploy.live and deploy.failed to the channels an org has
subscribed, durably, deduplicated per event (apps/git/notify.go:48-88).
Beyond the request span it emits the git.usage line and structured logs
only. Its stage is ga: the manifest row carries no stage field, and
absent means ga (HIP-0139 §8).
The push-to-build endpoint is not here. The deploy trigger's one registrant is the platform process, so the forge delivers pushes to platform's hook (HIP-1230); a receiver in this process signed, accepted, answered 204 and built nothing, which is why the address moved.
Upstreams
go-git/go-gitv5 (Apache-2.0) — repository init and the read path, confined to one file so nothing go-git-shaped escapes it (apps/git/gitbackend.go:3-12).go-git/go-billyv5 (Apache-2.0) — the filesystem abstraction under it.golang.org/x/crypto/ssh(BSD-3-Clause) — the SSH transport.- the system
gitbinary (GPL-2.0) — executed as a subprocess for stateless-RPC pack streaming (apps/git/gitexec.go), so multi-GB packs stream through stdin/stdout and never land in this process's memory; invoked, never linked. - the smart-HTTP framing patterns are ported from the upstream forge's
routers/web/repo/githttp.go(MIT) (apps/git/smart_http.go:30).
Rationale
The alternative to deleting the root spellings is keeping a second clone address alive in a binary that does not serve the host it is gated on — an address the manifest walk refuses and production traffic never reaches. The alternative to declaring the pack operations is typing them, which would publish JSON schemas for a wire that is not JSON and hand every generated SDK twelve methods that corrupt a clone when called.
Security Considerations
The wrong implementation leaks source code. A pack route that trusts the path org instead of comparing it to the authenticated one serves another tenant's repository to anyone who can spell its name; the path-vs-identity guard is the boundary. The SSH key registry is the authentication for the SSH transport, so writing to it is minting access: key registration is org-scoped and the fingerprint lookup is global only because it must run before identity exists. Pack streaming through the CLI is also the memory bound — buffering a push in-process would let one crafted pack exhaust the binary serving every org.
References
- HIP-0026 — Identity and Access Management
- HIP-0106 — The Hanzo Plugin Contract
- HIP-0139 — Capability
- HIP-1230 — Platform — The Container Plane
Copyright
Released under CC0 1.0 Universal Public Domain Dedication.