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.
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.
cmd/vole/change.go +18−3
| @@ -18,7 +18,7 @@ func changeUsage() string { | |||
| 18 | 18 | new --title <text> --base <branch> [--head <rev>] [-m <text> | -F <file>] | |
| 19 | 19 | list [--state open|closed|all] [--format <fmt>] | |
| 20 | 20 | show <id> | |
| 21 | comment <id> [-m <text> | -F <file>] [--path <file> --line <n> [--side new|old]] | ||
| 21 | comment <id> [-m <text> | -F <file>] [--path <file> --line <n> [--side new|old]] [--reply-to <comment-id>] | ||
| 22 | 22 | edit-comment <id> <comment-id> [-m <text> | -F <file>] | |
| 23 | 23 | rm-comment <id> <comment-id> | |
| 24 | 24 | update <id> [--head <rev>] | |
| @@ -51,6 +51,7 @@ func cmdChange(args []string) error { | |||
| 51 | 51 | {long: "path"}, | |
| 52 | 52 | {long: "line"}, | |
| 53 | 53 | {long: "side"}, | |
| 54 | {long: "reply-to"}, | ||
| 54 | 55 | {long: "state"}, | |
| 55 | 56 | {long: "remote"}, | |
| 56 | 57 | {long: "format"}, | |
| @@ -66,7 +67,7 @@ func cmdChange(args []string) error { | |||
| 66 | 67 | } | |
| 67 | 68 | changeFlags := map[string][]string{ | |
| 68 | 69 | "new": {"title", "base", "head", "message", "file", "remote", "no-push"}, | |
| 69 | "comment": {"message", "file", "path", "line", "side", "remote", "no-push"}, | ||
| 70 | "comment": {"message", "file", "path", "line", "side", "reply-to", "remote", "no-push"}, | ||
| 70 | 71 | "edit-comment": {"message", "file", "remote", "no-push"}, | |
| 71 | 72 | "rm-comment": {"remote", "no-push"}, | |
| 72 | 73 | "list": {"state", "format", "remote"}, | |
| @@ -198,11 +199,21 @@ func cmdChange(args []string) error { | |||
| 198 | 199 | } else if f.given("line") || f.given("side") { | |
| 199 | 200 | return usagef("--line and --side only mean something with --path") | |
| 200 | 201 | } | |
| 202 | replyTo := strings.TrimSpace(f.one("reply-to")) | ||
| 203 | if replyTo != "" && path != "" { | ||
| 204 | return usagef("--reply-to cannot be combined with --path: a reply belongs to its parent thread") | ||
| 205 | } | ||
| 206 | if replyTo != "" { | ||
| 207 | if replyTo, err = client.ResolveRequestCommentID(c, r.ID, replyTo); err != nil { | ||
| 208 | return err | ||
| 209 | } | ||
| 210 | } | ||
| 201 | 211 | cm, err := metadata.NewRequestComment(body, author, time.Now()) | |
| 202 | 212 | if err != nil { | |
| 203 | 213 | return err | |
| 204 | 214 | } | |
| 205 | 215 | cm.Anchor = anchor | |
| 216 | cm.ReplyTo = replyTo | ||
| 206 | 217 | reapply := func() error { return metadata.AddRequestComment(c.Repo, r.ID, cm, author) } | |
| 207 | 218 | if err := reapply(); err != nil { | |
| 208 | 219 | return err | |
| @@ -307,8 +318,12 @@ func cmdChange(args []string) error { | |||
| 307 | 318 | fmt.Printf("\n%s\n", strings.TrimRight(r.Description, "\n")) | |
| 308 | 319 | } | |
| 309 | 320 | for _, cm := range r.Comments { | |
| 321 | body := strings.TrimRight(cm.Body, "\n") | ||
| 322 | if cm.Deleted { | ||
| 323 | body = "(comment deleted)" | ||
| 324 | } | ||
| 310 | 325 | fmt.Printf("\n--- %s %s (%s) ---\n%s", cm.ID, cm.Author, | |
| 311 | cm.Created.Format(time.RFC3339), strings.TrimRight(cm.Body, "\n")+"\n") | ||
| 326 | cm.Created.Format(time.RFC3339), body+"\n") | ||
| 312 | 327 | } | |
| 313 | 328 | return nil | |
| 314 | 329 | ||