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 7 slice 2: a release is a tag the web can read
c56911e2c3ede0f70c7cde560042806dbf4b4ab0 · Keenan Nemetz · 17d ago · parent f76c635b
Phase 7 slice 2: a release is a tag the web can read A release is an annotated tag, so nothing new is stored: the tag message is the notes, the tree is the tarball, and both are already in every clone. - internal/gitx/tags.go lists refs/tags peeled to their commits and tells an annotated tag from a lightweight one; a lightweight tag's %(contents) is the commit message, so reading it as notes would invent a release description nobody wrote - internal/gitx/archive.go streams `git archive`; tarballs are generated per request and never stored, so they cannot disagree with the tag - internal/web/releases.go renders the list (lightweight tags labelled), the release page (notes through the same sanitiser as every pushed body, commits since the previous release, two downloads) and the archive handlers: ETag is the tag object id plus the format, conditional requests get 304, always Content-Disposition: attachment with nosniff, compressed streams queue behind a semaphore of 4 while plain tar does not - "since the previous release" is `git describe --abbrev=0 <tag>^`, not the next row of the tag list: two tags made in the same second sort arbitrarily, and a page that changes its baseline between requests is worse than one without one - the page warns when the tagged tree tracks LFS, because git archive copies pointer files verbatim (measured in spike D) - cmd/vole release create writes the annotated tag and refuses an empty message or an existing name: re-tagging is how a download link comes to mean different bytes Measured end to end against the binary with an 11.6 MiB, 2,566-file repository: release list 6.1 ms, release page 12.6 ms, .tar 53.7 ms / 13.7 MiB, .tar.gz 218 ms / 1.8 MiB, conditional request 304. Every file in the served tarball hashed to its ls-tree blob id (2,565 of 2,565).
internal/web/templates.go +63
| @@ -141,6 +141,7 @@ select{background:var(--card);color:var(--fg);border:1px solid var(--line);borde | |||
| 141 | 141 | ||
| 142 | 142 | <p><a href="/r/{{.Name}}/issues">Issues</a> <span class="muted">({{.IssueCount}})</span></p> | |
| 143 | 143 | <p><a href="/r/{{.Name}}/requests">Change requests</a></p> | |
| 144 | <p><a href="/r/{{.Name}}/releases">Releases</a> <span class="muted">({{.ReleaseCount}})</span></p> | ||
| 144 | 145 | ||
| 145 | 146 | <h2>Files</h2> | |
| 146 | 147 | {{if .Entries}} | |
| @@ -427,4 +428,66 @@ commit to land, plus <code>approvals/<user>.json</code> from each approver | |||
| 427 | 428 | {{end}} | |
| 428 | 429 | {{template "footer" .}} | |
| 429 | 430 | {{end}} | |
| 431 | |||
| 432 | {{define "releases"}} | ||
| 433 | {{template "header" .}} | ||
| 434 | <h1><a href="/r/{{.Name}}">{{.Name}}</a> · releases</h1> | ||
| 435 | {{if .Tags}} | ||
| 436 | <table> | ||
| 437 | <tr><th>Tag</th><th>Notes</th><th>Tagger</th><th>Age</th><th>Source</th></tr> | ||
| 438 | {{range .Tags}} | ||
| 439 | <tr> | ||
| 440 | <td><a href="/r/{{$.Name}}/releases/{{.Name}}">{{.Name}}</a>{{if not .Annotated}} <span class="label">lightweight</span>{{end}}</td> | ||
| 441 | <td class="muted">{{.Subject}}</td> | ||
| 442 | <td class="muted">{{if .Tagger}}{{.Tagger}}{{else}}—{{end}}</td> | ||
| 443 | <td class="muted">{{since .When}}</td> | ||
| 444 | <td><a href="/r/{{$.Name}}/releases/{{.Name}}/archive.tar">tar</a> · <a href="/r/{{$.Name}}/releases/{{.Name}}/archive.tar.gz">tar.gz</a></td> | ||
| 445 | </tr> | ||
| 446 | {{end}} | ||
| 447 | </table> | ||
| 448 | {{else}} | ||
| 449 | <p class="muted">No releases yet. A release is an annotated tag, so this is the whole flow:</p> | ||
| 450 | <pre>git tag -a v1.0.0 -F notes.md | ||
| 451 | git push --follow-tags</pre> | ||
| 452 | {{end}} | ||
| 453 | {{if .Lightweight}} | ||
| 454 | <p class="muted">{{.Lightweight}} lightweight tag(s) are listed but carry no notes: <code>git tag -a</code> | ||
| 455 | records a tagger and a message, which is what makes a tag a release.</p> | ||
| 456 | {{end}} | ||
| 457 | {{template "footer" .}} | ||
| 458 | {{end}} | ||
| 459 | |||
| 460 | {{define "release"}} | ||
| 461 | {{template "header" .}} | ||
| 462 | <h1><a href="/r/{{.Name}}">{{.Name}}</a> · {{.Tag.Name}}</h1> | ||
| 463 | <p class="muted"> | ||
| 464 | {{if .Tag.Annotated}}annotated tag{{else}}lightweight tag{{end}} | ||
| 465 | {{if .Tag.Tagger}}by {{.Tag.Tagger}}{{end}} | ||
| 466 | · {{since .Tag.When}} | ||
| 467 | · commit <a class="sha" href="/r/{{.Name}}/commit/{{.Tag.Commit}}">{{short .Tag.Commit}}</a> | ||
| 468 | </p> | ||
| 469 | |||
| 470 | {{if .Notes}} | ||
| 471 | <div class="md">{{.Notes}}</div> | ||
| 472 | {{else if not .Tag.Annotated}} | ||
| 473 | <p class="muted">A lightweight tag has no notes. Annotate it (<code>git tag -a</code>) to give this | ||
| 474 | release a description; the tarball below works either way.</p> | ||
| 475 | {{end}} | ||
| 476 | |||
| 477 | <div class="card"> | ||
| 478 | <h2>Source</h2> | ||
| 479 | <p><a href="/r/{{.Name}}/releases/{{.Tag.Name}}/archive.tar">Download .tar</a> | ||
| 480 | · <a href="/r/{{.Name}}/releases/{{.Tag.Name}}/archive.tar.gz">Download .tar.gz</a></p> | ||
| 481 | <p class="muted">Generated from the tag when you ask for it, not stored — the same bytes on | ||
| 482 | every request, so a cached copy cannot go stale.</p> | ||
| 483 | {{if .LFSPointers}}<p class="muted">This repository tracks files with Git LFS: those files appear | ||
| 484 | in the tarball as pointer files, not their contents. Clone with <code>git lfs</code> for the | ||
| 485 | real bytes.</p>{{end}} | ||
| 486 | </div> | ||
| 487 | |||
| 488 | <h2>{{if .Prev}}{{.Count}} commit(s) since {{.Prev}}{{else}}{{.Count}} commit(s){{end}}</h2> | ||
| 489 | {{template "commitlist" .}} | ||
| 490 | {{if .Capped}}<p class="muted">Showing the first {{.CappedAt}}; the rest are in the history.</p>{{end}} | ||
| 491 | {{template "footer" .}} | ||
| 492 | {{end}} | ||
| 430 | 493 | ` | |