github.com/zaquestion/lab@v0.25.1/cmd/mr_show.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/MakeNowJust/heredoc/v2" 8 "github.com/charmbracelet/glamour" 9 "github.com/rsteube/carapace" 10 "github.com/spf13/cobra" 11 gitlab "github.com/xanzy/go-gitlab" 12 "github.com/zaquestion/lab/internal/action" 13 "github.com/zaquestion/lab/internal/git" 14 lab "github.com/zaquestion/lab/internal/gitlab" 15 ) 16 17 var ( 18 mrShowPatch bool 19 mrShowPatchReverse bool 20 mrShowNoColorDiff bool 21 ) 22 23 var mrShowCmd = &cobra.Command{ 24 Use: "show [remote] [<MR id or branch>]", 25 Aliases: []string{"get"}, 26 ArgAliases: []string{"s"}, 27 Short: "Describe a merge request", 28 Example: heredoc.Doc(` 29 lab mr show 30 lab mr show origin -c 31 lab mr show --no-color-diff 32 lab mr show -M 33 lab mr show -p 34 lab mr show --reverse 35 lab mr show --since "1970-01-01 00:00:00.000 +0000 UTC"`), 36 PersistentPreRun: labPersistentPreRun, 37 Run: func(cmd *cobra.Command, args []string) { 38 rn, mrNum, err := parseArgsWithGitBranchMR(args) 39 if err != nil { 40 log.Fatal(err) 41 } 42 43 mr, err := lab.MRGet(rn, int(mrNum)) 44 if err != nil { 45 log.Fatal(err) 46 } 47 48 renderMarkdown := false 49 if isOutputTerminal() { 50 noMarkdown, _ := cmd.Flags().GetBool("no-markdown") 51 if err != nil { 52 log.Fatal(err) 53 } 54 renderMarkdown = !noMarkdown 55 } 56 57 if mrShowPatch { 58 var remote string 59 60 if len(args) < 2 { 61 remote = findLocalRemote(mr.TargetProjectID) 62 } else if len(args) == 2 { 63 remote = args[0] 64 } else { 65 log.Fatal("Too many arguments.") 66 } 67 68 err := git.Fetch(remote, mr.SHA) 69 if err != nil { 70 log.Fatal(err) 71 } 72 git.Show(mr.DiffRefs.BaseSha, mr.SHA, mrShowPatchReverse) 73 return 74 } 75 76 pager := newPager(cmd.Flags()) 77 defer pager.Close() 78 79 printMR(mr, rn, renderMarkdown) 80 81 var noteLevel = NoteLevelNone 82 83 showComments, _ := cmd.Flags().GetBool("comments") 84 showActivities, _ := cmd.Flags().GetBool("activities") 85 showFull, _ := cmd.Flags().GetBool("full") 86 87 if showFull || showComments && showActivities { 88 noteLevel = NoteLevelFull 89 } else if showComments { 90 noteLevel = NoteLevelComments 91 } else if showActivities { 92 noteLevel = NoteLevelActivities 93 } 94 95 if noteLevel != NoteLevelNone { 96 discussions, err := lab.MRListDiscussions(rn, int(mrNum)) 97 if err != nil { 98 log.Fatal(err) 99 } 100 101 since, err := cmd.Flags().GetString("since") 102 if err != nil { 103 log.Fatal(err) 104 } 105 106 printDiscussions(rn, discussions, since, "mr", int(mrNum), renderMarkdown, noteLevel) 107 } 108 }, 109 } 110 111 func findLocalRemote(ProjectID int) string { 112 var remote string 113 114 project, err := lab.GetProject(ProjectID) 115 if err != nil { 116 log.Fatal(err) 117 } 118 remotesStr, err := git.GetLocalRemotes() 119 if err != nil { 120 log.Fatal(err) 121 } 122 remotes := strings.Split(remotesStr, "\n") 123 124 // find the matching local remote for this project 125 for r := range remotes { 126 // The fetch and push entries can be different for a remote. 127 // Only the fetch entry is useful. 128 if strings.Contains(remotes[r], project.SSHURLToRepo+" (fetch)") || 129 strings.Contains(remotes[r], project.HTTPURLToRepo+" (fetch)") { 130 found := strings.Split(remotes[r], "\t") 131 remote = found[0] 132 break 133 } 134 } 135 136 if remote == "" { 137 log.Fatal("remote for ", project.NameWithNamespace, " not found in local remotes") 138 } 139 return remote 140 } 141 142 func printMR(mr *gitlab.MergeRequest, project string, renderMarkdown bool) { 143 assignee := "None" 144 milestone := "None" 145 labels := "None" 146 approvedByUsers := "None" 147 approvers := "None" 148 approverGroups := "None" 149 reviewers := "None" 150 subscribed := "No" 151 state := map[string]string{ 152 "opened": "Open", 153 "closed": "Closed", 154 "merged": "Merged", 155 }[mr.State] 156 157 var _tmpStringArray []string 158 159 if state == "Open" && mr.MergeStatus == "cannot_be_merged" { 160 state = "Open (Needs Rebase)" 161 } 162 163 if mr.Assignee != nil && mr.Assignee.Username != "" { 164 assignee = mr.Assignee.Username 165 } 166 if mr.Milestone != nil { 167 milestone = mr.Milestone.Title 168 } 169 if len(mr.Labels) > 0 { 170 labels = strings.Join(mr.Labels, ", ") 171 } 172 173 if renderMarkdown { 174 r, err := getTermRenderer(glamour.WithAutoStyle()) 175 if err != nil { 176 log.Fatal(err) 177 } 178 mr.Description, _ = r.Render(mr.Description) 179 } 180 181 closingIssues, err := lab.ListIssuesClosedOnMerge(project, mr.IID) 182 if err != nil { 183 log.Fatal(err) 184 } 185 186 approvalConfig, err := lab.GetMRApprovalsConfiguration(project, mr.IID) 187 if err != nil { 188 log.Fatal(err) 189 } 190 191 for _, approvedby := range approvalConfig.ApprovedBy { 192 _tmpStringArray = append(_tmpStringArray, approvedby.User.Username) 193 } 194 if len(_tmpStringArray) > 0 { 195 approvedByUsers = strings.Join(_tmpStringArray, ", ") 196 _tmpStringArray = nil 197 } 198 199 // An argument could be made to separate these two fields into their own 200 // entries, however, at a high level they essentially the users that can 201 // approve the MR 202 for _, approvers := range approvalConfig.Approvers { 203 _tmpStringArray = append(_tmpStringArray, approvers.User.Username) 204 } 205 for _, suggestedApprovers := range approvalConfig.SuggestedApprovers { 206 _tmpStringArray = append(_tmpStringArray, suggestedApprovers.Username) 207 } 208 if len(_tmpStringArray) > 0 { 209 approvers = strings.Join(_tmpStringArray, ", ") 210 _tmpStringArray = nil 211 } 212 213 for _, approversGroups := range approvalConfig.ApproverGroups { 214 _tmpStringArray = append(_tmpStringArray, approversGroups.Group.Name) 215 } 216 if len(_tmpStringArray) > 0 { 217 approverGroups = strings.Join(_tmpStringArray, ", ") 218 _tmpStringArray = nil 219 } 220 221 for _, reviewerUsers := range mr.Reviewers { 222 _tmpStringArray = append(_tmpStringArray, reviewerUsers.Username) 223 } 224 if len(_tmpStringArray) > 0 { 225 reviewers = strings.Join(_tmpStringArray, ", ") 226 _tmpStringArray = nil 227 } 228 229 if mr.Subscribed { 230 subscribed = "Yes" 231 } 232 233 fmt.Printf( 234 heredoc.Doc(` 235 !%d %s 236 =================================== 237 %s 238 ----------------------------------- 239 Project: %s 240 Branches: %s->%s 241 Status: %s 242 Assignee: %s 243 Author: %s 244 Approved By: %s 245 Approvers: %s 246 Approval Groups: %s 247 Reviewers: %s 248 Milestone: %s 249 Labels: %s 250 Issues Closed by this MR: %s 251 Subscribed: %s 252 WebURL: %s 253 `), 254 mr.IID, mr.Title, mr.Description, project, mr.SourceBranch, 255 mr.TargetBranch, state, assignee, mr.Author.Username, 256 approvedByUsers, approvers, approverGroups, reviewers, milestone, labels, 257 strings.Trim(strings.Replace(fmt.Sprint(closingIssues), " ", ",", -1), "[]"), 258 subscribed, mr.WebURL, 259 ) 260 } 261 262 func init() { 263 mrShowCmd.Flags().BoolP("no-markdown", "M", false, "don't use markdown renderer to print the issue description") 264 mrShowCmd.Flags().BoolP("comments", "c", false, "show only comments for the merge request (does not work with --patch)") 265 mrShowCmd.Flags().BoolP("activities", "a", false, "show only activities for the merge request (does not work with --patch)") 266 mrShowCmd.Flags().BoolP("full", "f", false, "show both activities and comments for the merge request (does not work with --patch)") 267 mrShowCmd.Flags().StringP("since", "s", "", "show comments since specified date (format: 2020-08-21 14:57:46.808 +0000 UTC)") 268 mrShowCmd.Flags().BoolVarP(&mrShowPatch, "patch", "p", false, "show MR patches (does not work with --comments)") 269 mrShowCmd.Flags().BoolVarP(&mrShowPatchReverse, "reverse", "", false, "reverse order when showing MR patches (chronological instead of anti-chronological)") 270 mrShowCmd.Flags().BoolVarP(&mrShowNoColorDiff, "no-color-diff", "", false, "do not show color diffs in comments") 271 mrCmd.AddCommand(mrShowCmd) 272 carapace.Gen(mrShowCmd).PositionalCompletion( 273 action.Remotes(), 274 action.MergeRequests(mrList), 275 ) 276 }