Merge remote-tracking branch 'origin/master' into pr-count
This commit is contained in:
commit
f51669e228
27 changed files with 552 additions and 159 deletions
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
|
|
@ -23,5 +23,5 @@ jobs:
|
|||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
go test ./...
|
||||
go test -race ./...
|
||||
go build -v ./cmd/gh
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -1,4 +1,4 @@
|
|||
# gh - The GitHub CLI tool
|
||||
# GitHub CLI
|
||||
|
||||
`gh` is GitHub on the command line, and it's now available in beta. It brings pull requests, issues, and other GitHub concepts to
|
||||
the terminal next to where you are already working with `git` and your code.
|
||||
|
|
@ -24,15 +24,16 @@ And if you spot bugs or have features that you'd really like to see in `gh`, ple
|
|||
- `gh repo [view, create, clone, fork]`
|
||||
- `gh help`
|
||||
|
||||
Check out the [docs][] for more information.
|
||||
## Documentation
|
||||
|
||||
Read the [official docs](https://cli.github.com/manual/) for more information.
|
||||
|
||||
## Comparison with hub
|
||||
|
||||
For many years, [hub][] was the unofficial GitHub CLI tool. `gh` is a new project for us to explore
|
||||
what an official GitHub CLI tool can look like with a fundamentally different design. While both
|
||||
tools bring GitHub to the terminal, `hub` behaves as a proxy to `git` and `gh` is a standalone
|
||||
tool.
|
||||
tool. Check out our [more detailed explanation](/docs/gh-vs-hub.md) to learn more.
|
||||
|
||||
|
||||
## Installation and Upgrading
|
||||
|
|
@ -83,7 +84,14 @@ Install and upgrade:
|
|||
1. Download the `.deb` file from the [releases page][]
|
||||
2. `sudo apt install git && sudo dpkg -i gh_*_linux_amd64.deb` install the downloaded file
|
||||
|
||||
### Fedora/Centos Linux
|
||||
### Fedora Linux
|
||||
|
||||
Install and upgrade:
|
||||
|
||||
1. Download the `.rpm` file from the [releases page][]
|
||||
2. `sudo dnf install gh_*_linux_amd64.rpm` install the downloaded file
|
||||
|
||||
### Centos Linux
|
||||
|
||||
Install and upgrade:
|
||||
|
||||
|
|
@ -109,7 +117,7 @@ $ yay -S github-cli
|
|||
|
||||
Install a prebuilt binary from the [releases page][]
|
||||
|
||||
### [Build from source](/source.md)
|
||||
### [Build from source](/docs/source.md)
|
||||
|
||||
[docs]: https://cli.github.com/manual
|
||||
[scoop]: https://scoop.sh
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
// FakeHTTP provides a mechanism by which to stub HTTP responses through
|
||||
type FakeHTTP struct {
|
||||
// Requests stores references to sequental requests that RoundTrip has received
|
||||
// Requests stores references to sequential requests that RoundTrip has received
|
||||
Requests []*http.Request
|
||||
count int
|
||||
responseStubs []*http.Response
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ func IssueStatus(client *Client, repo ghrepo.Interface, currentUsername string)
|
|||
return &payload, nil
|
||||
}
|
||||
|
||||
func IssueList(client *Client, repo ghrepo.Interface, state string, labels []string, assigneeString string, limit int) (*IssuesAndTotalCount, error) {
|
||||
func IssueList(client *Client, repo ghrepo.Interface, state string, labels []string, assigneeString string, limit int, authorString string) (*IssuesAndTotalCount, error) {
|
||||
var states []string
|
||||
switch state {
|
||||
case "open", "":
|
||||
|
|
@ -185,10 +185,10 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str
|
|||
}
|
||||
|
||||
query := fragments + `
|
||||
query($owner: String!, $repo: String!, $limit: Int, $endCursor: String, $states: [IssueState!] = OPEN, $labels: [String!], $assignee: String) {
|
||||
query($owner: String!, $repo: String!, $limit: Int, $endCursor: String, $states: [IssueState!] = OPEN, $labels: [String!], $assignee: String, $author: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
hasIssuesEnabled
|
||||
issues(first: $limit, after: $endCursor, orderBy: {field: CREATED_AT, direction: DESC}, states: $states, labels: $labels, filterBy: {assignee: $assignee}) {
|
||||
issues(first: $limit, after: $endCursor, orderBy: {field: CREATED_AT, direction: DESC}, states: $states, labels: $labels, filterBy: {assignee: $assignee, createdBy: $author}) {
|
||||
totalCount
|
||||
nodes {
|
||||
...issue
|
||||
|
|
@ -213,6 +213,9 @@ func IssueList(client *Client, repo ghrepo.Interface, state string, labels []str
|
|||
if assigneeString != "" {
|
||||
variables["assignee"] = assigneeString
|
||||
}
|
||||
if authorString != "" {
|
||||
variables["author"] = authorString
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Repository struct {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func TestIssueList(t *testing.T) {
|
|||
} } }
|
||||
`))
|
||||
|
||||
_, err := IssueList(client, ghrepo.FromFullName("OWNER/REPO"), "open", []string{}, "", 251)
|
||||
_, err := IssueList(client, ghrepo.FromFullName("OWNER/REPO"), "open", []string{}, "", 251, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
type PullRequestsPayload struct {
|
||||
ViewerCreated PullRequestAndTotalCount
|
||||
ReviewRequested PullRequestAndTotalCount
|
||||
CurrentPRs []PullRequest
|
||||
CurrentPR *PullRequest
|
||||
}
|
||||
|
||||
type PullRequestAndTotalCount struct {
|
||||
|
|
@ -262,13 +262,12 @@ func PullRequests(client *Client, repo ghrepo.Interface, currentPRNumber int, cu
|
|||
reviewRequested = append(reviewRequested, edge.Node)
|
||||
}
|
||||
|
||||
var currentPRs []PullRequest
|
||||
if resp.Repository.PullRequest != nil {
|
||||
currentPRs = append(currentPRs, *resp.Repository.PullRequest)
|
||||
} else {
|
||||
var currentPR = resp.Repository.PullRequest
|
||||
if currentPR == nil {
|
||||
for _, edge := range resp.Repository.PullRequests.Edges {
|
||||
if edge.Node.HeadLabel() == currentPRHeadRef {
|
||||
currentPRs = append(currentPRs, edge.Node)
|
||||
currentPR = &edge.Node
|
||||
break // Take the most recent PR for the current branch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -282,7 +281,7 @@ func PullRequests(client *Client, repo ghrepo.Interface, currentPRNumber int, cu
|
|||
PullRequests: reviewRequested,
|
||||
TotalCount: resp.ReviewRequested.TotalCount,
|
||||
},
|
||||
CurrentPRs: currentPRs,
|
||||
CurrentPR: currentPR,
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
|
|
@ -507,6 +506,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) (*P
|
|||
}
|
||||
}`
|
||||
|
||||
var check = make(map[int]struct{})
|
||||
var prs []PullRequest
|
||||
pageLimit := min(limit, 100)
|
||||
variables := map[string]interface{}{}
|
||||
|
|
@ -583,7 +583,12 @@ loop:
|
|||
}
|
||||
|
||||
for _, edge := range prData.Edges {
|
||||
if _, exists := check[edge.Node.Number]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
prs = append(prs, edge.Node)
|
||||
check[edge.Node.Number] = struct{}{}
|
||||
if len(prs) == limit {
|
||||
break loop
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ func ForkRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
|
|||
type RepoCreateInput struct {
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
HomepageURL string `json:"homepageUrl,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
OwnerID string `json:"ownerId,omitempty"`
|
||||
|
|
|
|||
45
api/queries_repo_test.go
Normal file
45
api/queries_repo_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_RepoCreate(t *testing.T) {
|
||||
http := &FakeHTTP{}
|
||||
client := NewClient(ReplaceTripper(http))
|
||||
|
||||
http.StubResponse(200, bytes.NewBufferString(`{}`))
|
||||
|
||||
input := RepoCreateInput{
|
||||
Description: "roasted chesnuts",
|
||||
HomepageURL: "http://example.com",
|
||||
}
|
||||
|
||||
_, err := RepoCreate(client, input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(http.Requests) != 1 {
|
||||
t.Fatalf("expected 1 HTTP request, seen %d", len(http.Requests))
|
||||
}
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[0].Body)
|
||||
json.Unmarshal(bodyBytes, &reqBody)
|
||||
if description := reqBody.Variables.Input["description"].(string); description != "roasted chesnuts" {
|
||||
t.Errorf("expected description to be %q, got %q", "roasted chesnuts", description)
|
||||
}
|
||||
if homepage := reqBody.Variables.Input["homepageUrl"].(string); homepage != "http://example.com" {
|
||||
t.Errorf("expected homepageUrl to be %q, got %q", "http://example.com", homepage)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,21 +9,21 @@ import (
|
|||
|
||||
func init() {
|
||||
RootCmd.AddCommand(completionCmd)
|
||||
completionCmd.Flags().StringP("shell", "s", "bash", "The type of shell")
|
||||
completionCmd.Flags().StringP("shell", "s", "bash", "Shell type: {bash|zsh|fish|powershell}")
|
||||
}
|
||||
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion",
|
||||
Hidden: true,
|
||||
Short: "Generates completion scripts",
|
||||
Long: `To enable completion in your shell, run:
|
||||
Use: "completion",
|
||||
Short: "Generate shell completion scripts",
|
||||
Long: `Generate shell completion scripts for GitHub CLI commands.
|
||||
|
||||
eval "$(gh completion)"
|
||||
For example, for bash you could add this to your '~/.bash_profile':
|
||||
|
||||
You can add that to your '~/.bash_profile' to enable completion whenever you
|
||||
start a new shell.
|
||||
eval "$(gh completion)"
|
||||
|
||||
When installing with Homebrew, see https://docs.brew.sh/Shell-Completion
|
||||
When installing GitHub CLI through a package manager, however, it's possible that
|
||||
no additional shell configuration is necessary to gain completion support. For
|
||||
Homebrew, see <https://docs.brew.sh/Shell-Completion>
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
shellType, err := cmd.Flags().GetString("shell")
|
||||
|
|
@ -36,6 +36,8 @@ When installing with Homebrew, see https://docs.brew.sh/Shell-Completion
|
|||
return RootCmd.GenBashCompletion(cmd.OutOrStdout())
|
||||
case "zsh":
|
||||
return RootCmd.GenZshCompletion(cmd.OutOrStdout())
|
||||
case "powershell":
|
||||
return RootCmd.GenPowerShellCompletion(cmd.OutOrStdout())
|
||||
case "fish":
|
||||
return cobrafish.GenCompletion(RootCmd, cmd.OutOrStdout())
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -38,6 +38,17 @@ func TestCompletion_fish(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCompletion_powerShell(t *testing.T) {
|
||||
output, err := RunCommand(completionCmd, `completion -s powershell`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(output.String(), "Register-ArgumentCompleter") {
|
||||
t.Errorf("problem in fish completion:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletion_unsupported(t *testing.T) {
|
||||
_, err := RunCommand(completionCmd, `completion -s csh`)
|
||||
if err == nil || err.Error() != `unsupported shell type "csh"` {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
func init() {
|
||||
RootCmd.AddCommand(issueCmd)
|
||||
issueCmd.AddCommand(issueStatusCmd)
|
||||
issueCmd.AddCommand(issueViewCmd)
|
||||
|
||||
issueCmd.AddCommand(issueCreateCmd)
|
||||
issueCreateCmd.Flags().StringP("title", "t", "",
|
||||
|
|
@ -37,7 +36,9 @@ func init() {
|
|||
issueListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label")
|
||||
issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: {open|closed|all}")
|
||||
issueListCmd.Flags().IntP("limit", "L", 30, "Maximum number of issues to fetch")
|
||||
issueListCmd.Flags().StringP("author", "A", "", "Filter by author")
|
||||
|
||||
issueCmd.AddCommand(issueViewCmd)
|
||||
issueViewCmd.Flags().BoolP("preview", "p", false, "Display preview of issue content")
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +67,7 @@ var issueStatusCmd = &cobra.Command{
|
|||
RunE: issueStatus,
|
||||
}
|
||||
var issueViewCmd = &cobra.Command{
|
||||
Use: "view {<number> | <url> | <branch>}",
|
||||
Use: "view {<number> | <url>}",
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return FlagError{errors.New("issue number or URL required as argument")}
|
||||
|
|
@ -109,7 +110,12 @@ func issueList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
listResult, err := api.IssueList(apiClient, baseRepo, state, labels, assignee, limit)
|
||||
author, err := cmd.Flags().GetString("author")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listResult, err := api.IssueList(apiClient, baseRepo, state, labels, assignee, limit, author)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -117,7 +123,7 @@ func issueList(cmd *cobra.Command, args []string) error {
|
|||
hasFilters := false
|
||||
cmd.Flags().Visit(func(f *pflag.Flag) {
|
||||
switch f.Name {
|
||||
case "state", "label", "assignee":
|
||||
case "state", "label", "assignee", "author":
|
||||
hasFilters = true
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ func TestIssueList_withFlags(t *testing.T) {
|
|||
} } }
|
||||
`))
|
||||
|
||||
output, err := RunCommand(issueListCmd, "issue list -a probablyCher -l web,bug -s open")
|
||||
output, err := RunCommand(issueListCmd, "issue list -a probablyCher -l web,bug -s open -A foo")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `issue list`: %v", err)
|
||||
}
|
||||
|
|
@ -158,6 +158,7 @@ No issues match your search in OWNER/REPO
|
|||
Assignee string
|
||||
Labels []string
|
||||
States []string
|
||||
Author string
|
||||
}
|
||||
}{}
|
||||
json.Unmarshal(bodyBytes, &reqBody)
|
||||
|
|
@ -165,6 +166,7 @@ No issues match your search in OWNER/REPO
|
|||
eq(t, reqBody.Variables.Assignee, "probablyCher")
|
||||
eq(t, reqBody.Variables.Labels, []string{"web", "bug"})
|
||||
eq(t, reqBody.Variables.States, []string{"OPEN"})
|
||||
eq(t, reqBody.Variables.Author, "foo")
|
||||
}
|
||||
|
||||
func TestIssueList_nullAssigneeLabels(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ func prStatus(cmd *cobra.Command, args []string) error {
|
|||
fmt.Fprintln(out, "")
|
||||
|
||||
printHeader(out, "Current branch")
|
||||
if prPayload.CurrentPRs != nil {
|
||||
printPrs(out, 0, prPayload.CurrentPRs...)
|
||||
if prPayload.CurrentPR != nil {
|
||||
printPrs(out, 0, *prPayload.CurrentPR)
|
||||
} else {
|
||||
message := fmt.Sprintf(" There is no pull request associated with %s", utils.Cyan("["+currentPRHeadRef+"]"))
|
||||
printMessage(out, message)
|
||||
|
|
@ -386,45 +386,51 @@ func printPrs(w io.Writer, totalCount int, prs ...api.PullRequest) {
|
|||
for _, pr := range prs {
|
||||
prNumber := fmt.Sprintf("#%d", pr.Number)
|
||||
|
||||
prNumberColorFunc := utils.Green
|
||||
prStateColorFunc := utils.Green
|
||||
if pr.IsDraft {
|
||||
prNumberColorFunc = utils.Gray
|
||||
prStateColorFunc = utils.Gray
|
||||
} else if pr.State == "MERGED" {
|
||||
prNumberColorFunc = utils.Magenta
|
||||
prStateColorFunc = utils.Magenta
|
||||
} else if pr.State == "CLOSED" {
|
||||
prNumberColorFunc = utils.Red
|
||||
prStateColorFunc = utils.Red
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, " %s %s %s", prNumberColorFunc(prNumber), text.Truncate(50, replaceExcessiveWhitespace(pr.Title)), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
fmt.Fprintf(w, " %s %s %s", prStateColorFunc(prNumber), text.Truncate(50, replaceExcessiveWhitespace(pr.Title)), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
|
||||
checks := pr.ChecksStatus()
|
||||
reviews := pr.ReviewStatus()
|
||||
if checks.Total > 0 || reviews.ChangesRequested || reviews.Approved {
|
||||
fmt.Fprintf(w, "\n ")
|
||||
}
|
||||
|
||||
if checks.Total > 0 {
|
||||
var summary string
|
||||
if checks.Failing > 0 {
|
||||
if checks.Failing == checks.Total {
|
||||
summary = utils.Red("All checks failing")
|
||||
} else {
|
||||
summary = utils.Red(fmt.Sprintf("%d/%d checks failing", checks.Failing, checks.Total))
|
||||
}
|
||||
} else if checks.Pending > 0 {
|
||||
summary = utils.Yellow("Checks pending")
|
||||
} else if checks.Passing == checks.Total {
|
||||
summary = utils.Green("Checks passing")
|
||||
if pr.State == "OPEN" {
|
||||
if checks.Total > 0 || reviews.ChangesRequested || reviews.Approved {
|
||||
fmt.Fprintf(w, "\n ")
|
||||
}
|
||||
fmt.Fprintf(w, " - %s", summary)
|
||||
}
|
||||
|
||||
if reviews.ChangesRequested {
|
||||
fmt.Fprintf(w, " - %s", utils.Red("Changes requested"))
|
||||
} else if reviews.ReviewRequired {
|
||||
fmt.Fprintf(w, " - %s", utils.Yellow("Review required"))
|
||||
} else if reviews.Approved {
|
||||
fmt.Fprintf(w, " - %s", utils.Green("Approved"))
|
||||
if checks.Total > 0 {
|
||||
var summary string
|
||||
if checks.Failing > 0 {
|
||||
if checks.Failing == checks.Total {
|
||||
summary = utils.Red("All checks failing")
|
||||
} else {
|
||||
summary = utils.Red(fmt.Sprintf("%d/%d checks failing", checks.Failing, checks.Total))
|
||||
}
|
||||
} else if checks.Pending > 0 {
|
||||
summary = utils.Yellow("Checks pending")
|
||||
} else if checks.Passing == checks.Total {
|
||||
summary = utils.Green("Checks passing")
|
||||
}
|
||||
fmt.Fprintf(w, " - %s", summary)
|
||||
}
|
||||
|
||||
if reviews.ChangesRequested {
|
||||
fmt.Fprintf(w, " - %s", utils.Red("Changes requested"))
|
||||
} else if reviews.ReviewRequired {
|
||||
fmt.Fprintf(w, " - %s", utils.Yellow("Review required"))
|
||||
} else if reviews.Approved {
|
||||
fmt.Fprintf(w, " - %s", utils.Green("Approved"))
|
||||
}
|
||||
} else {
|
||||
s := strings.Title(strings.ToLower(pr.State))
|
||||
fmt.Fprintf(w, " - %s", prStateColorFunc(s))
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\n")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
|
@ -41,6 +42,7 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("could not determine the current branch: %w", err)
|
||||
}
|
||||
headRepo, headRepoErr := repoContext.HeadRepo()
|
||||
|
||||
baseBranch, err := cmd.Flags().GetString("base")
|
||||
if err != nil {
|
||||
|
|
@ -49,70 +51,13 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
if baseBranch == "" {
|
||||
baseBranch = baseRepo.DefaultBranchRef.Name
|
||||
}
|
||||
|
||||
didForkRepo := false
|
||||
var headRemote *context.Remote
|
||||
headRepo, err := repoContext.HeadRepo()
|
||||
if err != nil {
|
||||
if baseRepo.IsPrivate {
|
||||
return fmt.Errorf("cannot write to private repository '%s'", ghrepo.FullName(baseRepo))
|
||||
}
|
||||
headRepo, err = api.ForkRepo(client, baseRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error forking repo: %w", err)
|
||||
}
|
||||
didForkRepo = true
|
||||
// TODO: support non-HTTPS git remote URLs
|
||||
baseRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(baseRepo))
|
||||
headRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(headRepo))
|
||||
// TODO: figure out what to name the new git remote
|
||||
gitRemote, err := git.AddRemote("fork", baseRepoURL, headRepoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding remote: %w", err)
|
||||
}
|
||||
headRemote = &context.Remote{
|
||||
Remote: gitRemote,
|
||||
Owner: headRepo.RepoOwner(),
|
||||
Repo: headRepo.RepoName(),
|
||||
}
|
||||
}
|
||||
|
||||
if headBranch == baseBranch && ghrepo.IsSame(baseRepo, headRepo) {
|
||||
if headBranch == baseBranch && headRepo != nil && ghrepo.IsSame(baseRepo, headRepo) {
|
||||
return fmt.Errorf("must be on a branch named differently than %q", baseBranch)
|
||||
}
|
||||
|
||||
if headRemote == nil {
|
||||
headRemote, err = repoContext.RemoteForRepo(headRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git remote not found for head repository: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ucc, err := git.UncommittedChangeCount(); err == nil && ucc > 0 {
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: %s\n", utils.Pluralize(ucc, "uncommitted change"))
|
||||
}
|
||||
pushTries := 0
|
||||
maxPushTries := 3
|
||||
for {
|
||||
// TODO: respect existing upstream configuration of the current branch
|
||||
if err := git.Push(headRemote.Name, fmt.Sprintf("HEAD:%s", headBranch)); err != nil {
|
||||
if didForkRepo && pushTries < maxPushTries {
|
||||
pushTries++
|
||||
// first wait 2 seconds after forking, then 4s, then 6s
|
||||
waitSeconds := 2 * pushTries
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
|
||||
time.Sleep(time.Duration(waitSeconds) * time.Second)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
headBranchLabel := headBranch
|
||||
if !ghrepo.IsSame(baseRepo, headRepo) {
|
||||
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
|
||||
}
|
||||
|
||||
title, err := cmd.Flags().GetString("title")
|
||||
if err != nil {
|
||||
|
|
@ -127,22 +72,19 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("could not parse web: %q", err)
|
||||
}
|
||||
if isWeb {
|
||||
compareURL := generateCompareURL(baseRepo, baseBranch, headBranchLabel, title, body)
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", displayURL(compareURL))
|
||||
return utils.OpenInBrowser(compareURL)
|
||||
}
|
||||
|
||||
fmt.Fprintf(colorableErr(cmd), "\nCreating pull request for %s into %s in %s\n\n",
|
||||
utils.Cyan(headBranchLabel),
|
||||
utils.Cyan(baseBranch),
|
||||
ghrepo.FullName(baseRepo))
|
||||
|
||||
action := SubmitAction
|
||||
if isWeb {
|
||||
action = PreviewAction
|
||||
} else {
|
||||
fmt.Fprintf(colorableErr(cmd), "\nCreating pull request for %s into %s in %s\n\n",
|
||||
utils.Cyan(headBranch),
|
||||
utils.Cyan(baseBranch),
|
||||
ghrepo.FullName(baseRepo))
|
||||
}
|
||||
|
||||
interactive := title == "" || body == ""
|
||||
|
||||
if interactive {
|
||||
// TODO: only drop into interactive mode if stdin & stdout are a tty
|
||||
if !isWeb && (title == "" || body == "") {
|
||||
var templateFiles []string
|
||||
if rootDir, err := git.ToplevelDir(); err == nil {
|
||||
// TODO: figure out how to stub this in tests
|
||||
|
|
@ -169,27 +111,81 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if action == SubmitAction && title == "" {
|
||||
return errors.New("pull request title must not be blank")
|
||||
}
|
||||
|
||||
isDraft, err := cmd.Flags().GetBool("draft")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse draft: %w", err)
|
||||
}
|
||||
if isDraft && isWeb {
|
||||
return errors.New("the --draft flag is not supported with --web")
|
||||
}
|
||||
|
||||
didForkRepo := false
|
||||
var headRemote *context.Remote
|
||||
if headRepoErr != nil {
|
||||
if baseRepo.IsPrivate {
|
||||
return fmt.Errorf("cannot fork private repository '%s'", ghrepo.FullName(baseRepo))
|
||||
}
|
||||
headRepo, err = api.ForkRepo(client, baseRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error forking repo: %w", err)
|
||||
}
|
||||
didForkRepo = true
|
||||
// TODO: support non-HTTPS git remote URLs
|
||||
baseRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(baseRepo))
|
||||
headRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(headRepo))
|
||||
// TODO: figure out what to name the new git remote
|
||||
gitRemote, err := git.AddRemote("fork", baseRepoURL, headRepoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding remote: %w", err)
|
||||
}
|
||||
headRemote = &context.Remote{
|
||||
Remote: gitRemote,
|
||||
Owner: headRepo.RepoOwner(),
|
||||
Repo: headRepo.RepoName(),
|
||||
}
|
||||
}
|
||||
|
||||
headBranchLabel := headBranch
|
||||
if !ghrepo.IsSame(baseRepo, headRepo) {
|
||||
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
|
||||
}
|
||||
|
||||
if headRemote == nil {
|
||||
headRemote, err = repoContext.RemoteForRepo(headRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git remote not found for head repository: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
pushTries := 0
|
||||
maxPushTries := 3
|
||||
for {
|
||||
// TODO: respect existing upstream configuration of the current branch
|
||||
if err := git.Push(headRemote.Name, fmt.Sprintf("HEAD:%s", headBranch)); err != nil {
|
||||
if didForkRepo && pushTries < maxPushTries {
|
||||
pushTries++
|
||||
// first wait 2 seconds after forking, then 4s, then 6s
|
||||
waitSeconds := 2 * pushTries
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
|
||||
time.Sleep(time.Duration(waitSeconds) * time.Second)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if action == SubmitAction {
|
||||
if title == "" {
|
||||
return fmt.Errorf("pull request title must not be blank")
|
||||
}
|
||||
|
||||
headRefName := headBranch
|
||||
if !ghrepo.IsSame(headRemote, baseRepo) {
|
||||
headRefName = fmt.Sprintf("%s:%s", headRemote.RepoOwner(), headBranch)
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"title": title,
|
||||
"body": body,
|
||||
"draft": isDraft,
|
||||
"baseRefName": baseBranch,
|
||||
"headRefName": headRefName,
|
||||
"headRefName": headBranchLabel,
|
||||
}
|
||||
|
||||
pr, err := api.CreatePullRequest(client, baseRepo, params)
|
||||
|
|
@ -208,7 +204,6 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func generateCompareURL(r ghrepo.Interface, base, head, title, body string) string {
|
||||
|
|
|
|||
|
|
@ -155,6 +155,65 @@ func TestPRStatus_reviewsAndChecks(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPRStatus_closedMerged(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "blueberries")
|
||||
http := initFakeHTTP()
|
||||
http.StubRepoResponse("OWNER", "REPO")
|
||||
|
||||
jsonFile, _ := os.Open("../test/fixtures/prStatusClosedMerged.json")
|
||||
defer jsonFile.Close()
|
||||
http.StubResponse(200, jsonFile)
|
||||
|
||||
output, err := RunCommand(prStatusCmd, "pr status")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `pr status`: %v", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"- Checks passing - Changes requested",
|
||||
"- Closed",
|
||||
"- Merged",
|
||||
}
|
||||
|
||||
for _, line := range expected {
|
||||
if !strings.Contains(output.String(), line) {
|
||||
t.Errorf("output did not contain %q: %q", line, output.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRStatus_currentBranch_showTheMostRecentPR(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "blueberries")
|
||||
http := initFakeHTTP()
|
||||
http.StubRepoResponse("OWNER", "REPO")
|
||||
|
||||
jsonFile, _ := os.Open("../test/fixtures/prStatusCurrentBranch.json")
|
||||
defer jsonFile.Close()
|
||||
http.StubResponse(200, jsonFile)
|
||||
|
||||
output, err := RunCommand(prStatusCmd, "pr status")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `pr status`: %v", err)
|
||||
}
|
||||
|
||||
expectedLine := regexp.MustCompile(`#10 Blueberries are certainly a good fruit \[blueberries\]`)
|
||||
if !expectedLine.MatchString(output.String()) {
|
||||
t.Errorf("output did not match regexp /%s/\n> output\n%s\n", expectedLine, output)
|
||||
return
|
||||
}
|
||||
|
||||
unexpectedLines := []*regexp.Regexp{
|
||||
regexp.MustCompile(`#9 Blueberries are a good fruit \[blueberries\] - Merged`),
|
||||
regexp.MustCompile(`#8 Blueberries are probably a good fruit \[blueberries\] - Closed`),
|
||||
}
|
||||
for _, r := range unexpectedLines {
|
||||
if r.MatchString(output.String()) {
|
||||
t.Errorf("output unexpectedly match regexp /%s/\n> output\n%s\n", r, output)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRStatus_blankSlate(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "blueberries")
|
||||
http := initFakeHTTP()
|
||||
|
|
@ -243,6 +302,26 @@ No pull requests match your search in OWNER/REPO
|
|||
eq(t, reqBody.Variables.Labels, []string{"one", "two", "three"})
|
||||
}
|
||||
|
||||
func TestPRList_filteringRemoveDuplicate(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "master")
|
||||
http := initFakeHTTP()
|
||||
http.StubRepoResponse("OWNER", "REPO")
|
||||
|
||||
jsonFile, _ := os.Open("../test/fixtures/prListWithDuplicates.json")
|
||||
defer jsonFile.Close()
|
||||
http.StubResponse(200, jsonFile)
|
||||
|
||||
output, err := RunCommand(prListCmd, "pr list -l one,two")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), `32 New feature feature
|
||||
29 Fixed bad bug hubot:bug-fix
|
||||
28 Improve documentation docs
|
||||
`)
|
||||
}
|
||||
|
||||
func TestPRList_filteringClosed(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "master")
|
||||
http := initFakeHTTP()
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ func repoCreate(cmd *cobra.Command, args []string) error {
|
|||
OwnerID: orgName,
|
||||
TeamID: teamSlug,
|
||||
Description: description,
|
||||
Homepage: homepage,
|
||||
HomepageURL: homepage,
|
||||
HasIssuesEnabled: hasIssuesEnabled,
|
||||
HasWikiEnabled: hasWikiEnabled,
|
||||
}
|
||||
|
|
@ -386,23 +386,40 @@ var Confirm = func(prompt string, result *bool) error {
|
|||
|
||||
func repoView(cmd *cobra.Command, args []string) error {
|
||||
ctx := contextForCommand(cmd)
|
||||
|
||||
var openURL string
|
||||
var toView ghrepo.Interface
|
||||
if len(args) == 0 {
|
||||
baseRepo, err := determineBaseRepo(cmd, ctx)
|
||||
var err error
|
||||
toView, err = determineBaseRepo(cmd, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
openURL = fmt.Sprintf("https://github.com/%s", ghrepo.FullName(baseRepo))
|
||||
} else {
|
||||
repoArg := args[0]
|
||||
if isURL(repoArg) {
|
||||
openURL = repoArg
|
||||
parsedURL, err := url.Parse(repoArg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
}
|
||||
|
||||
toView, err = ghrepo.FromURL(parsedURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
}
|
||||
} else {
|
||||
openURL = fmt.Sprintf("https://github.com/%s", repoArg)
|
||||
toView = ghrepo.FromFullName(repoArg)
|
||||
}
|
||||
}
|
||||
|
||||
apiClient, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = api.GitHubRepo(apiClient, toView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
openURL := fmt.Sprintf("https://github.com/%s", ghrepo.FullName(toView))
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", displayURL(openURL))
|
||||
return utils.OpenInBrowser(openURL)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -579,10 +579,14 @@ func TestRepoCreate_orgWithTeam(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func TestRepoView(t *testing.T) {
|
||||
initBlankContext("OWNER/REPO", "master")
|
||||
http := initFakeHTTP()
|
||||
http.StubRepoResponse("OWNER", "REPO")
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ }
|
||||
`))
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
|
|
@ -612,7 +616,10 @@ func TestRepoView_ownerRepo(t *testing.T) {
|
|||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
initFakeHTTP()
|
||||
http := initFakeHTTP()
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ }
|
||||
`))
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
|
|
@ -642,8 +649,10 @@ func TestRepoView_fullURL(t *testing.T) {
|
|||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
initFakeHTTP()
|
||||
|
||||
http := initFakeHTTP()
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ }
|
||||
`))
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
|
||||
seenCmd = cmd
|
||||
|
|
|
|||
|
|
@ -134,9 +134,7 @@ var apiClientForContext = func(ctx context.Context) (*api.Client, error) {
|
|||
api.AddHeader("Authorization", fmt.Sprintf("token %s", token)),
|
||||
api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", Version)),
|
||||
// antiope-preview: Checks
|
||||
// shadow-cat-preview: Draft pull requests
|
||||
api.AddHeader("Accept", "application/vnd.github.antiope-preview+json, application/vnd.github.shadow-cat-preview"),
|
||||
api.AddHeader("GraphQL-Features", "pe_mobile"),
|
||||
api.AddHeader("Accept", "application/vnd.github.antiope-preview+json"),
|
||||
)
|
||||
|
||||
return api.NewClient(opts...), nil
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type Context interface {
|
|||
}
|
||||
|
||||
// cap the number of git remotes looked up, since the user might have an
|
||||
// unusally large number of git remotes
|
||||
// unusually large number of git remotes
|
||||
const maxRemotesForLookup = 5
|
||||
|
||||
func ResolveRemotesToRepos(remotes Remotes, client *api.Client, base string) (ResolvedRemotes, error) {
|
||||
|
|
|
|||
1
docs/README.md
Normal file
1
docs/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
This folder is used for documentation related to developing `gh`. Docs for `gh` installation and usage are available at [https://cli.github.com/manual](https://cli.github.com/manual).
|
||||
27
docs/gh-vs-hub.md
Normal file
27
docs/gh-vs-hub.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# GitHub CLI & `hub`
|
||||
|
||||
[GitHub CLI](https://cli.github.com/) (`gh`) was [announced in early 2020](https://github.blog/2020-02-12-supercharge-your-command-line-experience-github-cli-is-now-in-beta/) and provides a more seamless way to interact with your GitHub repositories from the command line. We also know that many people are interested in the very similar [`hub`](https://hub.github.com/) project, so we wanted to clarify some potential points of confusion.
|
||||
|
||||
## Why didn’t you just build `gh` on top of `hub`?
|
||||
|
||||
We wrestled with the decision of whether to continue building onto `hub` and adopt it as an official GitHub project. In weighing different possibilities, we decided to start fresh without the constraints of 10 years of design decisions that `hub` has baked in and without the assumption that `hub` can be safely aliased to `git`. We also wanted to be more opinionated and focused on GitHub workflows, and doing this with `hub` had the risk of alienating many `hub` users who love the existing tool and expected it to work in the way they were used to.
|
||||
|
||||
## What’s next for `hub`?
|
||||
|
||||
The GitHub CLI team is focused solely on building out the new tool, `gh`. We aren’t shutting down `hub` or doing anything to change it. It’s an open source project and will continue to exist as long as it’s maintained and keeps receiving contributions.
|
||||
|
||||
## What does it mean that GitHub CLI is official and `hub` is unofficial?
|
||||
|
||||
GitHub CLI is built and maintained by a team of people who work on the tool on behalf of GitHub. When there’s something wrong with it, people can reach out to GitHub support or create an issue in the issue tracker, where an employee at GitHub will respond.
|
||||
|
||||
`hub` is a project whose maintainer also happens to be a GitHub employee. He chooses to maintain `hub` in his spare time, as many of our employees do with open source projects.
|
||||
|
||||
## Should I use `gh` or `hub`?
|
||||
|
||||
We have no interest in forcing anyone to use GitHub CLI instead of `hub`. We think people should use whatever set of tools makes them happiest and most productive working with GitHub.
|
||||
|
||||
If you are set on using a tool that acts as a wrapper for Git itself, `hub` is likely a better choice than `gh`. `hub` currently covers a larger overall surface area of GitHub’s API v3, provides more scripting functionality, and is compatible with GitHub Enterprise (though these are all things that we intend to improve in GitHub CLI).
|
||||
|
||||
If you want a tool that’s more opinionated and intended to help simplify your GitHub workflows from the command line, we hope you’ll use `gh`. And since `gh` is maintained by a team at GitHub, we intend to be responsive to people’s concerns and needs and improve the tool based on how people are using it over time.
|
||||
|
||||
GitHub CLI is not intended to be an exact replacement for `hub` and likely never will be, but our hope is that the vast majority of GitHub users who use the CLI will find more and more value in using `gh` as we continue to improve it.
|
||||
50
test/fixtures/prListWithDuplicates.json
vendored
Normal file
50
test/fixtures/prListWithDuplicates.json
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"data": {
|
||||
"repository": {
|
||||
"pullRequests": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"number": 32,
|
||||
"title": "New feature",
|
||||
"url": "https://github.com/monalisa/hello/pull/32",
|
||||
"headRefName": "feature"
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"number": 32,
|
||||
"title": "New feature",
|
||||
"url": "https://github.com/monalisa/hello/pull/32",
|
||||
"headRefName": "feature"
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"number": 29,
|
||||
"title": "Fixed bad bug",
|
||||
"url": "https://github.com/monalisa/hello/pull/29",
|
||||
"headRefName": "bug-fix",
|
||||
"isCrossRepository": true,
|
||||
"headRepositoryOwner": {
|
||||
"login": "hubot"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"number": 28,
|
||||
"title": "Improve documentation",
|
||||
"url": "https://github.com/monalisa/hello/pull/28",
|
||||
"headRefName": "docs"
|
||||
}
|
||||
}
|
||||
],
|
||||
"pageInfo": {
|
||||
"hasNextPage": false,
|
||||
"endCursor": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
test/fixtures/prStatusChecks.json
vendored
3
test/fixtures/prStatusChecks.json
vendored
|
|
@ -13,6 +13,7 @@
|
|||
"node": {
|
||||
"number": 8,
|
||||
"title": "Strawberries are not actually berries",
|
||||
"state": "OPEN",
|
||||
"url": "https://github.com/cli/cli/pull/8",
|
||||
"headRefName": "strawberries",
|
||||
"reviewDecision": "CHANGES_REQUESTED",
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
"node": {
|
||||
"number": 7,
|
||||
"title": "Bananas are berries",
|
||||
"state": "OPEN",
|
||||
"url": "https://github.com/cli/cli/pull/7",
|
||||
"headRefName": "banananana",
|
||||
"reviewDecision": "APPROVED",
|
||||
|
|
@ -66,6 +68,7 @@
|
|||
"node": {
|
||||
"number": 6,
|
||||
"title": "Avocado is probably not a berry",
|
||||
"state": "OPEN",
|
||||
"url": "https://github.com/cli/cli/pull/6",
|
||||
"headRefName": "avo",
|
||||
"reviewDecision": "REVIEW_REQUIRED",
|
||||
|
|
|
|||
65
test/fixtures/prStatusClosedMerged.json
vendored
Normal file
65
test/fixtures/prStatusClosedMerged.json
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"data": {
|
||||
"repository": {
|
||||
"pullRequests": {
|
||||
"totalCount": 1,
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"number": 8,
|
||||
"title": "Blueberries are a good fruit",
|
||||
"state": "OPEN",
|
||||
"url": "https://github.com/cli/cli/pull/8",
|
||||
"headRefName": "blueberries",
|
||||
"reviewDecision": "CHANGES_REQUESTED",
|
||||
"commits": {
|
||||
"nodes": [
|
||||
{
|
||||
"commit": {
|
||||
"statusCheckRollup": {
|
||||
"contexts": {
|
||||
"nodes": [
|
||||
{
|
||||
"state": "SUCCESS"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"viewerCreated": {
|
||||
"totalCount": 1,
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"number": 10,
|
||||
"state": "CLOSED",
|
||||
"title": "Strawberries are not actually berries",
|
||||
"url": "https://github.com/cli/cli/pull/10",
|
||||
"headRefName": "strawberries"
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"number": 9,
|
||||
"state": "MERGED",
|
||||
"title": "Bananas are berries",
|
||||
"url": "https://github.com/cli/cli/pull/9",
|
||||
"headRefName": "banananana"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"reviewRequested": {
|
||||
"totalCount": 0,
|
||||
"edges": []
|
||||
}
|
||||
}
|
||||
}
|
||||
61
test/fixtures/prStatusCurrentBranch.json
vendored
Normal file
61
test/fixtures/prStatusCurrentBranch.json
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"data": {
|
||||
"repository": {
|
||||
"pullRequests": {
|
||||
"totalCount": 3,
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"number": 10,
|
||||
"title": "Blueberries are certainly a good fruit",
|
||||
"state": "OPEN",
|
||||
"url": "https://github.com/PARENT/REPO/pull/10",
|
||||
"headRefName": "blueberries",
|
||||
"isDraft": false,
|
||||
"headRepositoryOwner": {
|
||||
"login": "OWNER/REPO"
|
||||
},
|
||||
"isCrossRepository": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"number": 9,
|
||||
"title": "Blueberries are a good fruit",
|
||||
"state": "MERGED",
|
||||
"url": "https://github.com/PARENT/REPO/pull/9",
|
||||
"headRefName": "blueberries",
|
||||
"isDraft": false,
|
||||
"headRepositoryOwner": {
|
||||
"login": "OWNER/REPO"
|
||||
},
|
||||
"isCrossRepository": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"node": {
|
||||
"number": 8,
|
||||
"title": "Blueberries are probably a good fruit",
|
||||
"state": "CLOSED",
|
||||
"url": "https://github.com/PARENT/REPO/pull/8",
|
||||
"headRefName": "blueberries",
|
||||
"isDraft": false,
|
||||
"headRepositoryOwner": {
|
||||
"login": "OWNER/REPO"
|
||||
},
|
||||
"isCrossRepository": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"viewerCreated": {
|
||||
"totalCount": 0,
|
||||
"edges": []
|
||||
},
|
||||
"reviewRequested": {
|
||||
"totalCount": 0,
|
||||
"edges": []
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue