Skip to content

Commit 262976b

Browse files
authored
Merge pull request cli#52 from github/pr-list
Add `pr list` command
2 parents fdee02e + b2c06ca commit 262976b

File tree

8 files changed

+387
-72
lines changed

8 files changed

+387
-72
lines changed

api/queries.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type PullRequestsPayload struct {
1414
type PullRequest struct {
1515
Number int
1616
Title string
17+
State string
1718
URL string
1819
HeadRefName string
1920
}
@@ -274,3 +275,96 @@ func PullRequestsForBranch(client *Client, ghRepo Repo, branch string) ([]PullRe
274275

275276
return prs, nil
276277
}
278+
279+
func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]PullRequest, error) {
280+
type response struct {
281+
Repository struct {
282+
PullRequests struct {
283+
Edges []struct {
284+
Node PullRequest
285+
}
286+
PageInfo struct {
287+
HasNextPage bool
288+
EndCursor string
289+
}
290+
}
291+
}
292+
}
293+
294+
query := `
295+
query(
296+
$owner: String!,
297+
$repo: String!,
298+
$limit: Int!,
299+
$endCursor: String,
300+
$baseBranch: String,
301+
$labels: [String!],
302+
$state: [PullRequestState!] = OPEN
303+
) {
304+
repository(owner: $owner, name: $repo) {
305+
pullRequests(
306+
states: $state,
307+
baseRefName: $baseBranch,
308+
labels: $labels,
309+
first: $limit,
310+
after: $endCursor,
311+
orderBy: {field: CREATED_AT, direction: DESC}
312+
) {
313+
edges {
314+
node {
315+
number
316+
title
317+
state
318+
url
319+
headRefName
320+
}
321+
}
322+
pageInfo {
323+
hasNextPage
324+
endCursor
325+
}
326+
}
327+
}
328+
}`
329+
330+
prs := []PullRequest{}
331+
pageLimit := min(limit, 100)
332+
variables := map[string]interface{}{}
333+
for name, val := range vars {
334+
variables[name] = val
335+
}
336+
337+
for {
338+
variables["limit"] = pageLimit
339+
var data response
340+
err := client.GraphQL(query, variables, &data)
341+
if err != nil {
342+
return nil, err
343+
}
344+
prData := data.Repository.PullRequests
345+
346+
for _, edge := range prData.Edges {
347+
prs = append(prs, edge.Node)
348+
if len(prs) == limit {
349+
goto done
350+
}
351+
}
352+
353+
if prData.PageInfo.HasNextPage {
354+
variables["endCursor"] = prData.PageInfo.EndCursor
355+
pageLimit = min(pageLimit, limit-len(prs))
356+
continue
357+
}
358+
done:
359+
break
360+
}
361+
362+
return prs, nil
363+
}
364+
365+
func min(a, b int) int {
366+
if a < b {
367+
return a
368+
}
369+
return b
370+
}

