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.

Name branches after the account, and labels after the display

474a812d199a109ba894124cd6f26be12c73285d · Keenan Nemetz · 15d 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/web.go +26−8

@@ -50,26 +50,44 @@ type Web struct {
50 50 }
51 51
52 52 func New(repos *repo.Store, ix *index.Store, ident *identity.Store, rs *runs.Store, cfg *config.Config) (*Web, error) {
53 t, err := template.New("vole").Funcs(template.FuncMap{
53 auth, err := newAuth(cfg)
54 if err != nil {
55 return nil, err
56 }
57 w := &Web{Repos: repos, Index: ix, Ident: ident, Runs: rs, Cfg: cfg,
58 Auth: auth, Ceremonies: newCeremonyStore(), sem: make(chan struct{}, archiveConc)}
59 // The templates are parsed after the Web exists so the roster-backed helper
60 // below can be a method: metadata files carry account names and approvals are
61 // keyed by ids, and a page should show the display label for either.
62 w.Tmpl, err = template.New("vole").Funcs(template.FuncMap{
54 63 "since": humanSince,
55 64 "short": format.SHA,
56 65 "esc": url.PathEscape,
57 66 "bytes": format.Bytes,
58 67 "credid": identity.CredentialIDString,
68 "who": w.who,
59 69 }).Parse(templates)
60 70 if err != nil {
61 71 return nil, err
62 72 }
63 auth, err := newAuth(cfg)
64 if err != nil {
65 return nil, err
66 }
67 w := &Web{Repos: repos, Index: ix, Ident: ident, Runs: rs, Cfg: cfg, Tmpl: t,
68 Auth: auth, Ceremonies: newCeremonyStore(), sem: make(chan struct{}, archiveConc)}
69 73 w.Logger = func(string, ...any) {}
70 74 return w, nil
71 75 }
72 76
77 // who renders the account an id or name refers to as its display label. It is a
78 // presentation helper only: authors on disk stay account names and approvals
79 // stay ids, and nothing is authorized by what this returns.
80 func (w *Web) who(idOrName string) string {
81 if w.Ident == nil || idOrName == "" {
82 return idOrName
83 }
84 a, err := w.Ident.Find(idOrName)
85 if err != nil {
86 return idOrName
87 }
88 return a.Label()
89 }
90
73 91 // Page is embedded by every view. Exported so html/template can promote its
74 92 // fields; an unexported embedded type is not reachable from a template.
75 93 type Page struct {
@@ -213,7 +231,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) {
213 231 sort.Strings(names)
214 232 var contributorBranches []string
215 233 for _, name := range names {
216 if _, ok := access.ContributorRefAccount("refs/heads/" + name); ok {
234 if _, ok := access.ContributorRefName("refs/heads/" + name); ok {
217 235 contributorBranches = append(contributorBranches, name)
218 236 }
219 237 }