X Tutup
Skip to content

Commit 44f9d03

Browse files
author
nate smith
committed
Merge remote-tracking branch 'origin/trunk' into issue703
2 parents a6c7bd8 + fac020e commit 44f9d03

File tree

303 files changed

+18296
-3898
lines changed

Some content is hidden

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

303 files changed

+18296
-3898
lines changed

.github/dependabot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: gomod
4+
directory: "/"
5+
schedule:
6+
interval: "daily"
7+
ignore:
8+
- dependency-name: "*"
9+
update-types:
10+
- version-update:semver-minor
11+
- version-update:semver-major
12+
- package-ecosystem: "github-actions"
13+
directory: "/"
14+
schedule:
15+
interval: "daily"

.github/workflows/codeql.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ name: Code Scanning
22

33
on:
44
push:
5+
branches: [trunk]
56
pull_request:
7+
branches: [trunk]
8+
paths-ignore:
9+
- '**/*.md'
610
schedule:
711
- cron: "0 0 * * 0"
812

.github/workflows/go.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ jobs:
1717
- name: Check out code
1818
uses: actions/checkout@v2
1919

20+
- name: Cache Go modules
21+
uses: actions/cache@v2
22+
with:
23+
path: ~/go
24+
key: ${{ runner.os }}-build-${{ hashFiles('go.mod') }}
25+
restore-keys: |
26+
${{ runner.os }}-build-
27+
${{ runner.os }}-
28+
2029
- name: Download dependencies
2130
run: go mod download
2231

.github/workflows/issueauto.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Issue Automation
2+
on:
3+
issues:
4+
types: [opened]
5+
jobs:
6+
issue-auto:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: label incoming issue
10+
env:
11+
GH_REPO: ${{ github.repository }}
12+
GH_TOKEN: ${{ secrets.AUTOMATION_TOKEN }}
13+
ISSUENUM: ${{ github.event.issue.number }}
14+
ISSUEAUTHOR: ${{ github.event.issue.user.login }}
15+
run: |
16+
if ! gh api orgs/cli/public_members/$ISSUEAUTHOR --silent 2>/dev/null
17+
then
18+
gh issue edit $ISSUENUM --add-label "needs-triage"
19+
fi

.github/workflows/prauto.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
PRNUM: ${{ github.event.pull_request.number }}
1616
PRHEAD: ${{ github.event.pull_request.head.label }}
1717
PRAUTHOR: ${{ github.event.pull_request.user.login }}
18+
PR_AUTHOR_TYPE: ${{ github.event.pull_request.user.type }}
1819
if: "!github.event.pull_request.draft"
1920
run: |
2021
commentPR () {
@@ -42,8 +43,12 @@ jobs:
4243
' -f colID="$(colID "Needs review")" -f prID="$PRID"
4344
}
4445
45-
if gh api orgs/cli/public_members/$PRAUTHOR --silent 2>/dev/null
46+
if [ "$PR_AUTHOR_TYPE" = "Bot" ] || gh api orgs/cli/public_members/$PRAUTHOR --silent 2>/dev/null
4647
then
48+
if [ "$PR_AUTHOR_TYPE" != "Bot" ]
49+
then
50+
gh pr edit $PRNUM --add-assignee $PRAUTHOR
51+
fi
4752
if ! errtext="$(addToBoard 2>&1)"
4853
then
4954
cat <<<"$errtext" >&2
@@ -55,6 +60,8 @@ jobs:
5560
exit 0
5661
fi
5762
63+
gh pr edit $PRNUM --add-label "external"
64+
5865
if [ "$PRHEAD" = "cli:trunk" ]
5966
then
6067
closePR

