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.

Identify accounts by their registered name

62576fa8157d431dcf2d360f3ebe481de77bda03 · Keenan Nemetz · 14d ago · parent 642e5994

Identify accounts by their registered name

Drop the internal account id and display name. Users, keys, passkeys,
invites, sessions, ACL entries, approvals, locks and CI records now all
use the registered name as the single identity.

The roster migrates in place: old id-keyed tables are rebuilt around the
name, and access-rule/lock references are rewritten through the same
mapping. Display-name editing and whoami --id are removed.

metadata/requests.go +13−18

@@ -27,7 +27,6 @@ import (
27 27 "time"
28 28
29 29 "github.com/rodent-software/vole/core/git"
30 "github.com/rodent-software/vole/core/id"
31 30 )
32 31
33 32 const (
@@ -39,15 +38,14 @@ const (
39 38
40 39 var (
41 40 shaRE = regexp.MustCompile(`^[0-9a-f]{40}$`)
42 requestPathRE = regexp.MustCompile(`^requests/([0-9A-HJKMNP-TV-Z]{26})(?:/(request\.md)|/approvals/([0-9A-HJKMNP-TV-Z]{26})\.md|/comments/([0-9A-HJKMNP-TV-Z]{26})\.md)$`)
41 requestPathRE = regexp.MustCompile(`^requests/([0-9A-HJKMNP-TV-Z]{26})(?:/(request\.md)|/approvals/([a-z0-9][a-z0-9_-]{1,31})\.md|/comments/([0-9A-HJKMNP-TV-Z]{26})\.md)$`)
43 42 branchNameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,62}$`)
44 43 )
45 44
46 45 type Request struct {
47 46 ID string
48 47 Title string
49 Author string // account name at the time of writing; labels are resolved for display
50 AuthorID string // account id: what the approval gate compares against
48 Author string // account name: what the approval gate compares against
51 49 Base string // branch name, e.g. "main"
52 50 Head string // commit sha under review
53 51 State string // open | merged | closed
@@ -86,13 +84,12 @@ func (r *Request) ApprovedBy(principal string) bool {
86 84 // meta is the frontmatter head of request.md. Field names are part of the
87 85 // public format. There is no updated field: the commit carries the time.
88 86 type requestMeta struct {
89 Title string `yaml:"title"`
90 Author string `yaml:"author"`
91 AuthorID string `yaml:"author_id"`
92 Base string `yaml:"base"`
93 Head string `yaml:"head"`
94 State string `yaml:"state"`
95 Created string `yaml:"created"`
87 Title string `yaml:"title"`
88 Author string `yaml:"author"`
89 Base string `yaml:"base"`
90 Head string `yaml:"head"`
91 State string `yaml:"state"`
92 Created string `yaml:"created"`
96 93 }
97 94
98 95 // Load reads and validates every request on the metadata branch.
@@ -179,7 +176,7 @@ func ParseRequests(contents map[string]string) ([]*Request, error) {
179 176 // Self-approval is not an approval. Catching it here rather than at the
180 177 // gate means the author learns immediately instead of after a rejection.
181 178 for _, a := range req.Approvals {
182 if a.Principal == req.AuthorID {
179 if a.Principal == req.Author {
183 180 problems = append(problems, fmt.Sprintf(
184 181 "%s/%s/%s/%s.md: the author cannot approve their own request", RequestsDir, req.ID, approvalDir, a.Principal))
185 182 }
@@ -221,11 +218,9 @@ func applyRequest(req *Request, raw, path string, problems *[]string) {
221 218 req.Author = strings.TrimSpace(m.Author)
222 219 if req.Author == "" {
223 220 *problems = append(*problems, fmt.Sprintf("%s: author is required", path))
224 }
225 req.AuthorID = strings.TrimSpace(m.AuthorID)
226 if !id.Valid(req.AuthorID) {
221 } else if !assigneeRE.MatchString(req.Author) {
227 222 *problems = append(*problems, fmt.Sprintf(
228 "%s: author_id must be the author's account id (a 26-character ULID), not their name; it is what blocks self-approval", path))
223 "%s: author %q is not an account name", path, req.Author))
229 224 }
230 225 req.Base = strings.TrimSpace(m.Base)
231 226 if !branchNameRE.MatchString(req.Base) {
@@ -256,8 +251,8 @@ func applyRequest(req *Request, raw, path string, problems *[]string) {
256 251
257 252 func parseApproval(principal, raw, path string) (Approval, string) {
258 253 a := Approval{Principal: principal}
259 if !id.Valid(principal) {
260 return a, fmt.Sprintf("%s: the file name must be the approver's account id (a 26-character ULID)", path)
254 if !assigneeRE.MatchString(principal) {
255 return a, fmt.Sprintf("%s: the file name must be the approver's account name", path)
261 256 }
262 257 head, _, err := Split([]byte(raw))
263 258 if err != nil {