| @@ -59,7 +59,7 @@ func init() { |
| 59 | 59 | Usage: "mr ready <owner/name> <n>", Run: runMRReady}) |
| 60 | 60 | register(Command{Path: []string{"mr", "list"}, |
| 61 | 61 | Summary: "list merge requests", |
| 62 | | Usage: "mr list <owner/name> [--state open|merged|closed|source_gone|all] [--author <user>] [--milestone <title>|none] [--search <text>] [--limit <n>] [--cursor <c>]", ReadOnly: true, Run: runMRList}) |
| 62 | Usage: "mr list <owner/name> [--state open|merged|closed|source_gone|all] [--label <l>] [--author <user>] [--milestone <title>|none] [--search <text>] [--limit <n>] [--cursor <c>]", ReadOnly: true, Run: runMRList}) |
| 63 | 63 | register(Command{Path: []string{"mr", "show"}, |
| 64 | 64 | Summary: "show a merge request", |
| 65 | 65 | Usage: "mr show <owner/name> <n>", ReadOnly: true, Run: runMRShow}) |
| @@ -83,6 +83,9 @@ func init() { |
| 83 | 83 | register(Command{Path: []string{"mr", "review", "request"}, |
| 84 | 84 | Summary: "ask specific people for a review", |
| 85 | 85 | Usage: "mr review request <owner/name> <n> [--add <user>]... [--remove <user>]...", Run: runMRReviewRequest}) |
| 86 | register(Command{Path: []string{"mr", "label"}, |
| 87 | Summary: "labels", |
| 88 | Usage: "mr label <owner/name> <n> [--add <l>]... [--remove <l>]...", Run: runMRLabel}) |
| 86 | 89 | register(Command{Path: []string{"mr", "merge"}, |
| 87 | 90 | Summary: "merge", |
| 88 | 91 | Usage: "mr merge <owner/name> <n> [--strategy ff|merge|squash|rebase]", Run: runMRMerge}) |
| @@ -365,14 +368,15 @@ type mrOut struct { |
| 365 | 368 | Title string `json:"title"` |
| 366 | 369 | State string `json:"state"` |
| 367 | 370 | // Draft is an open merge request not asking to be merged yet. |
| 368 | | Draft bool `json:"draft,omitempty"` |
| 369 | | Author string `json:"author"` |
| 370 | | Source string `json:"source"` // owner/name:branch, or branch, "" if gone |
| 371 | | TargetRef string `json:"target_ref"` |
| 372 | | HeadSHA string `json:"head_sha"` |
| 373 | | Body string `json:"body,omitempty"` |
| 374 | | BodyFormat string `json:"body_format,omitempty"` |
| 375 | | Milestone string `json:"milestone,omitempty"` |
| 371 | Draft bool `json:"draft,omitempty"` |
| 372 | Author string `json:"author"` |
| 373 | Source string `json:"source"` // owner/name:branch, or branch, "" if gone |
| 374 | TargetRef string `json:"target_ref"` |
| 375 | HeadSHA string `json:"head_sha"` |
| 376 | Body string `json:"body,omitempty"` |
| 377 | BodyFormat string `json:"body_format,omitempty"` |
| 378 | Milestone string `json:"milestone,omitempty"` |
| 379 | Labels []string `json:"labels,omitempty"` |
| 376 | 380 | // ReviewRequests is who has been asked, directly, for a review. |
| 377 | 381 | ReviewRequests []string `json:"review_requests,omitempty"` |
| 378 | 382 | // StackedOn is the open merge request whose source branch this one |
| @@ -430,8 +434,8 @@ func mrToOut(repo store.Repo, m store.MR, withBody bool) mrOut { |
| 430 | 434 | } |
| 431 | 435 | o := mrOut{Number: m.Number, Title: m.Title, State: m.State, Draft: m.Draft, Author: m.Author, |
| 432 | 436 | Source: src, TargetRef: m.TargetRef, HeadSHA: m.HeadSHA, Milestone: m.Milestone, |
| 433 | | ReviewRequests: m.ReviewRequests, |
| 434 | | CreatedAt: m.CreatedAt, MergedAt: m.MergedAt, MergedBy: m.MergedBy, |
| 437 | Labels: m.Labels, ReviewRequests: m.ReviewRequests, |
| 438 | CreatedAt: m.CreatedAt, MergedAt: m.MergedAt, MergedBy: m.MergedBy, |
| 435 | 439 | ClosedAt: m.ClosedAt, ClosedBy: m.ClosedBy, SupersededBy: m.SupersededBy} |
| 436 | 440 | if withBody { |
| 437 | 441 | o.Body = m.Body |
| @@ -446,7 +450,7 @@ func runMRList(c *Ctx, args []string) int { |
| 446 | 450 | return code |
| 447 | 451 | } |
| 448 | 452 | f := store.MRFilter{State: "open"} |
| 449 | | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--author", "--milestone", "--search"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 453 | fl, err := parseFlags(args, flagSpec{Values: []string{"--state", "--label", "--author", "--milestone", "--search"}, MaxPos: 1, Usage: c.Cmd.Usage}) |
| 450 | 454 | if err != nil { |
| 451 | 455 | return c.fail(protocol.ExitUsage, "%v", err) |
| 452 | 456 | } |
| @@ -454,7 +458,7 @@ func runMRList(c *Ctx, args []string) int { |
| 454 | 458 | if fl.Has("--state") { |
| 455 | 459 | f.State = fl.Value("--state") |
| 456 | 460 | } |
| 457 | | f.Author, f.Milestone = fl.Value("--author"), fl.Value("--milestone") |
| 461 | f.Label, f.Author, f.Milestone = fl.Value("--label"), fl.Value("--author"), fl.Value("--milestone") |
| 458 | 462 | f.Search = fl.Value("--search") |
| 459 | 463 | if fl.Has("--search") { |
| 460 | 464 | if err := validQuery(f.Search); err != nil { |
| @@ -586,6 +590,9 @@ func runMRShow(c *Ctx, args []string) int { |
| 586 | 590 | state = "draft" |
| 587 | 591 | } |
| 588 | 592 | fmt.Fprintf(w, "!%d %s [%s] by %s\n%s -> %s @ %.10s\n", d.Number, d.Title, state, d.Author, d.Source, d.TargetRef, d.HeadSHA) |
| 593 | if len(d.Labels) > 0 { |
| 594 | fmt.Fprintf(w, "labels: %s\n", strings.Join(d.Labels, ", ")) |
| 595 | } |
| 589 | 596 | if len(d.ReviewRequests) > 0 { |
| 590 | 597 | fmt.Fprintf(w, "reviewers: %s\n", strings.Join(d.ReviewRequests, ", ")) |
| 591 | 598 | } |
| @@ -874,6 +881,45 @@ func runMRReview(c *Ctx, args []string) int { |
| 874 | 881 | // runMRReviewRequest is issue assign's counterpart for merge requests: it |
| 875 | 882 | // pushes a merge request into a specific person's review queue and inbox |
| 876 | 883 | // directly, rather than waiting for them to be otherwise involved (#145). |
| 884 | func runMRLabel(c *Ctx, args []string) int { |
| 885 | rest, adds, removes, err := addRemoveFlags(args) |
| 886 | if err != nil { |
| 887 | return c.failInput(err) |
| 888 | } |
| 889 | if len(adds)+len(removes) == 0 { |
| 890 | return c.usage() |
| 891 | } |
| 892 | repo, mr, code := mrRef(c, rest, policy.CanWrite) |
| 893 | if code >= 0 { |
| 894 | return code |
| 895 | } |
| 896 | if code := refuseArchived(c, repo); code >= 0 { |
| 897 | return code |
| 898 | } |
| 899 | for _, l := range adds { |
| 900 | if err := c.Store.SetMRLabel(repo, mr.ID, l, true); err != nil { |
| 901 | return c.fail(protocol.ExitFailure, "%v", err) |
| 902 | } |
| 903 | } |
| 904 | for _, l := range removes { |
| 905 | if err := c.Store.SetMRLabel(repo, mr.ID, l, false); err != nil { |
| 906 | if errors.Is(err, store.ErrNotFound) { |
| 907 | return c.fail(protocol.ExitNotFound, "%v", err) |
| 908 | } |
| 909 | return c.fail(protocol.ExitFailure, "%v", err) |
| 910 | } |
| 911 | } |
| 912 | updated, err := c.Store.MRByNumber(repo.ID, mr.Number) |
| 913 | if err != nil { |
| 914 | return c.fail(protocol.ExitFailure, "%v", err) |
| 915 | } |
| 916 | c.Store.RecordEvent(repo.ID, c.User.ID, "mr.labeled", |
| 917 | fmt.Sprintf(`{"number":%d,"labels":%s}`, mr.Number, jsonStrings(updated.Labels))) |
| 918 | return c.emit(map[string]any{"number": mr.Number, "labels": updated.Labels}, func(w io.Writer) { |
| 919 | fmt.Fprintf(w, "labels on %s!%d: %s\n", repo.Path(), mr.Number, strings.Join(updated.Labels, ", ")) |
| 920 | }) |
| 921 | } |
| 922 | |
| 877 | 923 | func runMRReviewRequest(c *Ctx, args []string) int { |
| 878 | 924 | rest, adds, removes, err := addRemoveFlags(args) |
| 879 | 925 | if err != nil { |