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.
Name branches after the account, and labels after the display
474a812d199a109ba894124cd6f26be12c73285d · Keenan Nemetz · 16d ago · parent ade6107e
Name branches after the account, and labels after the display
A contributor's folder was `refs/heads/contrib/<account-id>/`, which told a
reader nothing without decoding a ULID. It is now the account name,
refs/heads/contrib/<name>/<branch>
and the name is therefore fixed at creation: renaming an account would strand
every ref under its old folder, so `vole admin user rename` is gone and the
users row itself — kept after retirement — is what stops a name being reissued.
What people see is now a separate, mutable display name, validated by the same
rules wherever it is set (CLI, remote admin, registration, `/account`) and shown
on pages, approvals, listings and refusals. Nothing is authorized by it and
nothing on disk is keyed by it: metadata authors stay account names because the
push boundary resolves them for ownership, and approvals stay account ids.
`user_names`, `Snapshot.Reserved` and `Account.Former` are dropped: with a fixed
name there are no former names to remember, and the UNIQUE column already does
the reserving. Opening an older roster adds the `display` column and drops the
alias table in place; contributor refs written under the old id namespace are
left where they are, readable, and landable by a maintainer.
server/web/templates.go +26−9
| @@ -269,7 +269,7 @@ form.inline{display:inline} | |||
| 269 | 269 | <td class="ln">{{if .NewNo}}{{.NewNo}}{{end}}</td> | |
| 270 | 270 | <td class="code">{{.Text}}</td> | |
| 271 | 271 | </tr> | |
| 272 | {{if .Comments}}<tr class="note"><td colspan="3">{{range .Comments}}<a class="label" href="#c-{{.ID}}">{{.Author}} commented</a>{{end}}</td></tr>{{end}} | ||
| 272 | {{if .Comments}}<tr class="note"><td colspan="3">{{range .Comments}}<a class="label" href="#c-{{.ID}}">{{who .Author}} commented</a>{{end}}</td></tr>{{end}} | ||
| 273 | 273 | {{end}} | |
| 274 | 274 | {{end}} | |
| 275 | 275 | </table> | |
| @@ -347,7 +347,7 @@ form.inline{display:inline} | |||
| 347 | 347 | <td class="sha">{{short .ID}}</td> | |
| 348 | 348 | <td><a href="/r/{{$.Name}}/issues/{{.ID}}">{{.Title}}</a>{{range .Labels}}<span class="label">{{.}}</span>{{end}}{{range .Assignees}} <a class="label" href="/r/{{$.Name}}/issues?assignee={{.}}">@{{.}}</a>{{end}}</td> | |
| 349 | 349 | <td class="muted">{{.State}}</td> | |
| 350 | <td class="muted">{{.Author}}</td> | ||
| 350 | <td class="muted">{{who .Author}}</td> | ||
| 351 | 351 | <td class="muted">{{.Comments}}</td> | |
| 352 | 352 | <td class="muted">{{since .Created.Unix}}</td> | |
| 353 | 353 | </tr> | |
| @@ -369,7 +369,7 @@ labels, then the body as markdown, which you can write with any editor.</p> | |||
| 369 | 369 | {{template "header" .}} | |
| 370 | 370 | <h1>{{.Issue.Title}}</h1> | |
| 371 | 371 | <p class="crumbs"> | |
| 372 | <span class="sha">{{.Issue.ID}}</span> · {{.Issue.State}} · opened by {{.Issue.Author}} {{since .Issue.Created.Unix}} | ||
| 372 | <span class="sha">{{.Issue.ID}}</span> · {{.Issue.State}} · opened by {{who .Issue.Author}} {{since .Issue.Created.Unix}} | ||
| 373 | 373 | {{if .Labels}}<span class="label">{{.Labels}}</span>{{end}} | |
| 374 | 374 | {{range .Issue.Assignees}}<a class="label" href="/r/{{$.Name}}/issues?assignee={{.}}">@{{.}}</a>{{end}} | |
| 375 | 375 | </p> | |
| @@ -377,7 +377,7 @@ labels, then the body as markdown, which you can write with any editor.</p> | |||
| 377 | 377 | <h2>{{len .Comments}} comment(s)</h2> | |
| 378 | 378 | {{range .Comments}} | |
| 379 | 379 | <div class="card" id="c-{{.ID}}"> | |
| 380 | <p class="muted"><span class="sha">{{short .ID}}</span> · {{.Author}} · {{since .Created.Unix}} | ||
| 380 | <p class="muted"><span class="sha">{{short .ID}}</span> · {{who .Author}} · {{since .Created.Unix}} | ||
| 381 | 381 | {{if .Anchor}}· on <code>{{.Anchor.Path}}:{{.Anchor.Line}}</code> ({{.Anchor.Side}} side) | |
| 382 | 382 | {{if not .Current}}· <span class="conflict">the file has changed since this comment</span>{{end}}{{end}} | |
| 383 | 383 | </p> | |
| @@ -433,7 +433,7 @@ labels, then the body as markdown, which you can write with any editor.</p> | |||
| 433 | 433 | </div> | |
| 434 | 434 | <label>Description <textarea name="body" placeholder="Why should this land?"></textarea></label> | |
| 435 | 435 | <button type="submit">Open change request</button> | |
| 436 | {{if not .HeadBranches}}<p class="muted">Push a branch first. Contributors can push only under <code>refs/heads/contrib/<account-id>/</code>.</p>{{end}} | ||
| 436 | {{if not .HeadBranches}}<p class="muted">Push a branch first. Contributors can push only under <code>refs/heads/contrib/<account-name>/</code>.</p>{{end}} | ||
| 437 | 437 | </form> | |
| 438 | 438 | {{end}} | |
| 439 | 439 | {{if .Rows}} | |
| @@ -466,7 +466,7 @@ commit to land, plus <code>approvals/<account-id>.md</code> from each appr | |||
| 466 | 466 | <h1>{{.Request.Title}}</h1> | |
| 467 | 467 | <p class="muted"> | |
| 468 | 468 | {{.Request.State}} · <span class="sha">{{short .Request.ID}}</span> · | |
| 469 | by {{.Request.Author}} · {{since .Request.Created.Unix}} | ||
| 469 | by {{who .Request.Author}} · {{since .Request.Created.Unix}} | ||
| 470 | 470 | </p> | |
| 471 | 471 | <p> | |
| 472 | 472 | <strong>{{.Request.Base}}</strong> ← <span class="sha">{{short .Request.Head}}</span> | |
| @@ -477,7 +477,7 @@ commit to land, plus <code>approvals/<account-id>.md</code> from each appr | |||
| 477 | 477 | <h2>Discussion</h2> | |
| 478 | 478 | {{range .Comments}} | |
| 479 | 479 | <div class="card" id="c-{{.ID}}"> | |
| 480 | <p class="muted"><span class="sha">{{short .ID}}</span> · {{.Author}} · {{since .Created.Unix}} | ||
| 480 | <p class="muted"><span class="sha">{{short .ID}}</span> · {{who .Author}} · {{since .Created.Unix}} | ||
| 481 | 481 | {{if .Anchor}}· on <code>{{.Anchor.Path}}:{{.Anchor.Line}}</code> ({{.Anchor.Side}} side) | |
| 482 | 482 | {{if not .Current}}· <span class="conflict">the file has changed since this comment</span>{{end}}{{end}} | |
| 483 | 483 | </p> | |
| @@ -693,10 +693,15 @@ release a description; the tarball below works either way.</p> | |||
| 693 | 693 | <label>Account name | |
| 694 | 694 | <input type="text" name="username" pattern="[a-z0-9][a-z0-9_-]{1,31}" maxlength="32" autocomplete="username webauthn" required autofocus> | |
| 695 | 695 | </label> | |
| 696 | <label>Display name <span class="muted">(optional, shown to other people)</span> | ||
| 697 | <input type="text" name="display" maxlength="64" autocomplete="name"> | ||
| 698 | </label> | ||
| 696 | 699 | <button type="submit">Create account and passkey</button> | |
| 697 | 700 | <p class="error" data-auth-error></p> | |
| 698 | 701 | </form> | |
| 699 | <p class="muted">Names are 2–32 characters: lowercase letters, digits, <code>-</code> and <code>_</code>.</p> | ||
| 702 | <p class="muted">Names are 2–32 characters: lowercase letters, digits, <code>-</code> and <code>_</code>. | ||
| 703 | The account name is permanent — it is the folder your branches live under and cannot be | ||
| 704 | changed later. The display name is a label and can be changed any time.</p> | ||
| 700 | 705 | </div> | |
| 701 | 706 | {{template "footer" .}} | |
| 702 | 707 | {{end}} | |
| @@ -728,7 +733,19 @@ release a description; the tarball below works either way.</p> | |||
| 728 | 733 | {{define "account"}} | |
| 729 | 734 | {{template "header" .}} | |
| 730 | 735 | <h1>Account</h1> | |
| 731 | <p><strong>{{.Account.Name}}</strong> <span class="muted">{{.Account.ID}}</span></p> | ||
| 736 | <p><strong>{{.Account.Label}}</strong> <span class="muted">{{.Account.Name}} · {{.Account.ID}}</span></p> | ||
| 737 | |||
| 738 | <h2>Display name</h2> | ||
| 739 | <p class="muted">Shown next to your account on requests, issues and approvals. Your account | ||
| 740 | name <strong>{{.Account.Name}}</strong> is permanent: your branches live under | ||
| 741 | <code>refs/heads/contrib/{{.Account.Name}}/</code>.</p> | ||
| 742 | <form method="post" action="/account/display" class="card"> | ||
| 743 | <input type="hidden" name="csrf" value="{{.CSRF}}"> | ||
| 744 | <label>Display name | ||
| 745 | <input type="text" name="display" maxlength="64" value="{{.Account.Display}}" placeholder="{{.Account.Name}}"> | ||
| 746 | </label> | ||
| 747 | <button type="submit">Save display name</button> | ||
| 748 | </form> | ||
| 732 | 749 | ||
| 733 | 750 | <h2>SSH keys</h2> | |
| 734 | 751 | {{if .Account.Keys}} | |