Skip to content

Commit

Permalink
Merge branch 'trunk' into readme-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ampinsk authored Sep 14, 2020
2 parents 14c4331 + a50b0db commit f43a65a
Show file tree
Hide file tree
Showing 82 changed files with 4,821 additions and 368 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
popd
- name: Run reprepro
env:
RELEASES: "focal stable"
RELEASES: "focal stable bionic trusty precise xenial"
run: |
mkdir -p upload
for release in $RELEASES; do
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ endif
GO_LDFLAGS := -X github.com/cli/cli/command.Version=$(GH_VERSION) $(GO_LDFLAGS)
GO_LDFLAGS := -X github.com/cli/cli/command.BuildDate=$(BUILD_DATE) $(GO_LDFLAGS)
ifdef GH_OAUTH_CLIENT_SECRET
GO_LDFLAGS := -X github.com/cli/cli/internal/config.oauthClientID=$(GH_OAUTH_CLIENT_ID) $(GO_LDFLAGS)
GO_LDFLAGS := -X github.com/cli/cli/internal/config.oauthClientSecret=$(GH_OAUTH_CLIENT_SECRET) $(GO_LDFLAGS)
GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientID=$(GH_OAUTH_CLIENT_ID) $(GO_LDFLAGS)
GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientSecret=$(GH_OAUTH_CLIENT_SECRET) $(GO_LDFLAGS)
endif

bin/gh: $(BUILD_FILES)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterpr
Read the [official docs][] for usage and more information.



## We want your feedback