command/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,6 @@ func issueView(cmd *cobra.Command, args []string) error {
109109

110110
func printIssues(issues ...api.Issue) {
111111
for _, issue := range issues {
112-
fmt.Printf(" #%d %s\n", issue.Number, truncateTitle(issue.Title, 70))
112+
fmt.Printf(" #%d %s\n", issue.Number, truncate(70, issue.Title))
113113
}
114114
}

command/pr.go

Lines changed: 140 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,49 @@ package command
22

33
import (
44
"fmt"
5+
"os"
56
"strconv"
67

78
"github.com/github/gh-cli/api"
89
"github.com/github/gh-cli/utils"
910
"github.com/spf13/cobra"
11+
"golang.org/x/crypto/ssh/terminal"
1012
)
1113

1214
func init() {
1315
RootCmd.AddCommand(prCmd)
14-
prCmd.AddCommand(
15-
&cobra.Command{
16-
Use: "list",
17-
Short: "List pull requests",
18-
RunE: prList,
19-
},
20-
&cobra.Command{
21-
Use: "view [pr-number]",
22-
Short: "Open a pull request in the browser",
23-
RunE: prView,
24-
},
25-
)
16+
prCmd.AddCommand(prListCmd)
17+
prCmd.AddCommand(prStatusCmd)
18+
prCmd.AddCommand(prViewCmd)
19+
20+
prListCmd.Flags().IntP("limit", "L", 30, "maximum number of items to fetch")
21+
prListCmd.Flags().StringP("state", "s", "open", "filter by state")
22+
prListCmd.Flags().StringP("base", "b", "", "filter by base branch")
23+
prListCmd.Flags().StringArrayP("label", "l", nil, "filter by label")
2624
}
2725

2826
var prCmd = &cobra.Command{
2927
Use: "pr",
3028
Short: "Work with pull requests",
31-
Long: `This command allows you to
32-
work with pull requests.`,
33-
Args: cobra.MinimumNArgs(1),
34-
RunE: func(cmd *cobra.Command, args []string) error {
35-
return fmt.Errorf("%+v is not a valid PR command", args)
36-
},
29+
Long: `Helps you work with pull requests.`,
30+
}
31+
var prListCmd = &cobra.Command{
32+
Use: "list",
33+
Short: "List pull requests",
34+
RunE: prList,
35+
}
36+
var prStatusCmd = &cobra.Command{
37+
Use: "status",
38+
Short: "Show status of relevant pull requests",
39+
RunE: prStatus,
40+
}
41+
var prViewCmd = &cobra.Command{
42+
Use: "view [pr-number]",
43+
Short: "Open a pull request in the browser",
44+
RunE: prView,
3745
}
3846

39-
func prList(cmd *cobra.Command, args []string) error {
47+
func prStatus(cmd *cobra.Command, args []string) error {
4048
ctx := contextForCommand(cmd)
4149
apiClient, err := apiClientForContext(ctx)
4250
if err != nil {
@@ -89,6 +97,117 @@ func prList(cmd *cobra.Command, args []string) error {
8997
return nil
9098
}
9199

100+
func prList(cmd *cobra.Command, args []string) error {
101+
ctx := contextForCommand(cmd)
102+
apiClient, err := apiClientForContext(ctx)
103+
if err != nil {
104+
return err
105+
}
106+
107+
baseRepo, err := ctx.BaseRepo()
108+
if err != nil {
109+
return err
110+
}
111+
112+
limit, err := cmd.Flags().GetInt("limit")
113+
if err != nil {
114+
return err
115+
}
116+
state, err := cmd.Flags().GetString("state")
117+
if err != nil {
118+
return err
119+
}
120+
baseBranch, err := cmd.Flags().GetString("base")
121+
if err != nil {
122+
return err
123+
}
124+
labels, err := cmd.Flags().GetStringArray("label")
125+
if err != nil {
126+
return err
127+
}
128+
129+
var graphqlState []string
130+
switch state {
131+
case "open":
132+
graphqlState = []string{"OPEN"}
133+
case "closed":
134+
graphqlState = []string{"CLOSED"}
135+
case "merged":
136+
graphqlState = []string{"MERGED"}
137+
case "all":
138+
graphqlState = []string{"OPEN", "CLOSED", "MERGED"}
139+
default:
140+
return fmt.Errorf("invalid state: %s", state)
141+
}
142+
143+
params := map[string]interface{}{
144+
"owner": baseRepo.RepoOwner(),
145+
"repo": baseRepo.RepoName(),
146+
"state": graphqlState,
147+
}
148+
if len(labels) > 0 {
149+
params["labels"] = labels
150+
}
151+
if baseBranch != "" {
152+
params["baseBranch"] = baseBranch
153+
}
154+
155+
prs, err := api.PullRequestList(apiClient, params, limit)
156+
if err != nil {
157+
return err
158+
}
159+
160+
tty := false
161+
ttyWidth := 80
162+
out := cmd.OutOrStdout()
163+
if outFile, isFile := out.(*os.File); isFile {
164+
fd := int(outFile.Fd())
165+
tty = terminal.IsTerminal(fd)
166+
if w, _, err := terminal.GetSize(fd); err == nil {
167+
ttyWidth = w
168+
}
169+
}
170+
171+
numWidth := 0
172+
maxTitleWidth := 0
173+
for _, pr := range prs {
174+
numLen := len(strconv.Itoa(pr.Number)) + 1
175+
if numLen > numWidth {
176+
numWidth = numLen
177+
}
178+
if len(pr.Title) > maxTitleWidth {
179+
maxTitleWidth = len(pr.Title)
180+
}
181+
}
182+
183+
branchWidth := 40
184+
titleWidth := ttyWidth - branchWidth - 2 - numWidth - 2
185+
186+
if maxTitleWidth < titleWidth {
187+
branchWidth += titleWidth - maxTitleWidth
188+
titleWidth = maxTitleWidth
189+
}
190+
191+
for _, pr := range prs {
192+
if tty {
193+
prNum := fmt.Sprintf("% *s", numWidth, fmt.Sprintf("#%d", pr.Number))
194+
switch pr.State {
195+
case "OPEN":
196+
prNum = utils.Green(prNum)
197+
case "CLOSED":
198+
prNum = utils.Red(prNum)
199+
case "MERGED":
200+
prNum = utils.Magenta(prNum)
201+
}
202+
prBranch := utils.Cyan(truncate(branchWidth, pr.HeadRefName))
203+
fmt.Fprintf(out, "%s %-*s %s\n", prNum, titleWidth, truncate(titleWidth, pr.Title), prBranch)
204+
} else {
205+
fmt.Fprintf(out, "%d\t%s\t%s\n", pr.Number, pr.Title, pr.HeadRefName)
206+
}
207+
}
208+
return nil
209+
}
210+
92211
func prView(cmd *cobra.Command, args []string) error {
93212
ctx := contextForCommand(cmd)
94213
baseRepo, err := ctx.BaseRepo()
@@ -129,7 +248,7 @@ func prView(cmd *cobra.Command, args []string) error {
129248

130249
func printPrs(prs ...api.PullRequest) {
131250
for _, pr := range prs {
132-
fmt.Printf(" #%d %s %s\n", pr.Number, truncateTitle(pr.Title, 50), utils.Cyan("["+pr.HeadRefName+"]"))
251+
fmt.Printf(" #%d %s %s\n", pr.Number, truncate(50, pr.Title), utils.Cyan("["+pr.HeadRefName+"]"))
133252
}
134253
}
135254

@@ -141,7 +260,7 @@ func printMessage(s string) {
141260
fmt.Println(utils.Gray(s))
142261
}
143262

144-
func truncateTitle(title string, maxLength int) string {
263+
func truncate(maxLength int, title string) string {
145264
if len(title) > maxLength {
146265
return title[0:maxLength-3] + "..."
147266
}

0 commit comments

Comments
 (0)