| Initial import of the rickub CLI as a standalone public project 1a1d430 Olivier Girardot 9h ago | 1 | package cmd |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strconv" |
| 6 | |
| 7 | "rickub.com/rickub/cli/internal/api" |
| 8 | |
| 9 | "github.com/spf13/cobra" |
| 10 | ) |
| 11 | |
| 12 | var ( |
| 13 | prRepo string |
| 14 | prState string |
| 15 | prBase string |
| 16 | prHead string |
| 17 | prTitle string |
| 18 | prBody string |
| 19 | prHeadOwner string |
| 20 | prHeadRepo string |
| 21 | prMergeMethod string |
| 22 | ) |
| 23 | |
| 24 | func init() { |
| 25 | prCmd := &cobra.Command{ |
| 26 | Use: "pr", |
| 27 | Aliases: []string{"mr", "pull"}, |
| 28 | Short: "Work with merge requests (pull requests)", |
| 29 | } |
| 30 | // --repo is shared by all subcommands; defaults to the cwd git remote. |
| 31 | prCmd.PersistentFlags().StringVarP(&prRepo, "repo", "R", "", "target repo as owner/repo (default: current git remote)") |
| 32 | |
| 33 | listCmd := &cobra.Command{ |
| 34 | Use: "list", |
| 35 | Short: "List merge requests", |
| 36 | Args: cobra.NoArgs, |
| 37 | RunE: runPRList, |
| 38 | } |
| 39 | listCmd.Flags().StringVar(&prState, "state", "open", "open | closed | merged | all") |
| 40 | addPaging(listCmd) |
| 41 | |
| 42 | viewCmd := &cobra.Command{ |
| 43 | Use: "view <number>", |
| 44 | Short: "Show a merge request with comments and reviews", |
| 45 | Args: cobra.ExactArgs(1), |
| 46 | RunE: runPRView, |
| 47 | } |
| 48 | |
| 49 | createCmd := &cobra.Command{ |
| 50 | Use: "create", |
| 51 | Short: "Open a merge request", |
| 52 | Args: cobra.NoArgs, |
| 53 | RunE: runPRCreate, |
| 54 | } |
| 55 | createCmd.Flags().StringVar(&prBase, "base", "", "base branch (required)") |
| 56 | createCmd.Flags().StringVar(&prHead, "head", "", "head branch (required)") |
| 57 | createCmd.Flags().StringVarP(&prTitle, "title", "t", "", "title (required)") |
| 58 | createCmd.Flags().StringVarP(&prBody, "body", "b", "", "description body") |
| 59 | createCmd.Flags().StringVar(&prHeadOwner, "head-owner", "", "fork owner for a cross-repo MR") |
| 60 | createCmd.Flags().StringVar(&prHeadRepo, "head-repo", "", "fork name for a cross-repo MR") |
| 61 | |
| 62 | mergeCmd := &cobra.Command{ |
| 63 | Use: "merge <number>", |
| 64 | Short: "Merge a merge request", |
| 65 | Args: cobra.ExactArgs(1), |
| 66 | RunE: runPRMerge, |
| 67 | } |
| 68 | mergeCmd.Flags().StringVar(&prMergeMethod, "method", "merge", "merge | squash | ff-only") |
| 69 | |
| 70 | closeCmd := &cobra.Command{ |
| 71 | Use: "close <number>", |
| 72 | Short: "Close a merge request", |
| 73 | Args: cobra.ExactArgs(1), |
| 74 | RunE: runPRClose, |
| 75 | } |
| 76 | |
| 77 | reopenCmd := &cobra.Command{ |
| 78 | Use: "reopen <number>", |
| 79 | Short: "Reopen a closed merge request", |
| 80 | Args: cobra.ExactArgs(1), |
| 81 | RunE: runPRReopen, |
| 82 | } |
| 83 | |
| 84 | commentCmd := &cobra.Command{ |
| 85 | Use: "comment <number>", |
| 86 | Short: "Comment on a merge request", |
| 87 | Args: cobra.ExactArgs(1), |
| 88 | RunE: runPRComment, |
| 89 | } |
| 90 | commentCmd.Flags().StringVarP(&prBody, "body", "b", "", "comment body (required)") |
| 91 | |
| 92 | prCmd.AddCommand(listCmd, viewCmd, createCmd, mergeCmd, closeCmd, reopenCmd, commentCmd) |
| 93 | rootCmd.AddCommand(prCmd) |
| 94 | } |
| 95 | |
| 96 | func prNumber(arg string) (int, error) { |
| 97 | n, err := strconv.Atoi(arg) |
| 98 | if err != nil || n <= 0 { |
| 99 | return 0, fmt.Errorf("invalid merge-request number %q", arg) |
| 100 | } |
| 101 | return n, nil |
| 102 | } |
| 103 | |
| 104 | func runPRList(cmd *cobra.Command, _ []string) error { |
| 105 | client, err := newClient() |
| 106 | if err != nil { |
| 107 | return err |
| 108 | } |
| 109 | owner, repo, err := resolveRepo(prRepo) |
| 110 | if err != nil { |
| 111 | return err |
| 112 | } |
| 113 | page, err := client.ListMergeRequests(cmd.Context(), owner, repo, prState, pageFlag, perPageFlag) |
| 114 | if err != nil { |
| 115 | return err |
| 116 | } |
| 117 | if flagJSON { |
| 118 | return printJSON(cmd.OutOrStdout(), page) |
| 119 | } |
| 120 | if len(page.Items) == 0 { |
| 121 | fmt.Fprintln(cmd.OutOrStdout(), "No merge requests.") |
| 122 | return nil |
| 123 | } |
| 124 | tw := newTabw(cmd.OutOrStdout()) |
| 125 | fmt.Fprintln(tw, "#\tSTATE\tTITLE\tHEAD→BASE\tAUTHOR") |
| 126 | for _, mr := range page.Items { |
| 127 | fmt.Fprintf(tw, "%d\t%s\t%s\t%s→%s\t%s\n", mr.Number, mr.State, mr.Title, mr.HeadBranch, mr.BaseBranch, mr.Author) |
| 128 | } |
| 129 | tw.Flush() |
| 130 | printPageFooter(cmd, page.Page) |
| 131 | return nil |
| 132 | } |
| 133 | |
| 134 | func runPRView(cmd *cobra.Command, args []string) error { |
| 135 | client, err := newClient() |
| 136 | if err != nil { |
| 137 | return err |
| 138 | } |
| 139 | owner, repo, err := resolveRepo(prRepo) |
| 140 | if err != nil { |
| 141 | return err |
| 142 | } |
| 143 | n, err := prNumber(args[0]) |
| 144 | if err != nil { |
| 145 | return err |
| 146 | } |
| 147 | mr, err := client.GetMergeRequest(cmd.Context(), owner, repo, n) |
| 148 | if err != nil { |
| 149 | return err |
| 150 | } |
| 151 | if flagJSON { |
| 152 | return printJSON(cmd.OutOrStdout(), mr) |
| 153 | } |
| 154 | out := cmd.OutOrStdout() |
| 155 | fmt.Fprintf(out, "#%d %s [%s]\n", mr.Number, mr.Title, mr.State) |
| 156 | fmt.Fprintf(out, "%s wants to merge %s → %s\n", mr.Author, mr.HeadBranch, mr.BaseBranch) |
| 157 | if mr.CrossRepo { |
| 158 | fmt.Fprintf(out, "cross-repo from %s/%s\n", mr.HeadOwner, mr.HeadRepo) |
| 159 | } |
| 160 | if mr.Body != "" { |
| 161 | fmt.Fprintf(out, "\n%s\n", mr.Body) |
| 162 | } |
| 163 | if len(mr.Reviewers) > 0 { |
| 164 | fmt.Fprintf(out, "\nReviewers: %s\n", joinSubjects(mr.Reviewers)) |
| 165 | } |
| 166 | if len(mr.Assignees) > 0 { |
| 167 | fmt.Fprintf(out, "Assignees: %s\n", joinSubjects(mr.Assignees)) |
| 168 | } |
| 169 | if len(mr.Reviews) > 0 { |
| 170 | fmt.Fprintln(out, "\nReviews:") |
| 171 | for _, r := range mr.Reviews { |
| 172 | fmt.Fprintf(out, " %s: %s\n", r.Reviewer, r.Verdict) |
| 173 | } |
| 174 | } |
| 175 | if len(mr.Comments) > 0 { |
| 176 | fmt.Fprintln(out, "\nComments:") |
| 177 | for _, c := range mr.Comments { |
| 178 | fmt.Fprintf(out, " %s (%s):\n %s\n", c.Author, humanTime(c.CreatedAt), c.Body) |
| 179 | } |
| 180 | } |
| 181 | return nil |
| 182 | } |
| 183 | |
| 184 | func joinSubjects(subs []api.Subject) string { |
| 185 | out := "" |
| 186 | for i, s := range subs { |
| 187 | if i > 0 { |
| 188 | out += ", " |
| 189 | } |
| 190 | label := s.Name |
| 191 | if s.Type == "team" { |
| 192 | label = "@team/" + s.Name |
| 193 | } |
| 194 | out += label |
| 195 | } |
| 196 | return out |
| 197 | } |
| 198 | |
| 199 | func runPRCreate(cmd *cobra.Command, _ []string) error { |
| 200 | client, err := newClient() |
| 201 | if err != nil { |
| 202 | return err |
| 203 | } |
| 204 | owner, repo, err := resolveRepo(prRepo) |
| 205 | if err != nil { |
| 206 | return err |
| 207 | } |
| 208 | if prBase == "" || prHead == "" || prTitle == "" { |
| 209 | return fmt.Errorf("--base, --head, and --title are required") |
| 210 | } |
| 211 | mr, err := client.CreateMergeRequest(cmd.Context(), owner, repo, api.MergeRequestCreate{ |
| 212 | Base: prBase, |
| 213 | Head: prHead, |
| 214 | Title: prTitle, |
| 215 | Body: prBody, |
| 216 | HeadOwner: prHeadOwner, |
| 217 | HeadRepo: prHeadRepo, |
| 218 | }) |
| 219 | if err != nil { |
| 220 | return err |
| 221 | } |
| 222 | if flagJSON { |
| 223 | return printJSON(cmd.OutOrStdout(), mr) |
| 224 | } |
| 225 | fmt.Fprintf(cmd.OutOrStdout(), "Opened merge request #%d: %s\n", mr.Number, mr.Title) |
| 226 | return nil |
| 227 | } |
| 228 | |
| 229 | func runPRMerge(cmd *cobra.Command, args []string) error { |
| 230 | client, err := newClient() |
| 231 | if err != nil { |
| 232 | return err |
| 233 | } |
| 234 | owner, repo, err := resolveRepo(prRepo) |
| 235 | if err != nil { |
| 236 | return err |
| 237 | } |
| 238 | n, err := prNumber(args[0]) |
| 239 | if err != nil { |
| 240 | return err |
| 241 | } |
| 242 | mr, err := client.MergeMergeRequest(cmd.Context(), owner, repo, n, prMergeMethod) |
| 243 | if err != nil { |
| 244 | return err |
| 245 | } |
| 246 | if flagJSON { |
| 247 | return printJSON(cmd.OutOrStdout(), mr) |
| 248 | } |
| 249 | fmt.Fprintf(cmd.OutOrStdout(), "Merged #%d (%s) → %s\n", mr.Number, prMergeMethod, dash(mr.MergeSHA)) |
| 250 | return nil |
| 251 | } |
| 252 | |
| 253 | func runPRClose(cmd *cobra.Command, args []string) error { |
| 254 | client, err := newClient() |
| 255 | if err != nil { |
| 256 | return err |
| 257 | } |
| 258 | owner, repo, err := resolveRepo(prRepo) |
| 259 | if err != nil { |
| 260 | return err |
| 261 | } |
| 262 | n, err := prNumber(args[0]) |
| 263 | if err != nil { |
| 264 | return err |
| 265 | } |
| 266 | mr, err := client.CloseMergeRequest(cmd.Context(), owner, repo, n) |
| 267 | if err != nil { |
| 268 | return err |
| 269 | } |
| 270 | if flagJSON { |
| 271 | return printJSON(cmd.OutOrStdout(), mr) |
| 272 | } |
| 273 | fmt.Fprintf(cmd.OutOrStdout(), "Closed #%d\n", mr.Number) |
| 274 | return nil |
| 275 | } |
| 276 | |
| 277 | func runPRReopen(cmd *cobra.Command, args []string) error { |
| 278 | client, err := newClient() |
| 279 | if err != nil { |
| 280 | return err |
| 281 | } |
| 282 | owner, repo, err := resolveRepo(prRepo) |
| 283 | if err != nil { |
| 284 | return err |
| 285 | } |
| 286 | n, err := prNumber(args[0]) |
| 287 | if err != nil { |
| 288 | return err |
| 289 | } |
| 290 | mr, err := client.ReopenMergeRequest(cmd.Context(), owner, repo, n) |
| 291 | if err != nil { |
| 292 | return err |
| 293 | } |
| 294 | if flagJSON { |
| 295 | return printJSON(cmd.OutOrStdout(), mr) |
| 296 | } |
| 297 | fmt.Fprintf(cmd.OutOrStdout(), "Reopened #%d\n", mr.Number) |
| 298 | return nil |
| 299 | } |
| 300 | |
| 301 | func runPRComment(cmd *cobra.Command, args []string) error { |
| 302 | client, err := newClient() |
| 303 | if err != nil { |
| 304 | return err |
| 305 | } |
| 306 | owner, repo, err := resolveRepo(prRepo) |
| 307 | if err != nil { |
| 308 | return err |
| 309 | } |
| 310 | n, err := prNumber(args[0]) |
| 311 | if err != nil { |
| 312 | return err |
| 313 | } |
| 314 | if prBody == "" { |
| 315 | return fmt.Errorf("--body is required") |
| 316 | } |
| 317 | c, err := client.CommentMergeRequest(cmd.Context(), owner, repo, n, prBody) |
| 318 | if err != nil { |
| 319 | return err |
| 320 | } |
| 321 | if flagJSON { |
| 322 | return printJSON(cmd.OutOrStdout(), c) |
| 323 | } |
| 324 | fmt.Fprintf(cmd.OutOrStdout(), "Commented on #%d\n", n) |
| 325 | return nil |
| 326 | } |