github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/list/list.go (about) 1 package list 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 "strings" 8 9 "github.com/MakeNowJust/heredoc" 10 "github.com/cli/cli/api" 11 "github.com/cli/cli/internal/ghrepo" 12 "github.com/cli/cli/pkg/cmd/pr/shared" 13 "github.com/cli/cli/pkg/cmdutil" 14 "github.com/cli/cli/pkg/iostreams" 15 "github.com/cli/cli/pkg/text" 16 "github.com/cli/cli/utils" 17 "github.com/spf13/cobra" 18 ) 19 20 type browser interface { 21 Browse(string) error 22 } 23 24 type ListOptions struct { 25 HttpClient func() (*http.Client, error) 26 IO *iostreams.IOStreams 27 BaseRepo func() (ghrepo.Interface, error) 28 Browser browser 29 30 WebMode bool 31 LimitResults int 32 Exporter cmdutil.Exporter 33 34 State string 35 BaseBranch string 36 Labels []string 37 Author string 38 Assignee string 39 Search string 40 } 41 42 func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command { 43 opts := &ListOptions{ 44 IO: f.IOStreams, 45 HttpClient: f.HttpClient, 46 Browser: f.Browser, 47 } 48 49 cmd := &cobra.Command{ 50 Use: "list", 51 Short: "List and filter pull requests in this repository", 52 Example: heredoc.Doc(` 53 List PRs authored by you 54 $ gh pr list --author "@me" 55 56 List PRs assigned to you 57 $ gh pr list --assignee "@me" 58 59 List PRs by label, combining multiple labels with AND 60 $ gh pr list --label bug --label "priority 1" 61 62 List PRs using search syntax 63 $ gh pr list --search "status:success review:required" 64 65 Open the list of PRs in a web browser 66 $ gh pr list --web 67 `), 68 Args: cmdutil.NoArgsQuoteReminder, 69 RunE: func(cmd *cobra.Command, args []string) error { 70 // support `-R, --repo` override 71 opts.BaseRepo = f.BaseRepo 72 73 if opts.LimitResults < 1 { 74 return &cmdutil.FlagError{Err: fmt.Errorf("invalid value for --limit: %v", opts.LimitResults)} 75 } 76 77 if runF != nil { 78 return runF(opts) 79 } 80 return listRun(opts) 81 }, 82 } 83 84 cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open the browser to list the pull requests") 85 cmd.Flags().IntVarP(&opts.LimitResults, "limit", "L", 30, "Maximum number of items to fetch") 86 cmd.Flags().StringVarP(&opts.State, "state", "s", "open", "Filter by state: {open|closed|merged|all}") 87 _ = cmd.RegisterFlagCompletionFunc("state", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 88 return []string{"open", "closed", "merged", "all"}, cobra.ShellCompDirectiveNoFileComp 89 }) 90 cmd.Flags().StringVarP(&opts.BaseBranch, "base", "B", "", "Filter by base branch") 91 cmd.Flags().StringSliceVarP(&opts.Labels, "label", "l", nil, "Filter by labels") 92 cmd.Flags().StringVarP(&opts.Author, "author", "A", "", "Filter by author") 93 cmd.Flags().StringVarP(&opts.Assignee, "assignee", "a", "", "Filter by assignee") 94 cmd.Flags().StringVarP(&opts.Search, "search", "S", "", "Search pull requests with `query`") 95 cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.PullRequestFields) 96 97 return cmd 98 } 99 100 var defaultFields = []string{ 101 "number", 102 "title", 103 "state", 104 "url", 105 "headRefName", 106 "headRepositoryOwner", 107 "isCrossRepository", 108 "isDraft", 109 } 110 111 func listRun(opts *ListOptions) error { 112 httpClient, err := opts.HttpClient() 113 if err != nil { 114 return err 115 } 116 117 baseRepo, err := opts.BaseRepo() 118 if err != nil { 119 return err 120 } 121 122 prState := strings.ToLower(opts.State) 123 if prState == "open" && shared.QueryHasStateClause(opts.Search) { 124 prState = "" 125 } 126 127 filters := shared.FilterOptions{ 128 Entity: "pr", 129 State: prState, 130 Author: opts.Author, 131 Assignee: opts.Assignee, 132 Labels: opts.Labels, 133 BaseBranch: opts.BaseBranch, 134 Search: opts.Search, 135 Fields: defaultFields, 136 } 137 if opts.Exporter != nil { 138 filters.Fields = opts.Exporter.Fields() 139 } 140 141 if opts.WebMode { 142 prListURL := ghrepo.GenerateRepoURL(baseRepo, "pulls") 143 openURL, err := shared.ListURLWithQuery(prListURL, filters) 144 if err != nil { 145 return err 146 } 147 148 if opts.IO.IsStdoutTTY() { 149 fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) 150 } 151 return opts.Browser.Browse(openURL) 152 } 153 154 listResult, err := listPullRequests(httpClient, baseRepo, filters, opts.LimitResults) 155 if err != nil { 156 return err 157 } 158 159 err = opts.IO.StartPager() 160 if err != nil { 161 fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err) 162 } 163 defer opts.IO.StopPager() 164 165 if opts.Exporter != nil { 166 return opts.Exporter.Write(opts.IO, listResult.PullRequests) 167 } 168 169 if opts.IO.IsStdoutTTY() { 170 title := shared.ListHeader(ghrepo.FullName(baseRepo), "pull request", len(listResult.PullRequests), listResult.TotalCount, !filters.IsDefault()) 171 fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title) 172 } 173 174 cs := opts.IO.ColorScheme() 175 table := utils.NewTablePrinter(opts.IO) 176 for _, pr := range listResult.PullRequests { 177 prNum := strconv.Itoa(pr.Number) 178 if table.IsTTY() { 179 prNum = "#" + prNum 180 } 181 table.AddField(prNum, nil, cs.ColorFromString(shared.ColorForPR(pr))) 182 table.AddField(text.ReplaceExcessiveWhitespace(pr.Title), nil, nil) 183 table.AddField(pr.HeadLabel(), nil, cs.Cyan) 184 if !table.IsTTY() { 185 table.AddField(prStateWithDraft(&pr), nil, nil) 186 } 187 table.EndRow() 188 } 189 err = table.Render() 190 if err != nil { 191 return err 192 } 193 194 return nil 195 } 196 197 func prStateWithDraft(pr *api.PullRequest) string { 198 if pr.IsDraft && pr.State == "OPEN" { 199 return "DRAFT" 200 } 201 202 return pr.State 203 }