We'd love to hear your feedback about `gh`. If you spot bugs or have features that you'd really like to see in `gh`, please check out the [contributing page][].
Expand Down
92 changes: 75 additions & 17 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ func NewClientFromHTTP(httpClient *http.Client) *Client {
func AddHeader(name, value string) ClientOption {
return func(tr http.RoundTripper) http.RoundTripper {
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
req.Header.Add(name, value)
if req.Header.Get(name) == "" {
req.Header.Add(name, value)
}
return tr.RoundTrip(req)
}}
}
Expand All @@ -55,11 +57,16 @@ func AddHeader(name, value string) ClientOption {
func AddHeaderFunc(name string, getValue func(*http.Request) (string, error)) ClientOption {
return func(tr http.RoundTripper) http.RoundTripper {
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
if req.Header.Get(name) != "" {
return tr.RoundTrip(req)
}
value, err := getValue(req)
if err != nil {
return nil, err
}
req.Header.Add(name, value)
if value != "" {
req.Header.Add(name, value)
}
return tr.RoundTrip(req)
}}
}
Expand All @@ -68,14 +75,15 @@ func AddHeaderFunc(name string, getValue func(*http.Request) (string, error)) Cl
// VerboseLog enables request/response logging within a RoundTripper
func VerboseLog(out io.Writer, logTraffic bool, colorize bool) ClientOption {
logger := &httpretty.Logger{
Time: true,
TLS: false,
Colors: colorize,
RequestHeader: logTraffic,
RequestBody: logTraffic,
ResponseHeader: logTraffic,
ResponseBody: logTraffic,
Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
Time: true,
TLS: false,
Colors: colorize,
RequestHeader: logTraffic,
RequestBody: logTraffic,
ResponseHeader: logTraffic,
ResponseBody: logTraffic,
Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
MaxResponseBody: 10000,
}
logger.SetOutput(out)
logger.SetBodyFilter(func(h http.Header) (skip bool, err error) {
Expand Down Expand Up @@ -190,7 +198,9 @@ type HTTPError struct {
}

func (err HTTPError) Error() string {
if err.Message != "" {
if msgs := strings.SplitN(err.Message, "\n", 2); len(msgs) > 1 {
return fmt.Sprintf("HTTP %d: %s (%s)\n%s", err.StatusCode, msgs[0], err.RequestURL, msgs[1])
} else if err.Message != "" {
return fmt.Sprintf("HTTP %d: %s (%s)", err.StatusCode, err.Message, err.RequestURL)
}
return fmt.Sprintf("HTTP %d (%s)", err.StatusCode, err.RequestURL)
Expand Down Expand Up @@ -222,7 +232,7 @@ func (c Client) HasMinimumScopes(hostname string) error {
}()

if res.StatusCode != 200 {
return handleHTTPError(res)
return HandleHTTPError(res)
}

hasScopes := strings.Split(res.Header.Get("X-Oauth-Scopes"), ",")
Expand Down Expand Up @@ -298,7 +308,7 @@ func (c Client) REST(hostname string, method string, p string, body io.Reader, d

success := resp.StatusCode >= 200 && resp.StatusCode < 300
if !success {
return handleHTTPError(resp)
return HandleHTTPError(resp)
}

if resp.StatusCode == http.StatusNoContent {
Expand All @@ -322,7 +332,7 @@ func handleResponse(resp *http.Response, data interface{}) error {
success := resp.StatusCode >= 200 && resp.StatusCode < 300

if !success {
return handleHTTPError(resp)
return HandleHTTPError(resp)
}

body, err := ioutil.ReadAll(resp.Body)
Expand All @@ -342,13 +352,18 @@ func handleResponse(resp *http.Response, data interface{}) error {
return nil
}

func handleHTTPError(resp *http.Response) error {
func HandleHTTPError(resp *http.Response) error {
httpError := HTTPError{
StatusCode: resp.StatusCode,
RequestURL: resp.Request.URL,
OAuthScopes: resp.Header.Get("X-Oauth-Scopes"),
}

if !jsonTypeRE.MatchString(resp.Header.Get("Content-Type")) {
httpError.Message = resp.Status
return httpError
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
httpError.Message = err.Error()
Expand All @@ -357,14 +372,57 @@ func handleHTTPError(resp *http.Response) error {

var parsedBody struct {
Message string `json:"message"`
Errors []json.RawMessage
}
if err := json.Unmarshal(body, &parsedBody); err != nil {
return httpError
}
if err := json.Unmarshal(body, &parsedBody); err == nil {
httpError.Message = parsedBody.Message

type errorObject struct {
Message string
Resource string
Field string
Code string
}

messages := []string{parsedBody.Message}
for _, raw := range parsedBody.Errors {
switch raw[0] {
case '"':
var errString string
_ = json.Unmarshal(raw, &errString)
messages = append(messages, errString)
case '{':
var errInfo errorObject
_ = json.Unmarshal(raw, &errInfo)
msg := errInfo.Message
if errInfo.Code != "custom" {
msg = fmt.Sprintf("%s.%s %s", errInfo.Resource, errInfo.Field, errorCodeToMessage(errInfo.Code))
}
if msg != "" {
messages = append(messages, msg)
}
}
}
httpError.Message = strings.Join(messages, "\n")

return httpError
}

func errorCodeToMessage(code string) string {
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors
switch code {
case "missing", "missing_field":
return "is missing"
case "invalid", "unprocessable":
return "is invalid"
case "already_exists":
return "already exists"
default:
return code
}
}

var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`)

func inspectableMIMEType(t string) bool {
Expand Down
17 changes: 13 additions & 4 deletions api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,19 @@ func TestRESTGetDelete(t *testing.T) {
}

func TestRESTError(t *testing.T) {
http := &httpmock.Registry{}
client := NewClient(ReplaceTripper(http))

http.StubResponse(422, bytes.NewBufferString(`{"message": "OH NO"}`))
fakehttp := &httpmock.Registry{}
client := NewClient(ReplaceTripper(fakehttp))

fakehttp.Register(httpmock.MatchAny, func(req *http.Request) (*http.Response, error) {
return &http.Response{
Request: req,
StatusCode: 422,
Body: ioutil.NopCloser(bytes.NewBufferString(`{"message": "OH NO"}`)),
Header: map[string][]string{
"Content-Type": {"application/json; charset=utf-8"},
},
}, nil
})

var httpErr HTTPError
err := client.REST("github.com", "DELETE", "repos/branch", nil, nil)
Expand Down
85 changes: 74 additions & 11 deletions api/queries_pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"net/http"
"strings"
"time"

"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/ghrepo"
Expand Down Expand Up @@ -72,12 +73,19 @@ type PullRequest struct {
TotalCount int
Nodes []struct {
Commit struct {
Oid string
StatusCheckRollup struct {
Contexts struct {
Nodes []struct {
State string
Status string
Conclusion string
Name string
Context string
State string
Status string
Conclusion string
StartedAt time.Time
CompletedAt time.Time
DetailsURL string
TargetURL string
}
}
}
Expand Down Expand Up @@ -227,7 +235,7 @@ func (c Client) PullRequestDiff(baseRepo ghrepo.Interface, prNumber int) (io.Rea
if resp.StatusCode == 404 {
return nil, &NotFoundError{errors.New("pull request not found")}
} else if resp.StatusCode != 200 {
return nil, handleHTTPError(resp)
return nil, HandleHTTPError(resp)
}

return resp.Body, nil
Expand Down Expand Up @@ -275,8 +283,8 @@ func PullRequests(client *Client, repo ghrepo.Interface, currentPRNumber int, cu
state
}
...on CheckRun {
status
conclusion
status
}
}
}
Expand Down Expand Up @@ -418,8 +426,32 @@ func PullRequestByNumber(client *Client, repo ghrepo.Interface, number int) (*Pu
author {
login
}
commits {
commits(last: 1) {
totalCount
nodes {
commit {
oid
statusCheckRollup {
contexts(last: 100) {
nodes {
...on StatusContext {
context
state
targetUrl
}
...on CheckRun {
name
status
conclusion
startedAt
completedAt
detailsUrl
}
}
}
}
}
}
}
baseRefName
headRefName
Expand Down Expand Up @@ -524,8 +556,32 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, baseBranch, hea
author {
login
}
commits {
commits(last: 1) {
totalCount
nodes {
commit {
oid
statusCheckRollup {
contexts(last: 100) {
nodes {
...on StatusContext {
context
state
targetUrl
}
...on CheckRun {
name
status
conclusion
startedAt
completedAt
detailsUrl
}
}
}
}
}
}
}
url
baseRefName
Expand Down Expand Up @@ -981,11 +1037,18 @@ func PullRequestMerge(client *Client, repo ghrepo.Interface, pr *PullRequest, m
} `graphql:"mergePullRequest(input: $input)"`
}

input := githubv4.MergePullRequestInput{
PullRequestID: pr.ID,
MergeMethod: &mergeMethod,
}

if m == PullRequestMergeMethodSquash {
commitHeadline := githubv4.String(fmt.Sprintf("%s (#%d)", pr.Title, pr.Number))
input.CommitHeadline = &commitHeadline
}

variables := map[string]interface{}{
"input": githubv4.MergePullRequestInput{
PullRequestID: pr.ID,
MergeMethod: &mergeMethod,
},
"input": input,
}

gql := graphQLClient(client.http, repo.RepoHost())
Expand Down
14 changes: 14 additions & 0 deletions api/queries_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strings"
"time"
Expand Down Expand Up @@ -127,6 +128,19 @@ func RepoDefaultBranch(client *Client, repo ghrepo.Interface) (string, error) {
return r.DefaultBranchRef.Name, nil
}

func CanPushToRepo(httpClient *http.Client, repo ghrepo.Interface) (bool, error) {
if r, ok := repo.(*Repository); ok && r.ViewerPermission != "" {
return r.ViewerCanPush(), nil
}

apiClient := NewClientFromHTTP(httpClient)
r, err := GitHubRepo(apiClient, repo)
if err != nil {
return false, err
}
return r.ViewerCanPush(), nil
}

// RepoParent finds out the parent repository of a fork
func RepoParent(client *Client, repo ghrepo.Interface) (ghrepo.Interface, error) {
var query struct {
Expand Down
Loading

0 comments on commit f43a65a

Please sign in to comment.