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/hook/names_test.go +5−5
| @@ -7,7 +7,7 @@ import ( | |||
| 7 | 7 | "github.com/rodent-software/vole/forge/identity" | |
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | func TestOpenNamesResolves(t *testing.T) { | ||
| 10 | func TestOpenLabelsResolves(t *testing.T) { | ||
| 11 | 11 | t.Parallel() | |
| 12 | 12 | path := filepath.Join(t.TempDir(), "users.db") | |
| 13 | 13 | if err := identity.Init(path); err != nil { | |
| @@ -19,12 +19,12 @@ func TestOpenNamesResolves(t *testing.T) { | |||
| 19 | 19 | } | |
| 20 | 20 | defer s.Close() | |
| 21 | 21 | _, pub := writeKey(t, filepath.Join(t.TempDir(), "key")) | |
| 22 | a, err := s.Create("keenan", pub, "test") | ||
| 22 | a, err := s.Create("keenan", "Keenan O'Brien", pub, "test") | ||
| 23 | 23 | if err != nil { | |
| 24 | 24 | t.Fatal(err) | |
| 25 | 25 | } | |
| 26 | got := openNames(path)(a.ID) | ||
| 27 | if got != "keenan" { | ||
| 28 | t.Fatalf("openNames = %q, want keenan (id %s)", got, a.ID) | ||
| 26 | got := openLabels(path)(a.ID) | ||
| 27 | if got != "Keenan O'Brien" { | ||
| 28 | t.Fatalf("openLabels = %q, want the display name (id %s)", got, a.ID) | ||
| 29 | 29 | } | |
| 30 | 30 | } | |