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

internal/gitx/gitx.go +7−2

@@ -365,6 +365,11 @@ func (r *Repo) Symref() string {
365 365 return strings.TrimSpace(string(out))
366 366 }
367 367
368 // commitFormat is one line per commit: sha, author, email, epoch, subject.
369 // %x1f is a unit separator, which cannot appear in a subject the way a tab or a
370 // newline can.
371 const commitFormat = "%H%x1f%an%x1f%ae%x1f%at%x1f%s"
372
368 373 type Commit struct {
369 374 SHA string
370 375 Author string
@@ -409,7 +414,7 @@ func scrubbedEnv() []string {
409 414
410 415 // Log returns up to n commits from rev.
411 416 func (r *Repo) Log(rev string, n int) ([]Commit, error) {
412 out, errs, err := r.run(nil, "log", "--format=%H%x1f%an%x1f%ae%x1f%at%x1f%s",
417 out, errs, err := r.run(nil, "log", "--format="+commitFormat,
413 418 "-n", strconv.Itoa(n), rev, "--")
414 419 if err != nil {
415 420 return nil, fmt.Errorf("log %q: %w: %s", rev, err, errs)
@@ -536,7 +541,7 @@ func (r *Repo) CountMerges(old, new string) (int, error) {
536 541 // backlinks, so the search happens in git rather than in a second index.
537 542 func (r *Repo) LogGrep(rev, pattern string, n int) ([]Commit, error) {
538 543 out, errs, err := r.run(nil, "log",
539 "--grep="+pattern, "--fixed-strings", "--format=%H%x1f%an%x1f%ae%x1f%at%x1f%s",
544 "--grep="+pattern, "--fixed-strings", "--format="+commitFormat,
540 545 "-n", strconv.Itoa(n), rev, "--")
541 546 if err != nil {
542 547 return nil, fmt.Errorf("log --grep %q: %w: %s", pattern, err, errs)