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 | |