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/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.

Build out change review and threaded discussions

f6f4b1db24292d1df877680714952a9c568e2b78 · Keenan Nemetz · 15d ago · parent 564acd4b

Build out change review and threaded discussions

- Add Conversation, Files and Runs tabs to change requests. Files uses the
  code-browser shape with a changed-file tree and one selected-file diff;
  Runs lists the head commit's workflow results.
- Show anchored review comments beneath the line, with a collapsed excerpt
  read from the exact blob that was reviewed, and return posted anchors to
  their line note.
- Add comment threads: any comment can be replied to, the UI flattens the
  descendants under one root, reply context links back to the parent, and
  reply targets feed a single composer at the foot of the discussion.
- Collapse reply groups while keeping the root visible, and scroll to the
  composer when Reply is selected.
- Add comment context menus with Reply, Copy text, Copy link, Edit and
  Delete. Add web edit/delete routes with author or admin checks; deletion
  leaves a tombstone so replies keep their thread.
- Support --reply-to in the issue and change CLI commands, and treat a
  missing parent as a tombstone rather than an invalid tree.
- Fix the diff comment gutter, action button colours, reply labels, and
  keep the Files tree and diff pane aligned.

Update metadata validation, web templates, CSS, JavaScript, tests, and the
CLI/change-request/issue docs.

server/auth_test.go +15−6

@@ -453,14 +453,14 @@ jobs:
453 453
454 454 // The Runs tab is history only; the approval belongs with the code under review.
455 455 resp, body = f.browserGet(t, "/r/"+f.repo+"/runs", contribCookie)
456 if resp.StatusCode != http.StatusOK || strings.Contains(body, "waiting for a maintainer") || strings.Contains(body, "Allow CI for this tip") {
456 if resp.StatusCode != http.StatusOK || strings.Contains(body, "waiting for a maintainer") || strings.Contains(body, "Allow CI</button>") {
457 457 t.Fatalf("runs tab still owns CI approval: status=%d body=%s", resp.StatusCode, body)
458 458 }
459 459 resp, body = f.browserGet(t, requestURL, contribCookie)
460 460 if resp.StatusCode != http.StatusOK || !strings.Contains(body, "waiting for a maintainer") {
461 461 t.Fatalf("contributor request CI status: status=%d body=%s", resp.StatusCode, body)
462 462 }
463 if strings.Contains(body, "Allow CI for this tip") {
463 if strings.Contains(body, "Allow CI</button>") {
464 464 t.Fatalf("contributor saw the Allow CI control: body=%s", body)
465 465 }
466 466 resp, _ = f.browserPost(t, "/r/"+f.repo+"/ci/allow", contribCookie, url.Values{
@@ -472,15 +472,17 @@ jobs:
472 472
473 473 ownerSess, ownerCookie := f.browserSession(t, f.ownerID)
474 474 resp, body = f.browserGet(t, requestURL, ownerCookie)
475 if resp.StatusCode != http.StatusOK || !strings.Contains(body, "Allow CI for this tip") {
476 t.Fatalf("maintainer CI control missing from request: status=%d body=%s", resp.StatusCode, body)
475 if resp.StatusCode != http.StatusOK || !strings.Contains(body, "Allow CI</button>") ||
476 !strings.Contains(body, `class="warn"`) || !strings.Contains(body, "Approve</button>") ||
477 !strings.Contains(body, `class="success"`) || !strings.Contains(body, "View runs") {
478 t.Fatalf("maintainer actions missing from request: status=%d body=%s", resp.StatusCode, body)
477 479 }
478 480 resp, runsPage := f.browserGet(t, "/r/"+f.repo+"/runs", ownerCookie)
479 if resp.StatusCode != http.StatusOK || strings.Contains(runsPage, "Allow CI for this tip") || strings.Contains(runsPage, "Awaiting CI approval") {
481 if resp.StatusCode != http.StatusOK || strings.Contains(runsPage, "Allow CI</button>") || strings.Contains(runsPage, "Awaiting CI approval") {
480 482 t.Fatalf("runs tab should not show contributor CI approval: status=%d body=%s", resp.StatusCode, runsPage)
481 483 }
482 484 resp, codePage := f.browserGet(t, "/r/"+f.repo+"/code", ownerCookie)
483 if resp.StatusCode != http.StatusOK || strings.Contains(codePage, "Allow CI for this tip") {
485 if resp.StatusCode != http.StatusOK || strings.Contains(codePage, "Allow CI</button>") {
484 486 t.Fatalf("contributor CI control should not be in the code browser: status=%d body=%s", resp.StatusCode, codePage)
485 487 }
486 488 resp, body = f.browserPost(t, "/r/"+f.repo+"/ci/allow", ownerCookie, url.Values{
@@ -496,6 +498,13 @@ jobs:
496 498 if run.Ref != "refs/heads/"+ref || run.Status != runs.StatusQueued {
497 499 t.Fatalf("queued run = %+v", run)
498 500 }
501
502 resp, body = f.browserGet(t, requestURL+"/runs", ownerCookie)
503 if resp.StatusCode != http.StatusOK || !strings.Contains(body, "/runs/"+run.ID) ||
504 !strings.Contains(body, "queued") || !strings.Contains(body, "Workflow results") ||
505 !strings.Contains(body, "View runs") || !strings.Contains(body, "</html>") {
506 t.Fatalf("request runs tab: status=%d body=%s", resp.StatusCode, body)
507 }
499 508 }
500 509
501 510 func TestOpenRegistrationPagesAndBegin(t *testing.T) {