.github/workflows/releases.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,34 @@ jobs:
1616
with:
1717
go-version: 1.16
1818
- name: Generate changelog
19+
id: changelog
1920
run: |
20-
echo "GORELEASER_CURRENT_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
21-
git fetch --unshallow
22-
script/changelog | tee CHANGELOG.md
21+
echo "::set-output name=tag-name::${GITHUB_REF#refs/tags/}"
22+
gh api repos/$GITHUB_REPOSITORY/releases/generate-notes \
23+
-f tag_name="${GITHUB_REF#refs/tags/}" \
24+
-f target_commitish=trunk \
25+
-q .body > CHANGELOG.md
26+
env:
27+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
28+
- name: Install osslsigncode
29+
run: sudo apt-get install -y osslsigncode
2330
- name: Run GoReleaser
2431
uses: goreleaser/goreleaser-action@v2
2532
with:
2633
version: v0.174.1
2734
args: release --release-notes=CHANGELOG.md
2835
env:
2936
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
37+
GORELEASER_CURRENT_TAG: ${{steps.changelog.outputs.tag-name}}
38+
GITHUB_CERT_PASSWORD: ${{secrets.GITHUB_CERT_PASSWORD}}
39+
DESKTOP_CERT_TOKEN: ${{secrets.DESKTOP_CERT_TOKEN}}
3040
- name: Checkout documentation site
3141
uses: actions/checkout@v2
3242
with:
3343
repository: github/cli.github.com
3444
path: site
3545
fetch-depth: 0
36-
token: ${{secrets.SITE_GITHUB_TOKEN}}
46+
ssh-key: ${{secrets.SITE_SSH_KEY}}
3747
- name: Update site man pages
3848
env:
3949
GIT_COMMITTER_NAME: cli automation
@@ -55,7 +65,6 @@ jobs:
5565
api-write --silent projects/columns/cards/$card/moves -f position=top -F column_id=$DONE_COLUMN
5666
done
5767
echo "moved ${#cards[@]} cards to the Done column"
58-
5968
- name: Install packaging dependencies
6069
run: sudo apt-get install -y rpm reprepro
6170
- name: Set up GPG

.goreleaser.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ release:
88
before:
99
hooks:
1010
- go mod tidy
11-
- make manpages
11+
- make manpages GH_VERSION={{.Version}}
12+
- ./script/prepare-windows-cert.sh '{{ if index .Env "GITHUB_CERT_PASSWORD" }}{{ .Env.GITHUB_CERT_PASSWORD}}{{ end }}' '{{ if index .Env "DESKTOP_CERT_TOKEN" }}{{ .Env.DESKTOP_CERT_TOKEN}}{{ end }}'
1213

1314
builds:
1415
- <<: &build_defaults
@@ -32,6 +33,9 @@ builds:
3233
id: windows
3334
goos: [windows]
3435
goarch: [386, amd64]
36+
hooks:
37+
post:
38+
- ./script/sign-windows-executable.sh '{{ .Path }}'
3539

3640
archives:
3741
- id: nix

CODEOWNERS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
* @cli/code-reviewers
2+
3+
pkg/cmd/codespace/ @cli/codespaces
4+
pkg/liveshare/ @cli/codespaces
5+
internal/codespaces/ @cli/codespaces

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ If anything feels off, or if you feel that some functionality is missing, please
1919

2020
### macOS
2121

22-
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], and as a downloadable binary from the [releases page][].
22+
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], [Spack][], and as a downloadable binary from the [releases page][].
2323

2424
#### Homebrew
2525

@@ -41,21 +41,27 @@ If anything feels off, or if you feel that some functionality is missing, please
4141

