Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 6 slice 1: the tree is the site

d3296cda0c18f78097e37373822292707113d245 · Keenan Nemetz · 18d ago · parent 64ac5071

Phase 6 slice 1: the tree is the site

A repository's `pages` branch is served at <repo>.<pages_domain>, straight out
of git. No export step, no build to drift: the push moves the branch and the
next request reads the new tree.

- internal/pages: a pool of `git cat-file --batch` processes per active
  repository (closed after two idle minutes), an LRU keyed by (commit, path),
  and tree parsing in git's binary format with the object-id width detected
  rather than assumed
- ETag is the blob sha, Last-Modified the commit time, Content-Type from the
  extension or a sniff; If-None-Match and If-Modified-Since answer 304
- directories serve index.html and redirect to add the trailing slash; path
  resolution walks trees, so a traversal-shaped request is a missing page
- repository and ACL are resolved once per one-second memo window together with
  the branch tip, which is what makes the read path fast: doing it per request
  spawned three git processes and cost 3.8 ms, against 6.7 µs warm
- sites follow clone visibility, so a private repository answers 404; the repo
  page links to the site when pages_domain is configured

Measured: a site pushed over SSH is live in 1.4 s (the phase criterion is 5 s);
warm requests 6.7 µs, cold 144 µs. Custom domains and their TLS certificates are
the remaining Phase 6 work.

internal/config/config.go +24

@@ -29,6 +29,19 @@ type Config struct {
29 29 // LeaseSeconds is how long a runner may hold a job without a heartbeat before
30 30 // the job is handed to somebody else.
31 31 LeaseSeconds int `toml:"lease_seconds"`
32 // PagesDomain is the suffix a repository's site is served under:
33 // <repo>.<pages_domain>. Empty disables site hosting entirely.
34 PagesDomain string `toml:"pages_domain"`
35 // PagesRef is the branch a site is read from. It is a normal branch, pushed
36 // and reviewed like any other, so there is no export step to drift.
37 PagesRef string `toml:"pages_ref"`
38 // PagesProcs is how many `git cat-file --batch` processes serve blobs.
39 // Reads parallelise across processes: the Phase 0 spike measured 12.1k req/s
40 // with one and 24.8k with four.
41 PagesProcs int `toml:"pages_procs"`
42 // PagesCacheBytes caps the in-memory blob cache. The cache is required, not an
43 // optimisation: cold git reads are ~1.2 ms p50, warm ones 0.075 ms.
44 PagesCacheBytes int64 `toml:"pages_cache_bytes"`
32 45
33 46 Path string `toml:"-"`
34 47 }
@@ -43,6 +56,10 @@ func defaults() *Config {
43 56
44 57 LFSObjectMaxBytes: 2 << 30, // 2 GiB per LFS object
45 58 LFSWarnBytes: 5 << 20, // flag binaries over 5 MiB that are not in LFS
59
60 PagesRef: "pages",
61 PagesProcs: 4,
62 PagesCacheBytes: 32 << 20, // 32 MiB holds several 10k-file sites
46 63 }
47 64 }
48 65
@@ -117,6 +134,13 @@ lfs_object_max_bytes = 2147483648
117 134 # pointer, gets a warning naming the git lfs track command. Zero disables it.
118 135 lfs_warn_bytes = 5242880
119 136
137 # Static sites: a repository's pages branch is served at
138 # <repo>.<pages_domain>. Leave empty to serve no sites at all.
139 # pages_domain = "pages.example.com"
140 # pages_ref = "pages"
141 # pages_procs = 4
142 # pages_cache_bytes = 33554432
143
120 144 # Cap a whole push. Enforced by git's receive.maxInputSize in each repo, because
121 145 # receive-pack indexes a pack in memory and a large push costs its own size in
122 146 # RSS.