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.
Add browser auth, contributor access, and gated CI
6c84d387e5f608360e30484a1a5b1cff41076850 · Keenan Nemetz · 16d ago · parent c36d5073
Add browser auth, contributor access, and gated CI Browser sign-in now uses passkeys. Enrollment links are one-time, sessions are hashed in users.db, and issue/comment/approval writes use the session account. HTTPS public_url is required for WebAuthn. Add the contributor role between read and maintain. Contributors can discuss and open change requests, and may push only refs/heads/contrib/<account-id>/. Contributor CI is held per commit until a maintainer allows the exact tip from the repository page. Rename the write role to maintain. write remains accepted as the historical spelling, and access.Write remains as a Go alias so existing callers and ACL rows keep working. Update the web, CLI, queue schema, and guides for the new flows, and cover the role, namespace, auth, and CI-gating paths in tests.
server/integration_test.go +6−6
| @@ -167,7 +167,7 @@ func keypair(t *testing.T, path string) (pubLine, privPath string) { | |||
| 167 | 167 | return strings.TrimSpace(string(ssh.MarshalAuthorizedKey(sshPub))), path | |
| 168 | 168 | } | |
| 169 | 169 | ||
| 170 | // addWriter creates a second user with write access and returns their key path. | ||
| 170 | // addWriter creates a second user with maintain access and returns their key path. | ||
| 171 | 171 | func (f *fixture) addWriter(t *testing.T) string { | |
| 172 | 172 | t.Helper() | |
| 173 | 173 | pub, priv := keypair(t, filepath.Join(f.cfg.DataDir, "id_writer")) | |
| @@ -181,7 +181,7 @@ func (f *fixture) addWriter(t *testing.T) string { | |||
| 181 | 181 | t.Fatal(err) | |
| 182 | 182 | } | |
| 183 | 183 | // ACL entries are keyed by account id, not by name. | |
| 184 | if err := a.SetEntry(writer.ID, "write"); err != nil { | ||
| 184 | if err := a.SetEntry(writer.ID, "maintain"); err != nil { | ||
| 185 | 185 | t.Fatal(err) | |
| 186 | 186 | } | |
| 187 | 187 | if err := m.SetACL(a, "test"); err != nil { | |
| @@ -556,7 +556,7 @@ func issueFiles(id, title, desc string) map[string]string { | |||
| 556 | 556 | } | |
| 557 | 557 | ||
| 558 | 558 | // TestIssuePushedThenRendered is the whole option-A loop: a user with an editor | |
| 559 | // and a key files a bug, and the read-only web shows it. | ||
| 559 | // and a key files a bug, and the web shows it. | ||
| 560 | 560 | func TestIssuePushedThenRendered(t *testing.T) { | |
| 561 | 561 | t.Parallel() | |
| 562 | 562 | f := newFixture(t) | |
| @@ -589,7 +589,7 @@ func TestIssuePushedThenRendered(t *testing.T) { | |||
| 589 | 589 | if !strings.Contains(body, "<code>code</code>") { | |
| 590 | 590 | t.Fatal("inline code was not rendered") | |
| 591 | 591 | } | |
| 592 | // An empty thread prints the command that fills it: the web is read-only, so | ||
| 592 | // An empty thread prints the command that fills it: the web has no comment, so | ||
| 593 | 593 | // telling somebody "no comments" and stopping is a dead end. | |
| 594 | 594 | if !strings.Contains(body, "vole issue comment "+testIssueID) { | |
| 595 | 595 | t.Fatalf("the empty comment state does not print the command:\n%s", body) | |
| @@ -1198,7 +1198,7 @@ func TestRequestPageSaysWhenItWouldConflict(t *testing.T) { | |||
| 1198 | 1198 | func TestRequestDiscussionIsPushedAndOwned(t *testing.T) { | |
| 1199 | 1199 | t.Parallel() | |
| 1200 | 1200 | f := newFixture(t) | |
| 1201 | writerKey := f.addWriter(t) // wilbur: write access, not admin | ||
| 1201 | writerKey := f.addWriter(t) // wilbur: maintain access, not admin | ||
| 1202 | 1202 | f.publish(f.repo) | |
| 1203 | 1203 | ||
| 1204 | 1204 | base := workRepo(t, map[string]string{"readme.md": "v1\n"}) | |
| @@ -1550,7 +1550,7 @@ func commentFile(author, created, body string) string { | |||
| 1550 | 1550 | func TestCommentEditsBelongToTheirAuthor(t *testing.T) { | |
| 1551 | 1551 | t.Parallel() | |
| 1552 | 1552 | f := newFixture(t) | |
| 1553 | writerKey := f.addWriter(t) // wilbur: write access, not admin | ||
| 1553 | writerKey := f.addWriter(t) // wilbur: maintain access, not admin | ||
| 1554 | 1554 | f.publish(f.repo) | |
| 1555 | 1555 | const ( | |
| 1556 | 1556 | issueID = "01M5JR2JK29KKMAA1T9MDNTPD1" | |