4242
Additional Conda installation options available on the [gh-feedstock page](https://github.com/conda-forge/gh-feedstock#installing-gh).
4343

44+
#### Spack
45+
46+
| Install: | Upgrade: |
47+
| ------------------ | ---------------------------------------- |
48+
| `spack install gh` | `spack uninstall gh && spack install gh` |
49+
4450
### Linux & BSD
4551

46-
`gh` is available via [Homebrew](#homebrew), [Conda](#Conda), and as downloadable binaries from the [releases page][].
52+
`gh` is available via [Homebrew](#homebrew), [Conda](#conda), [Spack](#spack), and as downloadable binaries from the [releases page][].
4753

4854
For instructions on specific distributions and package managers, see [Linux & BSD installation](./docs/install_linux.md).
4955

5056
### Windows
5157

52-
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#Conda), and as downloadable MSI.
58+
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#conda), and as downloadable MSI.
5359

5460
#### WinGet
5561

5662
| Install: | Upgrade: |
5763
| ------------------- | --------------------|
58-
| `winget install gh` | `winget upgrade gh` |
64+
| `winget install --id GitHub.cli` | `winget upgrade --id GitHub.cli` |
5965

6066
#### scoop
6167

@@ -99,6 +105,7 @@ tool. Check out our [more detailed explanation][gh-vs-hub] to learn more.
99105
[scoop]: https://scoop.sh
100106
[Chocolatey]: https://chocolatey.org
101107
[Conda]: https://docs.conda.io/en/latest/
108+
[Spack]: https://spack.io
102109
[releases page]: https://github.com/cli/cli/releases/latest
103110
[hub]: https://github.com/github/hub
104111
[contributing]: ./.github/CONTRIBUTING.md

api/client.go

Lines changed: 119 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
"strings"
1313

1414
"github.com/cli/cli/v2/internal/ghinstance"
15+
graphql "github.com/cli/shurcooL-graphql"
1516
"github.com/henvic/httpretty"
16-
"github.com/shurcooL/graphql"
1717
)
1818

1919
// ClientOption represents an argument to NewClient
@@ -98,6 +98,22 @@ func ReplaceTripper(tr http.RoundTripper) ClientOption {
9898
}
9999
}
100100

101+
// ExtractHeader extracts a named header from any response received by this client and, if non-blank, saves
102+
// it to dest.
103+
func ExtractHeader(name string, dest *string) ClientOption {
104+
return func(tr http.RoundTripper) http.RoundTripper {
105+
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
106+
res, err := tr.RoundTrip(req)
107+
if err == nil {
108+
if value := res.Header.Get(name); value != "" {
109+
*dest = value
110+
}
111+
}
112+
return res, err
113+
}}
114+
}
115+
}
116+
101117
type funcTripper struct {
102118
roundTrip func(*http.Request) (*http.Response, error)
103119
}
@@ -124,7 +140,18 @@ type graphQLResponse struct {
124140
type GraphQLError struct {
125141
Type string
126142
Message string
127-
// Path []interface // mixed strings and numbers
143+
Path []interface{} // mixed strings and numbers
144+
}
145+
146+
func (ge GraphQLError) PathString() string {
147+
var res strings.Builder
148+
for i, v := range ge.Path {
149+
if i > 0 {
150+
res.WriteRune('.')
151+
}
152+
fmt.Fprintf(&res, "%v", v)
153+
}
154+
return res.String()
128155
}
129156

130157
// GraphQLErrorResponse contains errors returned in a GraphQL response
@@ -135,18 +162,41 @@ type GraphQLErrorResponse struct {
135162
func (gr GraphQLErrorResponse) Error() string {
136163
errorMessages := make([]string, 0, len(gr.Errors))
137164
for _, e := range gr.Errors {
138-
errorMessages = append(errorMessages, e.Message)
165+
msg := e.Message
166+
if p := e.PathString(); p != "" {
167+
msg = fmt.Sprintf("%s (%s)", msg, p)
168+
}
169+
errorMessages = append(errorMessages, msg)
170+
}
171+
return fmt.Sprintf("GraphQL: %s", strings.Join(errorMessages, ", "))
172+
}
173+
174+
// Match checks if this error is only about a specific type on a specific path. If the path argument ends
175+
// with a ".", it will match all its subpaths as well.
176+
func (gr GraphQLErrorResponse) Match(expectType, expectPath string) bool {
177+
for _, e := range gr.Errors {
178+
if e.Type != expectType || !matchPath(e.PathString(), expectPath) {
179+
return false
180+
}
181+
}
182+
return true
183+
}
184+
185+
func matchPath(p, expect string) bool {
186+
if strings.HasSuffix(expect, ".") {
187+
return strings.HasPrefix(p, expect) || p == strings.TrimSuffix(expect, ".")
139188
}
140-
return fmt.Sprintf("GraphQL error: %s", strings.Join(errorMessages, "\n"))
189+
return p == expect
141190
}
142191

143192
// HTTPError is an error returned by a failed API call
144193
type HTTPError struct {
145-
StatusCode int
146-
RequestURL *url.URL
147-
Message string
148-
OAuthScopes string
149-
Errors []HTTPErrorItem
194+
StatusCode int
195+
RequestURL *url.URL
196+
Message string
197+
Errors []HTTPErrorItem
198+
199+
scopesSuggestion string
150200
}
151201

152202
type HTTPErrorItem struct {
@@ -165,7 +215,63 @@ func (err HTTPError) Error() string {
165215
return fmt.Sprintf("HTTP %d (%s)", err.StatusCode, err.RequestURL)
166216
}
167217

168-
// GraphQL performs a GraphQL request and parses the response
218+
func (err HTTPError) ScopesSuggestion() string {
219+
return err.scopesSuggestion
220+
}
221+
222+
// ScopesSuggestion is an error messaging utility that prints the suggestion to request additional OAuth
223+
// scopes in case a server response indicates that there are missing scopes.
224+
func ScopesSuggestion(resp *http.Response) string {
225+
if resp.StatusCode < 400 || resp.StatusCode > 499 || resp.StatusCode == 422 {
226+
return ""
227+
}
228+
229+
endpointNeedsScopes := resp.Header.Get("X-Accepted-Oauth-Scopes")
230+
tokenHasScopes := resp.Header.Get("X-Oauth-Scopes")
231+
if tokenHasScopes == "" {
232+
return ""
233+
}
234+
235+
gotScopes := map[string]struct{}{}
236+
for _, s := range strings.Split(tokenHasScopes, ",") {
237+
s = strings.TrimSpace(s)
238+
gotScopes[s] = struct{}{}
239+
if strings.HasPrefix(s, "admin:") {
240+
gotScopes["read:"+strings.TrimPrefix(s, "admin:")] = struct{}{}
241+
gotScopes["write:"+strings.TrimPrefix(s, "admin:")] = struct{}{}
242+
} else if strings.HasPrefix(s, "write:") {
243+
gotScopes["read:"+strings.TrimPrefix(s, "write:")] = struct{}{}
244+
}
245+
}
246+
247+
for _, s := range strings.Split(endpointNeedsScopes, ",") {
248+
s = strings.TrimSpace(s)
249+
if _, gotScope := gotScopes[s]; s == "" || gotScope {
250+
continue
251+
}
252+
return fmt.Sprintf(
253+
"This API operation needs the %[1]q scope. To request it, run: gh auth refresh -h %[2]s -s %[1]s",
254+
s,
255+
ghinstance.NormalizeHostname(resp.Request.URL.Hostname()),
256+
)
257+
}
258+
259+
return ""
260+
}
261+
262+
// EndpointNeedsScopes adds additional OAuth scopes to an HTTP response as if they were returned from the
263+
// server endpoint. This improves HTTP 4xx error messaging for endpoints that don't explicitly list the
264+
// OAuth scopes they need.
265+
func EndpointNeedsScopes(resp *http.Response, s string) *http.Response {
266+
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
267+
oldScopes := resp.Header.Get("X-Accepted-Oauth-Scopes")
268+
resp.Header.Set("X-Accepted-Oauth-Scopes", fmt.Sprintf("%s, %s", oldScopes, s))
269+
}
270+
return resp
271+
}
272+
273+
// GraphQL performs a GraphQL request and parses the response. If there are errors in the response,
274+
// *GraphQLErrorResponse will be returned, but the data will also be parsed into the receiver.
169275
func (c Client) GraphQL(hostname string, query string, variables map[string]interface{}, data interface{}) error {
170276
reqBody, err := json.Marshal(map[string]interface{}{"query": query, "variables": variables})
171277
if err != nil {
@@ -261,9 +367,9 @@ func handleResponse(resp *http.Response, data interface{}) error {
261367

262368
func HandleHTTPError(resp *http.Response) error {
263369
httpError := HTTPError{
264-
StatusCode: resp.StatusCode,
265-
RequestURL: resp.Request.URL,
266-
OAuthScopes: resp.Header.Get("X-Oauth-Scopes"),
370+
StatusCode: resp.StatusCode,
371+
RequestURL: resp.Request.URL,
372+
scopesSuggestion: ScopesSuggestion(resp),
267373
}
268374

269375
if !jsonTypeRE.MatchString(resp.Header.Get("Content-Type")) {

0 commit comments

Comments
 (0)
X Tutup