Commit 4bfcb3893d
Verified · cmc ci/build: success ci/test: success ci/vuln: success
Layout: unified · split
internal/control/issue.go +2 −5
| @@ -153,7 +153,7 @@ func runIssueCreate(c *Ctx, args []string) int { | |||
| 153 | notifyUsers(c, targets, issueSubject(repo, n, title), | 153 | notifyUsers(c, targets, issueSubject(repo, n, title), |
| 154 | notifyBody(c, fmt.Sprintf("opened issue #%d", n), b, fmt.Sprintf("%s/issues/%d", repo.Path(), n))) | 154 | notifyBody(c, fmt.Sprintf("opened issue #%d", n), b, fmt.Sprintf("%s/issues/%d", repo.Path(), n))) |
| 155 | } | 155 | } |
| 156 | return c.emit(map[string]any{"number": n}, func(w io.Writer) { | 156 | return c.emit(Created{Number: n}, func(w io.Writer) { |
| 157 | fmt.Fprintf(w, "created %s#%d\n", repo.Path(), n) | 157 | fmt.Fprintf(w, "created %s#%d\n", repo.Path(), n) |
| 158 | }) | 158 | }) |
| 159 | } | 159 | } |
| @@ -216,10 +216,7 @@ func runIssueShow(c *Ctx, args []string) int { | |||
| 216 | for _, cm := range comments { | 216 | for _, cm := range comments { |
| 217 | cs = append(cs, commentOut{cm.Author, cm.Body, cm.BodyFormat, cm.CreatedAt}) | 217 | cs = append(cs, commentOut{cm.Author, cm.Body, cm.BodyFormat, cm.CreatedAt}) |
| 218 | } | 218 | } |
| 219 | d := struct { | 219 | d := IssueShow{issueOut: issueToOut(issue, true), Comments: cs} |
| 220 | issueOut | ||
| 221 | Comments []commentOut `json:"comments,omitempty"` | ||
| 222 | }{issueToOut(issue, true), cs} | ||
| 223 | _ = repo | 220 | _ = repo |
| 224 | return c.emit(d, func(w io.Writer) { | 221 | return c.emit(d, func(w io.Writer) { |
| 225 | fmt.Fprintf(w, "#%d %s [%s] by %s\n", d.Number, d.Title, d.State, d.Author) | 222 | fmt.Fprintf(w, "#%d %s [%s] by %s\n", d.Number, d.Title, d.State, d.Author) |
internal/control/mr.go +12 −38
| @@ -281,16 +281,14 @@ func runMRCreate(c *Ctx, args []string) int { | |||
| 281 | notifyUsers(c, targets, mrSubject(repo, n, title), | 281 | notifyUsers(c, targets, mrSubject(repo, n, title), |
| 282 | notifyBody(c, fmt.Sprintf("opened merge request !%d (%s -> %s)", n, source, target), b, fmt.Sprintf("%s/mrs/%d", repo.Path(), n))) | 282 | notifyBody(c, fmt.Sprintf("opened merge request !%d (%s -> %s)", n, source, target), b, fmt.Sprintf("%s/mrs/%d", repo.Path(), n))) |
| 283 | } | 283 | } |
| 284 | out := map[string]any{"number": n, "head_sha": headSHA} | 284 | out := MRCreated{Number: n, HeadSHA: headSHA} |
| 285 | var parent *stackRef | ||
| 286 | if p, ok, err := c.Store.OpenMRBySource(repo.ID, target); err == nil && ok { | 285 | if p, ok, err := c.Store.OpenMRBySource(repo.ID, target); err == nil && ok { |
| 287 | parent = &stackRef{p.Number, p.Title} | 286 | out.StackedOn = &stackRef{p.Number, p.Title} |
| 288 | out["stacked_on"] = parent | ||
| 289 | } | 287 | } |
| 290 | return c.emit(out, func(w io.Writer) { | 288 | return c.emit(out, func(w io.Writer) { |
| 291 | fmt.Fprintf(w, "created %s!%d (%s -> %s)\n", repo.Path(), n, source, target) | 289 | fmt.Fprintf(w, "created %s!%d (%s -> %s)\n", repo.Path(), n, source, target) |
| 292 | if parent != nil { | 290 | if out.StackedOn != nil { |
| 293 | fmt.Fprintf(w, "stacked on !%d %s\n", parent.Number, parent.Title) | 291 | fmt.Fprintf(w, "stacked on !%d %s\n", out.StackedOn.Number, out.StackedOn.Title) |
| 294 | } | 292 | } |
| 295 | }) | 293 | }) |
| 296 | } | 294 | } |
| @@ -449,22 +447,9 @@ func runMRShow(c *Ctx, args []string) int { | |||
| 449 | if err != nil { | 447 | if err != nil { |
| 450 | return c.fail(protocol.ExitFailure, "%v", err) | 448 | return c.fail(protocol.ExitFailure, "%v", err) |
| 451 | } | 449 | } |
| 452 | type reviewOut struct { | 450 | var checks []CheckOut |
| 453 | Reviewer string `json:"reviewer"` | ||
| 454 | Verdict string `json:"verdict"` | ||
| 455 | Stale bool `json:"stale"` | ||
| 456 | CreatedAt string `json:"created_at"` | ||
| 457 | } | ||
| 458 | type checkOut struct { | ||
| 459 | Context string `json:"context"` | ||
| 460 | State string `json:"state"` | ||
| 461 | URL string `json:"url,omitempty"` | ||
| 462 | UpdatedAt string `json:"updated_at"` | ||
| 463 | Duration string `json:"duration,omitempty"` // CI checks only, once finished | ||
| 464 | } | ||
| 465 | var checks []checkOut | ||
| 466 | for _, st := range statuses { | 451 | for _, st := range statuses { |
| 467 | out := checkOut{Context: st.Context, State: st.State, URL: st.TargetURL, UpdatedAt: st.UpdatedAt} | 452 | out := CheckOut{Context: st.Context, State: st.State, URL: st.TargetURL, UpdatedAt: st.UpdatedAt} |
| 468 | if st.Duration > 0 { | 453 | if st.Duration > 0 { |
| 469 | out.Duration = st.Duration.String() | 454 | out.Duration = st.Duration.String() |
| 470 | } | 455 | } |
| @@ -474,16 +459,12 @@ func runMRShow(c *Ctx, args []string) int { | |||
| 474 | for _, cm := range comments { | 459 | for _, cm := range comments { |
| 475 | cs = append(cs, commentOut{cm.Author, cm.Body, cm.BodyFormat, cm.CreatedAt}) | 460 | cs = append(cs, commentOut{cm.Author, cm.Body, cm.BodyFormat, cm.CreatedAt}) |
| 476 | } | 461 | } |
| 477 | var rs []reviewOut | 462 | var rs []ReviewOut |
| 478 | for _, r := range reviews { | 463 | for _, r := range reviews { |
| 479 | rs = append(rs, reviewOut{r.Reviewer, r.Verdict, r.Stale, r.CreatedAt}) | 464 | rs = append(rs, ReviewOut{r.Reviewer, r.Verdict, r.Stale, r.CreatedAt}) |
| 480 | } | 465 | } |
| 481 | // The commits this MR carries: base..head, the diff's range. | 466 | // The commits this MR carries: base..head, the diff's range. |
| 482 | type commitOut struct { | 467 | var commits []CommitOut |
| 483 | SHA string `json:"sha"` | ||
| 484 | Subject string `json:"subject"` | ||
| 485 | } | ||
| 486 | var commits []commitOut | ||
| 487 | dir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name) | 468 | dir := RepoDir(c.Cfg.Server.Root, repo.OwnerName, repo.Name) |
| 488 | base := mr.MergedBase | 469 | base := mr.MergedBase |
| 489 | if base == "" { | 470 | if base == "" { |
| @@ -500,19 +481,12 @@ func runMRShow(c *Ctx, args []string) int { | |||
| 500 | subject = parsed.Subject | 481 | subject = parsed.Subject |
| 501 | } | 482 | } |
| 502 | } | 483 | } |
| 503 | commits = append(commits, commitOut{sha, subject}) | 484 | commits = append(commits, CommitOut{sha, subject}) |
| 504 | } | 485 | } |
| 505 | } | 486 | } |
| 506 | } | 487 | } |
| 507 | d := struct { | 488 | d := MRShow{mrOut: mrToOut(repo, mr, true), Checks: checks, Combined: combined, |
| 508 | mrOut | 489 | UnresolvedThreads: unresolved, Commits: commits, Comments: cs, Reviews: rs} |
| 509 | Checks []checkOut `json:"checks,omitempty"` | ||
| 510 | Combined string `json:"checks_combined,omitempty"` | ||
| 511 | UnresolvedThreads int `json:"unresolved_threads,omitempty"` | ||
| 512 | Commits []commitOut `json:"commits,omitempty"` | ||
| 513 | Comments []commentOut `json:"comments,omitempty"` | ||
| 514 | Reviews []reviewOut `json:"reviews,omitempty"` | ||
| 515 | }{mrToOut(repo, mr, true), checks, combined, unresolved, commits, cs, rs} | ||
| 516 | d.StackedOn, d.Stacked = stackOf(c, repo, mr) | 490 | d.StackedOn, d.Stacked = stackOf(c, repo, mr) |
| 517 | return c.emit(d, func(w io.Writer) { | 491 | return c.emit(d, func(w io.Writer) { |
| 518 | fmt.Fprintf(w, "!%d %s [%s] by %s\n%s -> %s @ %.10s\n", d.Number, d.Title, d.State, d.Author, d.Source, d.TargetRef, d.HeadSHA) | 492 | fmt.Fprintf(w, "!%d %s [%s] by %s\n%s -> %s @ %.10s\n", d.Number, d.Title, d.State, d.Author, d.Source, d.TargetRef, d.HeadSHA) |
internal/control/output.go added +57
| @@ -0,0 +1,57 @@ | |||
| 1 | package control | ||
| 2 | |||
| 3 | // Named payloads for the commands other surfaces decode. A command used | ||
| 4 | // to declare its output inline, so the web type-asserted map keys and | ||
| 5 | // nothing tied what a command emits to what a client reads (#126). | ||
| 6 | |||
| 7 | // Created is what issue create, issue comment and mr comment emit. | ||
| 8 | type Created struct { | ||
| 9 | Number int64 `json:"number"` | ||
| 10 | } | ||
| 11 | |||
| 12 | // MRCreated is what mr create emits. | ||
| 13 | type MRCreated struct { | ||
| 14 | Number int64 `json:"number"` | ||
| 15 | HeadSHA string `json:"head_sha"` | ||
| 16 | StackedOn *stackRef `json:"stacked_on,omitempty"` | ||
| 17 | } | ||
| 18 | |||
| 19 | // IssueShow is issue show's payload: the issue and its comments. | ||
| 20 | type IssueShow struct { | ||
| 21 | issueOut | ||
| 22 | Comments []commentOut `json:"comments,omitempty"` | ||
| 23 | } | ||
| 24 | |||
| 25 | // MRShow is mr show's payload. | ||
| 26 | type MRShow struct { | ||
| 27 | mrOut | ||
| 28 | Checks []CheckOut `json:"checks,omitempty"` | ||
| 29 | Combined string `json:"checks_combined,omitempty"` | ||
| 30 | UnresolvedThreads int `json:"unresolved_threads,omitempty"` | ||
| 31 | Commits []CommitOut `json:"commits,omitempty"` | ||
| 32 | Comments []commentOut `json:"comments,omitempty"` | ||
| 33 | Reviews []ReviewOut `json:"reviews,omitempty"` | ||
| 34 | } | ||
| 35 | |||
| 36 | // ReviewOut is one review on a merge request. | ||
| 37 | type ReviewOut struct { | ||
| 38 | Reviewer string `json:"reviewer"` | ||
| 39 | Verdict string `json:"verdict"` | ||
| 40 | Stale bool `json:"stale"` | ||
| 41 | CreatedAt string `json:"created_at"` | ||
| 42 | } | ||
| 43 | |||
| 44 | // CheckOut is one commit status on a merge request head. | ||
| 45 | type CheckOut struct { | ||
| 46 | Context string `json:"context"` | ||
| 47 | State string `json:"state"` | ||
| 48 | URL string `json:"url,omitempty"` | ||
| 49 | UpdatedAt string `json:"updated_at"` | ||
| 50 | Duration string `json:"duration,omitempty"` // CI checks only, once finished | ||
| 51 | } | ||
| 52 | |||
| 53 | // CommitOut is one commit a merge request carries. | ||
| 54 | type CommitOut struct { | ||
| 55 | SHA string `json:"sha"` | ||
| 56 | Subject string `json:"subject"` | ||
| 57 | } | ||
internal/control/thread.go +1 −1
| @@ -110,7 +110,7 @@ func runComment(c *Ctx, args []string, t thread, noun string, | |||
| 110 | notifyUsers(c, parts, subject, | 110 | notifyUsers(c, parts, subject, |
| 111 | notifyBody(c, fmt.Sprintf("commented on %s%d", t.symbol, number), body, fmt.Sprintf("%s/%s/%d", repo.Path(), t.segment, number))) | 111 | notifyBody(c, fmt.Sprintf("commented on %s%d", t.symbol, number), body, fmt.Sprintf("%s/%s/%d", repo.Path(), t.segment, number))) |
| 112 | } | 112 | } |
| 113 | return c.emit(map[string]any{"number": number}, func(w io.Writer) { | 113 | return c.emit(Created{Number: number}, func(w io.Writer) { |
| 114 | fmt.Fprintf(w, "commented on %s%s%d\n", repo.Path(), t.symbol, number) | 114 | fmt.Fprintf(w, "commented on %s%s%d\n", repo.Path(), t.symbol, number) |
| 115 | }) | 115 | }) |
| 116 | } | 116 | } |
internal/httpd/accounts.go +3 −2
| @@ -268,12 +268,13 @@ func (s *Server) issueCreateForm(w http.ResponseWriter, r *http.Request, u store | |||
| 268 | func (s *Server) issueCreateSubmit(w http.ResponseWriter, r *http.Request, u store.User) { | 268 | func (s *Server) issueCreateSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
| 269 | repoPath := r.PathValue("owner") + "/" + r.PathValue("repo") | 269 | repoPath := r.PathValue("owner") + "/" + r.PathValue("repo") |
| 270 | title := strings.TrimSpace(r.FormValue("title")) | 270 | title := strings.TrimSpace(r.FormValue("title")) |
| 271 | code, data, msg := s.dispatchJSON(u, []string{"issue", "create", repoPath, "--title", title, "--file", "-"}, r.FormValue("body")) | 271 | var created control.Created |
| 272 | code, msg := s.dispatchIntoStdin(u, []string{"issue", "create", repoPath, "--title", title, "--file", "-"}, r.FormValue("body"), &created) | ||
| 272 | if code != protocol.ExitOK { | 273 | if code != protocol.ExitOK { |
| 273 | http.Error(w, msg, statusForExit(code)) | 274 | http.Error(w, msg, statusForExit(code)) |
| 274 | return | 275 | return |
| 275 | } | 276 | } |
| 276 | n := int64(data["number"].(float64)) | 277 | n := created.Number |
| 277 | // Labels need write access, matching the SSH rule; the command refuses | 278 | // Labels need write access, matching the SSH rule; the command refuses |
| 278 | // otherwise and the issue stands without them. | 279 | // otherwise and the issue stands without them. |
| 279 | if args := fieldArgs("--add", r.FormValue("labels")); len(args) > 0 { | 280 | if args := fieldArgs("--add", r.FormValue("labels")); len(args) > 0 { |
internal/httpd/control.go +7 −1
| @@ -113,6 +113,12 @@ func (s *Server) runControlIntoCode(u store.User, argv []string, target any) (co | |||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | func (s *Server) dispatchInto(u store.User, argv []string, target any) (int, string) { | 115 | func (s *Server) dispatchInto(u store.User, argv []string, target any) (int, string) { |
| 116 | return s.dispatchIntoStdin(u, argv, "", target) | ||
| 117 | } | ||
| 118 | |||
| 119 | // dispatchIntoStdin is dispatchInto with a body on stdin, decoding the | ||
| 120 | // command's named payload rather than a map (#126). | ||
| 121 | func (s *Server) dispatchIntoStdin(u store.User, argv []string, stdin string, target any) (int, string) { | ||
| 116 | var stdout, stderr bytes.Buffer | 122 | var stdout, stderr bytes.Buffer |
| 117 | ctx := &control.Ctx{ | 123 | ctx := &control.Ctx{ |
| 118 | User: u, | 124 | User: u, |
| @@ -120,7 +126,7 @@ func (s *Server) dispatchInto(u store.User, argv []string, target any) (int, str | |||
| 120 | Scope: "full", | 126 | Scope: "full", |
| 121 | Store: s.st, | 127 | Store: s.st, |
| 122 | Cfg: s.cfg, | 128 | Cfg: s.cfg, |
| 123 | Stdin: strings.NewReader(""), | 129 | Stdin: strings.NewReader(stdin), |
| 124 | Stdout: &stdout, | 130 | Stdout: &stdout, |
| 125 | Stderr: &stderr, | 131 | Stderr: &stderr, |
| 126 | JSON: true, | 132 | JSON: true, |
internal/httpd/mractions.go +4 −4
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | "strconv" | 7 | "strconv" |
| 8 | "strings" | 8 | "strings" |
| 9 | 9 | ||
| 10 | "gitbay.org/gitbay/internal/control" | ||
| 10 | "gitbay.org/gitbay/internal/gitutil" | 11 | "gitbay.org/gitbay/internal/gitutil" |
| 11 | "gitbay.org/gitbay/internal/store" | 12 | "gitbay.org/gitbay/internal/store" |
| 12 | ) | 13 | ) |
| @@ -191,11 +192,10 @@ func (s *Server) mrCreateSubmit(w http.ResponseWriter, r *http.Request, u store. | |||
| 191 | if body != "" { | 192 | if body != "" { |
| 192 | argv = append(argv, "--body", body) | 193 | argv = append(argv, "--body", body) |
| 193 | } | 194 | } |
| 194 | data, msg, ok := s.runControlJSON(u, argv) | 195 | var created control.MRCreated |
| 195 | if !ok { | 196 | if msg, ok := s.runControlInto(u, argv, &created); !ok { |
| 196 | back(msg) | 197 | back(msg) |
| 197 | return | 198 | return |
| 198 | } | 199 | } |
| 199 | n, _ := data["number"].(float64) | 200 | http.Redirect(w, r, fmt.Sprintf("/%s/mrs/%d", p.Repo.Path(), created.Number), http.StatusSeeOther) |
| 200 | http.Redirect(w, r, fmt.Sprintf("/%s/mrs/%d", p.Repo.Path(), int64(n)), http.StatusSeeOther) | ||
| 201 | } | 201 | } |