X Tutup
Skip to content

Commit f43a65a

Browse files
author
Amanda Pinsker
authored
Merge branch 'trunk' into readme-updates
2 parents 14c4331 + a50b0db commit f43a65a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+4821
-368
lines changed

.github/workflows/releases.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ jobs:
7878
popd
7979
- name: Run reprepro
8080
env:
81-
RELEASES: "focal stable"
81+
RELEASES: "focal stable bionic trusty precise xenial"
8282
run: |
8383
mkdir -p upload
8484
for release in $RELEASES; do

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ endif
2222
GO_LDFLAGS := -X github.com/cli/cli/command.Version=$(GH_VERSION) $(GO_LDFLAGS)
2323
GO_LDFLAGS := -X github.com/cli/cli/command.BuildDate=$(BUILD_DATE) $(GO_LDFLAGS)
2424
ifdef GH_OAUTH_CLIENT_SECRET
25-
GO_LDFLAGS := -X github.com/cli/cli/internal/config.oauthClientID=$(GH_OAUTH_CLIENT_ID) $(GO_LDFLAGS)
26-
GO_LDFLAGS := -X github.com/cli/cli/internal/config.oauthClientSecret=$(GH_OAUTH_CLIENT_SECRET) $(GO_LDFLAGS)
25+
GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientID=$(GH_OAUTH_CLIENT_ID) $(GO_LDFLAGS)
26+
GO_LDFLAGS := -X github.com/cli/cli/internal/authflow.oauthClientSecret=$(GH_OAUTH_CLIENT_SECRET) $(GO_LDFLAGS)
2727
endif
2828

2929
bin/gh: $(BUILD_FILES)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ GitHub CLI is available for repositories hosted on GitHub.com and GitHub Enterpr
1414
Read the [official docs][] for usage and more information.
1515

1616

17+
1718
## We want your feedback
1819

1920
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][].

