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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0: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).

README.md +36−1

@@ -52,6 +52,10 @@ Working today:
52 52 (`git-lfs-transfer`); downloads come through the HTTP clone URL, anonymously for a
53 53 public repository. A pointer whose object is missing fails the push that introduced
54 54 it, and `vole gc` sweeps what nothing reaches
55 - Releases are tags: the tag message is the notes and `.tar` / `.tar.gz` are generated
56 from the tagged tree per request, with an `ETag` from the tag object and a `304` for
57 repeat downloads. `vole release create` writes the annotated tag; `git tag -a` does
58 the same thing without the CLI
55 59 - Repo create / rename / delete from the CLI
56 60 - A derived SQLite index that can be deleted and rebuilt from the repos
57 61 - Static sites: a repository's `pages` branch is served as a website at
@@ -60,7 +64,8 @@ Working today:
60 64 - An instance ed25519 key generated at first boot (used for metadata signing in
61 65 Phase 7)
62 66
63 Not yet: releases and release artifacts, workflow artifact uploads, LFS locking.
67 Not yet: release artifacts (binaries attached to a release), workflow artifact uploads,
68 LFS locking.
64 69 Signature verification is deferred,
65 70 not planned: the SSH handshake already establishes who pushed, so signing approvals
66 71 would restate it for a verifier this deployment does not have (see the roadmap).
@@ -221,6 +226,36 @@ vole gc --dry-run # mark from every ref, report what is unreachable
221 226 vole gc # remove it (objects younger than --grace stay)
222 227 ```
223 228
229 ## Releases
230
231 A release is an annotated tag: the message is the notes and the tree is the source
232 tarball, so both travel with any clone and there is no release table to drift.
233
234 ```sh
235 vole release create v1.0.0 -F notes.md
236 git push --follow-tags
237
238 # plain git does the same thing, which is the point
239 git tag -a v1.0.0 -F notes.md && git push --follow-tags
240 ```
241
242 `/r/<repo>/releases` lists every tag and marks the lightweight ones, because a tag with
243 no tagger has no notes to show. The release page carries the notes, the commits since the
244 previous release, and two downloads:
245
246 ```sh
247 curl -O http://host/r/demo/releases/v1.0.0/archive.tar
248 curl -OJ http://host/r/demo/releases/v1.0.0/archive.tar.gz
249 ```
250
251 Both are produced from the tag when you ask, so nothing can go stale: the `ETag` is the
252 tag object id, `If-None-Match` gets a `304`, and the same tag yields the same bytes on
253 every instance. Measured on an 11.6 MiB, 2,566-file repository: 54 ms for `.tar`, 218 ms
254 for `.tar.gz`, and every extracted file checks out against the tree. A repository that
255 tracks files with Git LFS gets a warning on the page, because `git archive` copies
256 pointers rather than their contents. Binaries attached to a release are not built yet
257 (see the roadmap).
258
224 259 ## Hosting a site
225 260
226 261 A repository's `pages` branch is a website at `<repo>.<pages_domain>`. Nothing is