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  }