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/web/web.go +8
| @@ -140,6 +140,12 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) { | |||
| 140 | 140 | if w.Runs != nil { | |
| 141 | 141 | recentRuns, _ = w.Runs.Recent(m.Name, 8) | |
| 142 | 142 | } | |
| 143 | // A site is a branch, so the page can link to it whether or not the branch | ||
| 144 | // exists yet: pushing it makes the link true, and until then it 404s. | ||
| 145 | site := "" | ||
| 146 | if w.Cfg.PagesDomain != "" { | ||
| 147 | site = "http://" + m.Name + "." + w.Cfg.PagesDomain | ||
| 148 | } | ||
| 143 | 149 | branches, _ := g.ListRefs("refs/heads/") | |
| 144 | 150 | names := make([]string, 0, len(branches)) | |
| 145 | 151 | for k := range branches { | |
| @@ -158,6 +164,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) { | |||
| 158 | 164 | Count int | |
| 159 | 165 | Branches []string | |
| 160 | 166 | IssueCount int | |
| 167 | Site string | ||
| 161 | 168 | Runs []*runs.Run | |
| 162 | 169 | }{ | |
| 163 | 170 | Page: w.base(m.Name), | |
| @@ -172,6 +179,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) { | |||
| 172 | 179 | Count: count, | |
| 173 | 180 | Branches: names, | |
| 174 | 181 | IssueCount: issueCount, | |
| 182 | Site: site, | ||
| 175 | 183 | Runs: recentRuns, | |
| 176 | 184 | }, 0) | |
| 177 | 185 | } | |