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 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/&lt;user&gt;.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 `