gitmote

gitmote / docs / architecture / urls.md  ·  history  ·  branches

11171 bytes · raw

URLs

gitmote is a personal instance: one owner, a handful of repos, a few invited people. That single fact sets the whole URL shape — repos live in a flat, single-segment namespace (gitmote.example.com/<repo>), not user/repo. There is no per-user namespace to disambiguate, so there is nothing to disambiguate.

Status: landed. The flat single-segment namespace, access & visibility (public → anonymous read, repo-read browse, admin-only default-branch force-push), tree/blob/raw content routing (ref-in-path greedy resolution, unified tree, self-healing blob, the bare /{repo} landing), rendered-markdown link rewriting (relative nav → blob/tree, embeds → raw, ref preserved), and the dashboard + UI reshape (viewer-scoped /, top-level /login·/users·/tokens·/static, per-repo settings·access·secrets) are all implemented and reconciled into storage.md and auth.md.

The namespace

Repos are a single path segment. Everything that is not repo content is either a verb under the repo or a top-level global route:

What URL Access
Dashboard / repo list / per viewer
Repo landing (README + tree @ default branch) /<repo> (+ git's own suffixes) read
Clone / fetch GET /<repo>/info/refs?service=git-upload-pack, POST /<repo>/git-upload-pack read
Push POST /<repo>/git-receive-pack write
Browse (dir or file) /<repo>/tree/<ref>/<path…> read
File (explicit) /<repo>/blob/<ref>/<path…> read
Raw bytes /<repo>/raw/<ref>/<path…> read
History / one commit /<repo>/commits/<ref>/<path…>, /<repo>/commit/<sha> read
Compare two refs /<repo>/compare/<base>...<head> read
Branches / tags /<repo>/refs read
CI runs / one run /<repo>/runs, /<repo>/runs/<id> read
Settings (default branch, visibility, rename, delete) /<repo>/settings admin
Access (invite / revoke collaborators & spectators) /<repo>/access admin
CI secrets /<repo>/secrets admin
Login / logout /login, /logout —
Users /users admin
Personal access tokens /tokens signed-in
Static assets /static/… —
Liveness / version /healthz, /version —

Reserved names are a small fixed set — the top-level globals login, logout, users, tokens, settings, new, search, api, metrics, internal, static, healthz, version — plus two structural rules: a repo name may not start with . and may not be the bare - (nor be empty). A name is rejected against this at creation with a clear error. internal is already live (the CI report API, request-flows.md); the rest are reserved ahead of need, because un-reserving later — once a repo owns the name — is a breaking rename, while reserving costs the single owner nothing.

Nothing else is reserved: the content/action verbs (tree, blob, raw, commits, runs, …) sit at segment 2, so a repo named tree is legal (/tree/tree/main/x = repo tree, verb tree, ref main, path x). Git's own segment-2 suffixes (info, git-upload-pack, git-receive-pack, HEAD, objects) and the UI verbs are disjoint fixed vocabularies.

Repo-name rules: a single segment, no /; not reserved (above); a trailing .git is stripped (clone host/gitmote.git == host/gitmote).

Content addressing

Three content verbs, and the ref lives in the path, never a query parameter:

The UI and the markdown rewriter emit blob for files and tree for directories (self-describing, GitHub-familiar); both verbs stay tolerant as above so a hand-written or external link with the "wrong" verb still resolves.

Ref resolution is greedy. <ref> and <path> both may contain slashes, so the server takes the longest leading path prefix that names a real ref (branch or tag from the materialized repo). /<repo>/tree/feature/login/src/x.go with a branch feature/login → ref feature/login, path src/x.go. An omitted ref (the bare /<repo> landing) means the repo's default branch.

Relative links in rendered content

Ref-in-path is what makes relative links work: a link from /<repo>/tree/main/docs/readme.md resolves against that URL and keeps <repo> and <ref> for free. But two things a bare relative link can't carry, the renderer must supply, so rendered markdown has its relative links rewritten:

Absolute URLs, external http(s) links, and in-page anchors (#…) are left untouched.

Access & visibility

Users exist so the owner can invite people; access is per-repo, read on every request from the acls table (auth.md):

Per-repo ownership leaves the namespace entirely: repos are instance-level, the admin implicitly owns them, and ACLs grant everyone else. That is the core simplification the flat namespace buys.

Implementation seams

Two non-obvious constraints the flat scheme creates:

Why it looks like this