api/client.go

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ func NewClientFromHTTP(httpClient *http.Client) *Client {
4545
func AddHeader(name, value string) ClientOption {
4646
return func(tr http.RoundTripper) http.RoundTripper {
4747
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
48-
req.Header.Add(name, value)
48+
if req.Header.Get(name) == "" {
49+
req.Header.Add(name, value)
50+
}
4951
return tr.RoundTrip(req)
5052
}}
5153
}
@@ -55,11 +57,16 @@ func AddHeader(name, value string) ClientOption {
5557
func AddHeaderFunc(name string, getValue func(*http.Request) (string, error)) ClientOption {
5658
return func(tr http.RoundTripper) http.RoundTripper {
5759
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
60+
if req.Header.Get(name) != "" {
61+
return tr.RoundTrip(req)
62+
}
5863
value, err := getValue(req)
5964
if err != nil {
6065
return nil, err
6166
}
62-
req.Header.Add(name, value)
67+
if value != "" {
68+
req.Header.Add(name, value)
69+
}
6370
return tr.RoundTrip(req)
6471
}}
6572
}
@@ -68,14 +75,15 @@ func AddHeaderFunc(name string, getValue func(*http.Request) (string, error)) Cl
6875
// VerboseLog enables request/response logging within a RoundTripper
6976
func VerboseLog(out io.Writer, logTraffic bool, colorize bool) ClientOption {
7077
logger := &httpretty.Logger{
71-
Time: true,
72-
TLS: false,
73-
Colors: colorize,
74-
RequestHeader: logTraffic,
75-
RequestBody: logTraffic,
76-
ResponseHeader: logTraffic,
77-
ResponseBody: logTraffic,
78-
Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
78+
Time: true,
79+
TLS: false,
80+
Colors: colorize,
81+
RequestHeader: logTraffic,
82+
RequestBody: logTraffic,
83+
ResponseHeader: logTraffic,
84+
ResponseBody: logTraffic,
85+
Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}},
86+
MaxResponseBody: 10000,
7987
}
8088
logger.SetOutput(out)
8189
logger.SetBodyFilter(func(h http.Header) (skip bool, err error) {
@@ -190,7 +198,9 @@ type HTTPError struct {
190198
}
191199

192200
func (err HTTPError) Error() string {
193-
if err.Message != "" {
201+
if msgs := strings.SplitN(err.Message, "\n", 2); len(msgs) > 1 {
202+
return fmt.Sprintf("HTTP %d: %s (%s)\n%s", err.StatusCode, msgs[0], err.RequestURL, msgs[1])
203+
} else if err.Message != "" {
194204
return fmt.Sprintf("HTTP %d: %s (%s)", err.StatusCode, err.Message, err.RequestURL)
195205
}
196206
return fmt.Sprintf("HTTP %d (%s)", err.StatusCode, err.RequestURL)
@@ -222,7 +232,7 @@ func (c Client) HasMinimumScopes(hostname string) error {
222232
}()
223233

224234
if res.StatusCode != 200 {
225-
return handleHTTPError(res)
235+
return HandleHTTPError(res)
226236
}
227237

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

299309
success := resp.StatusCode >= 200 && resp.StatusCode < 300
300310
if !success {
301-
return handleHTTPError(resp)
311+
return HandleHTTPError(resp)
302312
}
303313

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

324334
if !success {
325-
return handleHTTPError(resp)
335+
return HandleHTTPError(resp)
326336
}
327337

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

345-
func handleHTTPError(resp *http.Response) error {
355+
func HandleHTTPError(resp *http.Response) error {
346356
httpError := HTTPError{
347357
StatusCode: resp.StatusCode,
348358
RequestURL: resp.Request.URL,
349359
OAuthScopes: resp.Header.Get("X-Oauth-Scopes"),
350360
}
351361

362+
if !jsonTypeRE.MatchString(resp.Header.Get("Content-Type")) {
363+
httpError.Message = resp.Status
364+
return httpError
365+
}
366+
352367
body, err := ioutil.ReadAll(resp.Body)
353368
if err != nil {
354369
httpError.Message = err.Error()
@@ -357,14 +372,57 @@ func handleHTTPError(resp *http.Response) error {
357372

358373
var parsedBody struct {
359374
Message string `json:"message"`
375+
Errors []json.RawMessage
376+
}
377+
if err := json.Unmarshal(body, &parsedBody); err != nil {
378+
return httpError
360379
}
361-
if err := json.Unmarshal(body, &parsedBody); err == nil {
362-
httpError.Message = parsedBody.Message
380+
381+
type errorObject struct {
382+
Message string
383+
Resource string
384+
Field string
385+
Code string
386+
}
387+
388+
messages := []string{parsedBody.Message}
389+
for _, raw := range parsedBody.Errors {
390+
switch raw[0] {
391+
case '"':
392+
var errString string
393+
_ = json.Unmarshal(raw, &errString)
394+
messages = append(messages, errString)
395+
case '{':
396+
var errInfo errorObject
397+
_ = json.Unmarshal(raw, &errInfo)
398+
msg := errInfo.Message
399+
if errInfo.Code != "custom" {
400+
msg = fmt.Sprintf("%s.%s %s", errInfo.Resource, errInfo.Field, errorCodeToMessage(errInfo.Code))
401+
}
402+
if msg != "" {
403+
messages = append(messages, msg)
404+
}
405+
}
363406
}
407+
httpError.Message = strings.Join(messages, "\n")
364408

365409
return httpError
366410
}
367411

412+
func errorCodeToMessage(code string) string {
413+
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors
414+
switch code {
415+
case "missing", "missing_field":
416+
return "is missing"
417+
case "invalid", "unprocessable":
418+
return "is invalid"
419+
case "already_exists":
420+
return "already exists"
421+
default:
422+
return code
423+
}
424+
}
425+
368426
var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`)
369427

370428
func inspectableMIMEType(t string) bool {

api/client_test.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,19 @@ func TestRESTGetDelete(t *testing.T) {
7676
}
7777

7878
func TestRESTError(t *testing.T) {
79-
http := &httpmock.Registry{}
80-
client := NewClient(ReplaceTripper(http))
81-
82-
http.StubResponse(422, bytes.NewBufferString(`{"message": "OH NO"}`))
79+
fakehttp := &httpmock.Registry{}
80+
client := NewClient(ReplaceTripper(fakehttp))
81+
82+
fakehttp.Register(httpmock.MatchAny, func(req *http.Request) (*http.Response, error) {
83+
return &http.Response{
84+
Request: req,
85+
StatusCode: 422,
86+
Body: ioutil.NopCloser(bytes.NewBufferString(`{"message": "OH NO"}`)),
87+
Header: map[string][]string{
88+
"Content-Type": {"application/json; charset=utf-8"},
89+
},
90+
}, nil
91+
})
8392

8493
var httpErr HTTPError
8594
err := client.REST("github.com", "DELETE", "repos/branch", nil, nil)

api/queries_pr.go

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net/http"
99
"strings"
10+
"time"
1011

1112
"github.com/cli/cli/internal/ghinstance"
1213
"github.com/cli/cli/internal/ghrepo"
@@ -72,12 +73,19 @@ type PullRequest struct {
7273
TotalCount int
7374
Nodes []struct {
7475
Commit struct {
76+
Oid string
7577
StatusCheckRollup struct {
7678
Contexts struct {
7779
Nodes []struct {
78-
State string
79-
Status string
80-
Conclusion string
80+
Name string
81+
Context string
82+
State string
83+
Status string
84+
Conclusion string
85+
StartedAt time.Time
86+
CompletedAt time.Time
87+
DetailsURL string
88+
TargetURL string
8189
}
8290
}
8391
}
@@ -227,7 +235,7 @@ func (c Client) PullRequestDiff(baseRepo ghrepo.Interface, prNumber int) (io.Rea
227235
if resp.StatusCode == 404 {
228236
return nil, &NotFoundError{errors.New("pull request not found")}
229237
} else if resp.StatusCode != 200 {
230-
return nil, handleHTTPError(resp)
238+
return nil, HandleHTTPError(resp)
231239
}
232240

233241
return resp.Body, nil
@@ -275,8 +283,8 @@ func PullRequests(client *Client, repo ghrepo.Interface, currentPRNumber int, cu
275283
state
276284
}
277285
...on CheckRun {
278-
status
279286
conclusion
287+
status
280288
}
281289
}
282290
}
@@ -418,8 +426,32 @@ func PullRequestByNumber(client *Client, repo ghrepo.Interface, number int) (*Pu
418426
author {
419427
login
420428
}
421-
commits {
429+
commits(last: 1) {
422430
totalCount
431+
nodes {
432+
commit {
433+
oid
434+
statusCheckRollup {
435+
contexts(last: 100) {
436+
nodes {
437+
...on StatusContext {
438+
context
439+
state
440+
targetUrl
441+
}
442+
...on CheckRun {
443+
name
444+
status
445+
conclusion
446+
startedAt
447+
completedAt
448+
detailsUrl
449+
}
450+
}
451+
}
452+
}
453+
}
454+
}
423455
}
424456
baseRefName
425457
headRefName
@@ -524,8 +556,32 @@ func PullRequestForBranch(client *Client, repo ghrepo.Interface, baseBranch, hea
524556
author {
525557
login
526558
}
527-
commits {
559+
commits(last: 1) {
528560
totalCount
561+
nodes {
562+
commit {
563+
oid
564+
statusCheckRollup {
565+
contexts(last: 100) {
566+
nodes {
567+
...on StatusContext {
568+
context
569+
state
570+
targetUrl
571+
}
572+
...on CheckRun {
573+
name
574+
status
575+
conclusion
576+
startedAt
577+
completedAt
578+
detailsUrl
579+
}
580+
}
581+
}
582+
}
583+
}
584+
}
529585
}
530586
url
531587
baseRefName
@@ -981,11 +1037,18 @@ func PullRequestMerge(client *Client, repo ghrepo.Interface, pr *PullRequest, m
9811037
} `graphql:"mergePullRequest(input: $input)"`
9821038
}
9831039

1040+
input := githubv4.MergePullRequestInput{
1041+
PullRequestID: pr.ID,
1042+
MergeMethod: &mergeMethod,
1043+
}
1044+
1045+
if m == PullRequestMergeMethodSquash {
1046+
commitHeadline := githubv4.String(fmt.Sprintf("%s (#%d)", pr.Title, pr.Number))
1047+
input.CommitHeadline = &commitHeadline
1048+
}
1049+
9841050
variables := map[string]interface{}{
985-
"input": githubv4.MergePullRequestInput{
986-
PullRequestID: pr.ID,
987-
MergeMethod: &mergeMethod,
988-
},
1051+
"input": input,
9891052
}
9901053

9911054
gql := graphQLClient(client.http, repo.RepoHost())

api/queries_repo.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"net/http"
910
"sort"
1011
"strings"
1112
"time"
@@ -127,6 +128,19 @@ func RepoDefaultBranch(client *Client, repo ghrepo.Interface) (string, error) {
127128
return r.DefaultBranchRef.Name, nil
128129
}
129130

131+
func CanPushToRepo(httpClient *http.Client, repo ghrepo.Interface) (bool, error) {
132+
if r, ok := repo.(*Repository); ok && r.ViewerPermission != "" {
133+
return r.ViewerCanPush(), nil
134+
}
135+
136+
apiClient := NewClientFromHTTP(httpClient)
137+
r, err := GitHubRepo(apiClient, repo)
138+
if err != nil {
139+
return false, err
140+
}
141+
return r.ViewerCanPush(), nil
142+
}
143+
130144
// RepoParent finds out the parent repository of a fork
131145
func RepoParent(client *Client, repo ghrepo.Interface) (ghrepo.Interface, error) {
132146
var query struct {

0 commit comments

Comments
 (0)
X Tutup