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.
Serve changes at /r/<repo>/changes
8fb7aabf534adf208cd795724b1e3d287dc62f5a · Keenan Nemetz · 3d ago · parent b492b557
Serve changes at /r/<repo>/changes The metadata tree and the interface say changes; the browser was the last place spelling the route requests. Rename every method on that subtree and the URLs the server builds: redirects, activity and search results, the number alias, code links, templates, and the CLI's page links. The old route is gone rather than aliased. Nothing has been publicly released, so there is no bookmark or client to keep working, and a redirect would leave two spellings of the same page in the code. Refs #204 (01M6S6685JFG8S64ZFF452217W).
server/auth_test.go +19−19
| @@ -248,7 +248,7 @@ func TestWebApprovalNamesTheSessionAccount(t *testing.T) { | |||
| 248 | 248 | } | |
| 249 | 249 | ||
| 250 | 250 | sess, cookie := f.browserSession(t, f.ownerID) | |
| 251 | resp, body := f.browserPost(t, "/r/"+f.repo+"/requests/"+req.ID+"/approve", cookie, | ||
| 251 | resp, body := f.browserPost(t, "/r/"+f.repo+"/changes/"+req.ID+"/approve", cookie, | ||
| 252 | 252 | url.Values{"csrf": {sess.CSRF}, "base": {"main"}, "head": {head}}) | |
| 253 | 253 | if resp.StatusCode != http.StatusSeeOther { | |
| 254 | 254 | t.Fatalf("approve: status=%d body=%s", resp.StatusCode, body) | |
| @@ -282,7 +282,7 @@ func TestWebApprovalRefusesAStaleTarget(t *testing.T) { | |||
| 282 | 282 | } | |
| 283 | 283 | ||
| 284 | 284 | sess, cookie := f.browserSession(t, f.ownerID) | |
| 285 | resp, body := f.browserPost(t, "/r/"+f.repo+"/requests/"+req.ID+"/approve", cookie, | ||
| 285 | resp, body := f.browserPost(t, "/r/"+f.repo+"/changes/"+req.ID+"/approve", cookie, | ||
| 286 | 286 | url.Values{"csrf": {sess.CSRF}, "base": {"main"}, "head": {stale}}) | |
| 287 | 287 | if resp.StatusCode != http.StatusBadRequest { | |
| 288 | 288 | t.Fatalf("stale approval: status=%d body=%s", resp.StatusCode, body) | |
| @@ -296,7 +296,7 @@ func TestWebApprovalRefusesAStaleTarget(t *testing.T) { | |||
| 296 | 296 | if _, err := metadata.SetRequestBase(m.Git(), req.ID, "other", f.ownerID); err != nil { | |
| 297 | 297 | t.Fatal(err) | |
| 298 | 298 | } | |
| 299 | resp, body = f.browserPost(t, "/r/"+f.repo+"/requests/"+req.ID+"/approve", cookie, | ||
| 299 | resp, body = f.browserPost(t, "/r/"+f.repo+"/changes/"+req.ID+"/approve", cookie, | ||
| 300 | 300 | url.Values{"csrf": {sess.CSRF}, "base": {"main"}, "head": {head}}) | |
| 301 | 301 | if resp.StatusCode != http.StatusBadRequest { | |
| 302 | 302 | t.Fatalf("stale base approval: status=%d body=%s", resp.StatusCode, body) | |
| @@ -401,12 +401,12 @@ func TestContributorNamespaceAndRequestUseTheAccountName(t *testing.T) { | |||
| 401 | 401 | t.Fatalf("contributor issue: status=%d body=%s", resp.StatusCode, body) | |
| 402 | 402 | } | |
| 403 | 403 | ||
| 404 | resp, body = f.browserGet(t, "/r/"+f.repo+"/requests/new", cookie) | ||
| 404 | resp, body = f.browserGet(t, "/r/"+f.repo+"/changes/new", cookie) | ||
| 405 | 405 | if resp.StatusCode != http.StatusOK || !strings.Contains(body, headRef) || !strings.Contains(body, "Open change") { | |
| 406 | 406 | t.Fatalf("request form: status=%d body=%s", resp.StatusCode, body) | |
| 407 | 407 | } | |
| 408 | 408 | ||
| 409 | resp, body = f.browserPost(t, "/r/"+f.repo+"/requests", cookie, url.Values{ | ||
| 409 | resp, body = f.browserPost(t, "/r/"+f.repo+"/changes", cookie, url.Values{ | ||
| 410 | 410 | "csrf": {sess.CSRF}, "title": {"Contributor patch"}, "body": {"please review"}, | |
| 411 | 411 | "base": {"main"}, "head": {headRef}, | |
| 412 | 412 | }) | |
| @@ -427,7 +427,7 @@ func TestContributorNamespaceAndRequestUseTheAccountName(t *testing.T) { | |||
| 427 | 427 | ||
| 428 | 428 | // The handler refuses a head outside the contributor namespace even if the | |
| 429 | 429 | // form is edited by hand. | |
| 430 | resp, _ = f.browserPost(t, "/r/"+f.repo+"/requests", cookie, url.Values{ | ||
| 430 | resp, _ = f.browserPost(t, "/r/"+f.repo+"/changes", cookie, url.Values{ | ||
| 431 | 431 | "csrf": {sess.CSRF}, "title": {"Wrong branch"}, "base": {"main"}, "head": {"main"}, | |
| 432 | 432 | }) | |
| 433 | 433 | if resp.StatusCode != http.StatusForbidden { | |
| @@ -471,7 +471,7 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 471 | 471 | } | |
| 472 | 472 | ||
| 473 | 473 | sess, cookie := f.browserSession(t, contrib.Name) | |
| 474 | resp, body := f.browserPost(t, "/r/"+f.repo+"/requests", cookie, url.Values{ | ||
| 474 | resp, body := f.browserPost(t, "/r/"+f.repo+"/changes", cookie, url.Values{ | ||
| 475 | 475 | "csrf": {sess.CSRF}, "title": {"Contributor patch"}, "base": {"main"}, "head": {headRef}, | |
| 476 | 476 | }) | |
| 477 | 477 | if resp.StatusCode != http.StatusSeeOther { | |
| @@ -508,11 +508,11 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 508 | 508 | } | |
| 509 | 509 | ||
| 510 | 510 | // The control is offered, with the contributor's own branch as a choice. | |
| 511 | resp, body = f.browserGet(t, "/r/"+f.repo+"/requests/"+id, cookie) | ||
| 511 | resp, body = f.browserGet(t, "/r/"+f.repo+"/changes/"+id, cookie) | ||
| 512 | 512 | if resp.StatusCode != http.StatusOK { | |
| 513 | 513 | t.Fatalf("request page: status=%d body=%s", resp.StatusCode, body) | |
| 514 | 514 | } | |
| 515 | if !strings.Contains(body, `action="/r/`+f.repo+`/requests/`+id+`/target"`) || !strings.Contains(body, headRef) { | ||
| 515 | if !strings.Contains(body, `action="/r/`+f.repo+`/changes/`+id+`/target"`) || !strings.Contains(body, headRef) { | ||
| 516 | 516 | t.Fatalf("the request page does not offer the target control:\n%s", body) | |
| 517 | 517 | } | |
| 518 | 518 | // The Review header carries the head commit under review. | |
| @@ -520,7 +520,7 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 520 | 520 | t.Fatalf("the Review header does not show the head commit:\n%s", body) | |
| 521 | 521 | } | |
| 522 | 522 | ||
| 523 | resp, body = f.browserPost(t, "/r/"+f.repo+"/requests/"+id+"/target", cookie, url.Values{ | ||
| 523 | resp, body = f.browserPost(t, "/r/"+f.repo+"/changes/"+id+"/target", cookie, url.Values{ | ||
| 524 | 524 | "csrf": {sess.CSRF}, "base": {"main"}, "head": {headRef}, | |
| 525 | 525 | }) | |
| 526 | 526 | if resp.StatusCode != http.StatusSeeOther { | |
| @@ -536,7 +536,7 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 536 | 536 | ||
| 537 | 537 | // The namespace rule is the one the create form applies: a contributor cannot | |
| 538 | 538 | // point their request at a branch they could not have pushed. | |
| 539 | resp, _ = f.browserPost(t, "/r/"+f.repo+"/requests/"+id+"/target", cookie, url.Values{ | ||
| 539 | resp, _ = f.browserPost(t, "/r/"+f.repo+"/changes/"+id+"/target", cookie, url.Values{ | ||
| 540 | 540 | "csrf": {sess.CSRF}, "base": {"main"}, "head": {"main"}, | |
| 541 | 541 | }) | |
| 542 | 542 | if resp.StatusCode != http.StatusForbidden { | |
| @@ -549,7 +549,7 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 549 | 549 | // A maintainer may move any open request, including a contributor's. | |
| 550 | 550 | ownerSess, ownerCookie := f.browserSession(t, f.ownerID) | |
| 551 | 551 | mainTip, _ := m.Git().RevParse("refs/heads/main") | |
| 552 | resp, body = f.browserPost(t, "/r/"+f.repo+"/requests/"+id+"/target", ownerCookie, url.Values{ | ||
| 552 | resp, body = f.browserPost(t, "/r/"+f.repo+"/changes/"+id+"/target", ownerCookie, url.Values{ | ||
| 553 | 553 | "csrf": {ownerSess.CSRF}, "base": {"main"}, "head": {"main"}, | |
| 554 | 554 | }) | |
| 555 | 555 | if resp.StatusCode != http.StatusSeeOther { | |
| @@ -565,11 +565,11 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 565 | 565 | if _, errs, err := f.git(base, "push", f.sshURL(f.repo), "main:refs/heads/parent"); err != nil { | |
| 566 | 566 | t.Fatalf("parent push: %v\n%s", err, errs) | |
| 567 | 567 | } | |
| 568 | resp, body = f.browserGet(t, "/r/"+f.repo+"/requests/"+id, cookie) | ||
| 569 | if resp.StatusCode != http.StatusOK || !strings.Contains(body, `action="/r/`+f.repo+`/requests/`+id+`/target"`) { | ||
| 568 | resp, body = f.browserGet(t, "/r/"+f.repo+"/changes/"+id, cookie) | ||
| 569 | if resp.StatusCode != http.StatusOK || !strings.Contains(body, `action="/r/`+f.repo+`/changes/`+id+`/target"`) { | ||
| 570 | 570 | t.Fatalf("the request page does not offer the target control:\n%s", body) | |
| 571 | 571 | } | |
| 572 | resp, body = f.browserPost(t, "/r/"+f.repo+"/requests/"+id+"/target", cookie, url.Values{ | ||
| 572 | resp, body = f.browserPost(t, "/r/"+f.repo+"/changes/"+id+"/target", cookie, url.Values{ | ||
| 573 | 573 | "csrf": {sess.CSRF}, "base": {"parent"}, "head": {"main"}, | |
| 574 | 574 | }) | |
| 575 | 575 | if resp.StatusCode != http.StatusSeeOther { | |
| @@ -580,13 +580,13 @@ func TestContributorRepointsRequestTarget(t *testing.T) { | |||
| 580 | 580 | } | |
| 581 | 581 | ||
| 582 | 582 | // Closed requests are settled: the head and base are history, not settings. | |
| 583 | resp, body = f.browserPost(t, "/r/"+f.repo+"/requests/"+id+"/state", cookie, url.Values{ | ||
| 583 | resp, body = f.browserPost(t, "/r/"+f.repo+"/changes/"+id+"/state", cookie, url.Values{ | ||
| 584 | 584 | "csrf": {sess.CSRF}, "state": {"closed"}, | |
| 585 | 585 | }) | |
| 586 | 586 | if resp.StatusCode != http.StatusSeeOther { | |
| 587 | 587 | t.Fatalf("close: status=%d body=%s", resp.StatusCode, body) | |
| 588 | 588 | } | |
| 589 | resp, _ = f.browserPost(t, "/r/"+f.repo+"/requests/"+id+"/target", cookie, url.Values{ | ||
| 589 | resp, _ = f.browserPost(t, "/r/"+f.repo+"/changes/"+id+"/target", cookie, url.Values{ | ||
| 590 | 590 | "csrf": {sess.CSRF}, "base": {"main"}, "head": {headRef}, | |
| 591 | 591 | }) | |
| 592 | 592 | if resp.StatusCode != http.StatusForbidden { | |
| @@ -652,7 +652,7 @@ jobs: | |||
| 652 | 652 | } | |
| 653 | 653 | ||
| 654 | 654 | contribSess, contribCookie := f.browserSession(t, contrib.Name) | |
| 655 | resp, body := f.browserPost(t, "/r/"+f.repo+"/requests", contribCookie, url.Values{ | ||
| 655 | resp, body := f.browserPost(t, "/r/"+f.repo+"/changes", contribCookie, url.Values{ | ||
| 656 | 656 | "csrf": {contribSess.CSRF}, "title": {"CI gate"}, "body": {"please review"}, | |
| 657 | 657 | "base": {"main"}, "head": {ref}, | |
| 658 | 658 | }) | |
| @@ -664,7 +664,7 @@ jobs: | |||
| 664 | 664 | t.Fatalf("requests = %v, err = %v", list, err) | |
| 665 | 665 | } | |
| 666 | 666 | requestID := list[0].ID | |
| 667 | requestURL := "/r/" + f.repo + "/requests/" + requestID | ||
| 667 | requestURL := "/r/" + f.repo + "/changes/" + requestID | ||
| 668 | 668 | ||
| 669 | 669 | // The Runs tab is history only; the approval belongs with the code under review. | |
| 670 | 670 | resp, body = f.browserGet(t, "/r/"+f.repo+"/runs", contribCookie) | |