Merge branch 'trunk' into repo-rename
This commit is contained in:
commit
3a936f65e2
24 changed files with 564 additions and 186 deletions
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
|
|
@ -2,7 +2,11 @@ name: Code Scanning
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [trunk]
|
||||
pull_request:
|
||||
branches: [trunk]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
schedule:
|
||||
- cron: "0 0 * * 0"
|
||||
|
||||
|
|
|
|||
3
.github/workflows/prauto.yml
vendored
3
.github/workflows/prauto.yml
vendored
|
|
@ -15,6 +15,7 @@ jobs:
|
|||
PRNUM: ${{ github.event.pull_request.number }}
|
||||
PRHEAD: ${{ github.event.pull_request.head.label }}
|
||||
PRAUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
PR_AUTHOR_TYPE: ${{ github.event.pull_request.user.type }}
|
||||
if: "!github.event.pull_request.draft"
|
||||
run: |
|
||||
commentPR () {
|
||||
|
|
@ -42,7 +43,7 @@ jobs:
|
|||
' -f colID="$(colID "Needs review")" -f prID="$PRID"
|
||||
}
|
||||
|
||||
if gh api orgs/cli/public_members/$PRAUTHOR --silent 2>/dev/null
|
||||
if [ "$PR_AUTHOR_TYPE" = "Bot" ] || gh api orgs/cli/public_members/$PRAUTHOR --silent 2>/dev/null
|
||||
then
|
||||
if ! errtext="$(addToBoard 2>&1)"
|
||||
then
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -19,7 +19,7 @@ If anything feels off, or if you feel that some functionality is missing, please
|
|||
|
||||
### macOS
|
||||
|
||||
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], and as a downloadable binary from the [releases page][].
|
||||
`gh` is available via [Homebrew][], [MacPorts][], [Conda][], [Spack][], and as a downloadable binary from the [releases page][].
|
||||
|
||||
#### Homebrew
|
||||
|
||||
|
|
@ -41,15 +41,21 @@ If anything feels off, or if you feel that some functionality is missing, please
|
|||
|
||||
Additional Conda installation options available on the [gh-feedstock page](https://github.com/conda-forge/gh-feedstock#installing-gh).
|
||||
|
||||
#### Spack
|
||||
|
||||
| Install: | Upgrade: |
|
||||
| ------------------ | ---------------------------------------- |
|
||||
| `spack install gh` | `spack uninstall gh && spack install gh` |
|
||||
|
||||
### Linux & BSD
|
||||
|
||||
`gh` is available via [Homebrew](#homebrew), [Conda](#Conda), and as downloadable binaries from the [releases page][].
|
||||
`gh` is available via [Homebrew](#homebrew), [Conda](#conda), [Spack](#spack), and as downloadable binaries from the [releases page][].
|
||||
|
||||
For instructions on specific distributions and package managers, see [Linux & BSD installation](./docs/install_linux.md).
|
||||
|
||||
### Windows
|
||||
|
||||
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#Conda), and as downloadable MSI.
|
||||
`gh` is available via [WinGet][], [scoop][], [Chocolatey][], [Conda](#conda), and as downloadable MSI.
|
||||
|
||||
#### WinGet
|
||||
|
||||
|
|
@ -99,6 +105,7 @@ tool. Check out our [more detailed explanation][gh-vs-hub] to learn more.
|
|||
[scoop]: https://scoop.sh
|
||||
[Chocolatey]: https://chocolatey.org
|
||||
[Conda]: https://docs.conda.io/en/latest/
|
||||
[Spack]: https://spack.io
|
||||
[releases page]: https://github.com/cli/cli/releases/latest
|
||||
[hub]: https://github.com/github/hub
|
||||
[contributing]: ./.github/CONTRIBUTING.md
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -267,30 +265,6 @@ func (pr *PullRequest) DisplayableReviews() PullRequestReviews {
|
|||
return PullRequestReviews{Nodes: published, TotalCount: len(published)}
|
||||
}
|
||||
|
||||
func (c Client) PullRequestDiff(baseRepo ghrepo.Interface, prNumber int) (io.ReadCloser, error) {
|
||||
url := fmt.Sprintf("%srepos/%s/pulls/%d",
|
||||
ghinstance.RESTPrefix(baseRepo.RepoHost()), ghrepo.FullName(baseRepo), prNumber)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/vnd.github.v3.diff; charset=utf-8")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
return nil, errors.New("pull request not found")
|
||||
} else if resp.StatusCode != 200 {
|
||||
return nil, HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
type pullRequestFeature struct {
|
||||
HasReviewDecision bool
|
||||
HasStatusCheckRollup bool
|
||||
|
|
|
|||
22
go.mod
22
go.mod
|
|
@ -5,27 +5,27 @@ go 1.16
|
|||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||
github.com/MakeNowJust/heredoc v1.0.0
|
||||
github.com/briandowns/spinner v1.11.1
|
||||
github.com/briandowns/spinner v1.16.0
|
||||
github.com/charmbracelet/glamour v0.3.0
|
||||
github.com/cli/browser v1.1.0
|
||||
github.com/cli/oauth v0.8.0
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0
|
||||
github.com/creack/pty v1.1.13
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||
github.com/creack/pty v1.1.16
|
||||
github.com/fatih/camelcase v1.0.0
|
||||
github.com/gabriel-vasile/mimetype v1.1.2
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/henvic/httpretty v0.0.6
|
||||
github.com/itchyny/gojq v0.12.4
|
||||
github.com/itchyny/gojq v0.12.5
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mattn/go-isatty v0.0.13
|
||||
github.com/mattn/go-colorable v0.1.11
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5
|
||||
github.com/muesli/termenv v0.8.1
|
||||
github.com/muesli/termenv v0.9.0
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
|
|
@ -39,7 +39,7 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
|
|
|||
56
go.sum
56
go.sum
|
|
@ -62,8 +62,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
|
|||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0=
|
||||
github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs=
|
||||
github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/glamour v0.3.0 h1:3H+ZrKlSg8s+WU6V7eF2eRVYt8lCueffbi7r2+ffGkc=
|
||||
github.com/charmbracelet/glamour v0.3.0/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw=
|
||||
|
|
@ -87,10 +87,11 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
|
|||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.13 h1:rTPnd/xocYRjutMfqide2zle1u96upp1gm6eUHKi7us=
|
||||
github.com/creack/pty v1.1.13/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.16 h1:vfetlOf3A+9YKggibynnX9mnFjuSVvkRj+IWpcTSLEQ=
|
||||
github.com/creack/pty v1.1.16/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -110,8 +111,8 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo
|
|||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU=
|
||||
github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
|
@ -160,8 +161,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
|
@ -203,8 +205,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
|
||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
|
@ -222,8 +224,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
|
|||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA=
|
||||
github.com/itchyny/gojq v0.12.4 h1:8zgOZWMejEWCLjbF/1mWY7hY7QEARm7dtuhC6Bp4R8o=
|
||||
github.com/itchyny/gojq v0.12.4/go.mod h1:EQUSKgW/YaOxmXpAwGiowFDO4i2Rmtk5+9dFyeiymAg=
|
||||
github.com/itchyny/gojq v0.12.5 h1:6SJ1BQ1VAwJAlIvLSIZmqHP/RUEq3qfVWvsRxrqhsD0=
|
||||
github.com/itchyny/gojq v0.12.5/go.mod h1:3e1hZXv+Kwvdp6V9HXpVrvddiHVApi5EDZwS+zLFeiE=
|
||||
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
||||
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
|
@ -248,16 +250,18 @@ github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
|
|||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
|
|
@ -278,8 +282,9 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5 h1:T+Fc6qGlSfM+z0JPlp+n5rijvlg6C6JYFSNaqnCifDU=
|
||||
github.com/muesli/reflow v0.2.1-0.20210502190812-c80126ec2ad5/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/termenv v0.8.1 h1:9q230czSP3DHVpkaPDXGp0TOfAwyjyYwXlUCQxQSaBk=
|
||||
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
|
|
@ -300,15 +305,15 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b h1:0/ecDXh/HTHRtSDSFnD2/Ta1yQ5J76ZspVY4u0/jGFk=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
|
|
@ -428,8 +433,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -499,10 +505,13 @@ golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
|
|
@ -512,8 +521,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
|||
|
|
@ -34,10 +34,13 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
|
|
@ -190,62 +193,70 @@ type CodespaceEnvironmentConnection struct {
|
|||
HostPublicKeys []string `json:"hostPublicKeys"`
|
||||
}
|
||||
|
||||
// codespacesListResponse is the response body for the `/user/codespaces` endpoint
|
||||
type getCodespacesListResponse struct {
|
||||
Codespaces []*Codespace `json:"codespaces"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
// ListCodespaces returns a list of codespaces for the user. Pass a negative limit to request all pages from
|
||||
// the API until all codespaces have been fetched.
|
||||
func (a *API) ListCodespaces(ctx context.Context, limit int) (codespaces []*Codespace, err error) {
|
||||
perPage := 100
|
||||
if limit > 0 && limit < 100 {
|
||||
perPage = limit
|
||||
}
|
||||
|
||||
// ListCodespaces returns a list of codespaces for the user.
|
||||
// It consumes all pages returned by the API until all codespaces have been fetched.
|
||||
func (a *API) ListCodespaces(ctx context.Context) (codespaces []*Codespace, err error) {
|
||||
per_page := 100
|
||||
for page := 1; ; page++ {
|
||||
response, err := a.fetchCodespaces(ctx, page, per_page)
|
||||
listURL := fmt.Sprintf("%s/user/codespaces?per_page=%d", a.githubAPI, perPage)
|
||||
for {
|
||||
req, err := http.NewRequest(http.MethodGet, listURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
a.setHeaders(req)
|
||||
|
||||
resp, err := a.do(ctx, req, "/user/codespaces")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, api.HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Codespaces []*Codespace `json:"codespaces"`
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
if err := dec.Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
nextURL := findNextPage(resp.Header.Get("Link"))
|
||||
codespaces = append(codespaces, response.Codespaces...)
|
||||
if page*per_page >= response.TotalCount {
|
||||
|
||||
if nextURL == "" || (limit > 0 && len(codespaces) >= limit) {
|
||||
break
|
||||
}
|
||||
|
||||
if newPerPage := limit - len(codespaces); limit > 0 && newPerPage < 100 {
|
||||
u, _ := url.Parse(nextURL)
|
||||
q := u.Query()
|
||||
q.Set("per_page", strconv.Itoa(newPerPage))
|
||||
u.RawQuery = q.Encode()
|
||||
listURL = u.String()
|
||||
} else {
|
||||
listURL = nextURL
|
||||
}
|
||||
}
|
||||
|
||||
return codespaces, nil
|
||||
}
|
||||
|
||||
func (a *API) fetchCodespaces(ctx context.Context, page int, per_page int) (response *getCodespacesListResponse, err error) {
|
||||
req, err := http.NewRequest(
|
||||
http.MethodGet, a.githubAPI+"/user/codespaces", nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
a.setHeaders(req)
|
||||
q := req.URL.Query()
|
||||
q.Add("page", strconv.Itoa(page))
|
||||
q.Add("per_page", strconv.Itoa(per_page))
|
||||
var linkRE = regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`)
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
resp, err := a.do(ctx, req, "/user/codespaces")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %w", err)
|
||||
func findNextPage(linkValue string) string {
|
||||
for _, m := range linkRE.FindAllStringSubmatch(linkValue, -1) {
|
||||
if len(m) > 2 && m[2] == "next" {
|
||||
return m[1]
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
}
|
||||
return response, nil
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetCodespace returns the user codespace based on the provided name.
|
||||
|
|
@ -425,7 +436,7 @@ type CreateCodespaceParams struct {
|
|||
func (a *API) CreateCodespace(ctx context.Context, params *CreateCodespaceParams) (*Codespace, error) {
|
||||
codespace, err := a.startCreate(ctx, params.RepositoryID, params.Machine, params.Branch, params.Location)
|
||||
if err != errProvisioningInProgress {
|
||||
return nil, err
|
||||
return codespace, err
|
||||
}
|
||||
|
||||
// errProvisioningInProgress indicates that codespace creation did not complete
|
||||
|
|
|
|||
|
|
@ -44,13 +44,19 @@ func createFakeListEndpointServer(t *testing.T, initalTotal int, finalTotal int)
|
|||
TotalCount: finalTotal,
|
||||
}
|
||||
|
||||
if page == 1 {
|
||||
switch page {
|
||||
case 1:
|
||||
response.Codespaces = generateCodespaceList(0, per_page)
|
||||
response.TotalCount = initalTotal
|
||||
} else if page == 2 {
|
||||
w.Header().Set("Link", fmt.Sprintf(`<http://%[1]s/user/codespaces?page=3&per_page=%[2]d>; rel="last", <http://%[1]s/user/codespaces?page=2&per_page=%[2]d>; rel="next"`, r.Host, per_page))
|
||||
case 2:
|
||||
response.Codespaces = generateCodespaceList(per_page, per_page*2)
|
||||
response.TotalCount = finalTotal
|
||||
} else {
|
||||
w.Header().Set("Link", fmt.Sprintf(`<http://%s/user/codespaces?page=3&per_page=%d>; rel="next"`, r.Host, per_page))
|
||||
case 3:
|
||||
response.Codespaces = generateCodespaceList(per_page*2, per_page*3-per_page/2)
|
||||
response.TotalCount = finalTotal
|
||||
default:
|
||||
t.Fatal("Should not check extra page")
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +65,7 @@ func createFakeListEndpointServer(t *testing.T, initalTotal int, finalTotal int)
|
|||
}))
|
||||
}
|
||||
|
||||
func TestListCodespaces(t *testing.T) {
|
||||
func TestListCodespaces_limited(t *testing.T) {
|
||||
svr := createFakeListEndpointServer(t, 200, 200)
|
||||
defer svr.Close()
|
||||
|
||||
|
|
@ -69,25 +75,24 @@ func TestListCodespaces(t *testing.T) {
|
|||
token: "faketoken",
|
||||
}
|
||||
ctx := context.TODO()
|
||||
codespaces, err := api.ListCodespaces(ctx)
|
||||
codespaces, err := api.ListCodespaces(ctx, 200)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(codespaces) != 200 {
|
||||
t.Fatalf("expected 100 codespace, got %d", len(codespaces))
|
||||
}
|
||||
|
||||
if len(codespaces) != 200 {
|
||||
t.Fatalf("expected 200 codespace, got %d", len(codespaces))
|
||||
}
|
||||
if codespaces[0].Name != "codespace-0" {
|
||||
t.Fatalf("expected codespace-0, got %s", codespaces[0].Name)
|
||||
}
|
||||
|
||||
if codespaces[199].Name != "codespace-199" {
|
||||
t.Fatalf("expected codespace-199, got %s", codespaces[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMidIterationDeletion(t *testing.T) {
|
||||
svr := createFakeListEndpointServer(t, 200, 199)
|
||||
func TestListCodespaces_unlimited(t *testing.T) {
|
||||
svr := createFakeListEndpointServer(t, 200, 200)
|
||||
defer svr.Close()
|
||||
|
||||
api := API{
|
||||
|
|
@ -96,30 +101,18 @@ func TestMidIterationDeletion(t *testing.T) {
|
|||
token: "faketoken",
|
||||
}
|
||||
ctx := context.TODO()
|
||||
codespaces, err := api.ListCodespaces(ctx)
|
||||
codespaces, err := api.ListCodespaces(ctx, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(codespaces) != 200 {
|
||||
t.Fatalf("expected 200 codespace, got %d", len(codespaces))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMidIterationAddition(t *testing.T) {
|
||||
svr := createFakeListEndpointServer(t, 199, 200)
|
||||
defer svr.Close()
|
||||
|
||||
api := API{
|
||||
githubAPI: svr.URL,
|
||||
client: &http.Client{},
|
||||
token: "faketoken",
|
||||
}
|
||||
ctx := context.TODO()
|
||||
codespaces, err := api.ListCodespaces(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(codespaces) != 200 {
|
||||
t.Fatalf("expected 200 codespace, got %d", len(codespaces))
|
||||
|
||||
if len(codespaces) != 250 {
|
||||
t.Fatalf("expected 250 codespace, got %d", len(codespaces))
|
||||
}
|
||||
if codespaces[0].Name != "codespace-0" {
|
||||
t.Fatalf("expected codespace-0, got %s", codespaces[0].Name)
|
||||
}
|
||||
if codespaces[249].Name != "codespace-249" {
|
||||
t.Fatalf("expected codespace-249, got %s", codespaces[0].Name)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ func TestGenManSeeAlso(t *testing.T) {
|
|||
if err := assertNextLineEquals(scanner, ".PP"); err != nil {
|
||||
t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
|
||||
}
|
||||
if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil {
|
||||
if err := assertNextLineEquals(scanner, `\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP`); err != nil {
|
||||
t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func NewApp(logger *output.Logger, apiClient apiClient) *App {
|
|||
type apiClient interface {
|
||||
GetUser(ctx context.Context) (*api.User, error)
|
||||
GetCodespace(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error)
|
||||
ListCodespaces(ctx context.Context) ([]*api.Codespace, error)
|
||||
ListCodespaces(ctx context.Context, limit int) ([]*api.Codespace, error)
|
||||
DeleteCodespace(ctx context.Context, name string) error
|
||||
StartCodespace(ctx context.Context, name string) error
|
||||
CreateCodespace(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error)
|
||||
|
|
@ -49,7 +49,7 @@ type apiClient interface {
|
|||
var errNoCodespaces = errors.New("you have no codespaces")
|
||||
|
||||
func chooseCodespace(ctx context.Context, apiClient apiClient) (*api.Codespace, error) {
|
||||
codespaces, err := apiClient.ListCodespaces(ctx)
|
||||
codespaces, err := apiClient.ListCodespaces(ctx, -1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting codespaces: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) (err error) {
|
|||
var codespaces []*api.Codespace
|
||||
nameFilter := opts.codespaceName
|
||||
if nameFilter == "" {
|
||||
codespaces, err = a.apiClient.ListCodespaces(ctx)
|
||||
codespaces, err = a.apiClient.ListCodespaces(ctx, -1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting codespaces: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ func TestDelete(t *testing.T) {
|
|||
},
|
||||
}
|
||||
if tt.opts.codespaceName == "" {
|
||||
apiMock.ListCodespacesFunc = func(_ context.Context) ([]*api.Codespace, error) {
|
||||
apiMock.ListCodespacesFunc = func(_ context.Context, num int) ([]*api.Codespace, error) {
|
||||
return tt.codespaces, nil
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -6,28 +6,35 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmd/codespace/output"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newListCmd(app *App) *cobra.Command {
|
||||
var asJSON bool
|
||||
var limit int
|
||||
|
||||
listCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List your codespaces",
|
||||
Args: noArgsConstraint,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return app.List(cmd.Context(), asJSON)
|
||||
if limit < 1 {
|
||||
return &cmdutil.FlagError{Err: fmt.Errorf("invalid limit: %v", limit)}
|
||||
}
|
||||
|
||||
return app.List(cmd.Context(), asJSON, limit)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")
|
||||
listCmd.Flags().IntVarP(&limit, "limit", "L", 30, "Maximum number of codespaces to list")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func (a *App) List(ctx context.Context, asJSON bool) error {
|
||||
codespaces, err := a.apiClient.ListCodespaces(ctx)
|
||||
func (a *App) List(ctx context.Context, asJSON bool, limit int) error {
|
||||
codespaces, err := a.apiClient.ListCodespaces(ctx, limit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting codespaces: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import (
|
|||
// GetUserFunc: func(ctx context.Context) (*api.User, error) {
|
||||
// panic("mock out the GetUser method")
|
||||
// },
|
||||
// ListCodespacesFunc: func(ctx context.Context) ([]*api.Codespace, error) {
|
||||
// ListCodespacesFunc: func(ctx context.Context, limit int) ([]*api.Codespace, error) {
|
||||
// panic("mock out the ListCodespaces method")
|
||||
// },
|
||||
// StartCodespaceFunc: func(ctx context.Context, name string) error {
|
||||
|
|
@ -84,7 +84,7 @@ type apiClientMock struct {
|
|||
GetUserFunc func(ctx context.Context) (*api.User, error)
|
||||
|
||||
// ListCodespacesFunc mocks the ListCodespaces method.
|
||||
ListCodespacesFunc func(ctx context.Context) ([]*api.Codespace, error)
|
||||
ListCodespacesFunc func(ctx context.Context, limit int) ([]*api.Codespace, error)
|
||||
|
||||
// StartCodespaceFunc mocks the StartCodespace method.
|
||||
StartCodespaceFunc func(ctx context.Context, name string) error
|
||||
|
|
@ -162,6 +162,8 @@ type apiClientMock struct {
|
|||
ListCodespaces []struct {
|
||||
// Ctx is the ctx argument value.
|
||||
Ctx context.Context
|
||||
// Limit is the limit argument value.
|
||||
Limit int
|
||||
}
|
||||
// StartCodespace holds details about calls to the StartCodespace method.
|
||||
StartCodespace []struct {
|
||||
|
|
@ -508,29 +510,33 @@ func (mock *apiClientMock) GetUserCalls() []struct {
|
|||
}
|
||||
|
||||
// ListCodespaces calls ListCodespacesFunc.
|
||||
func (mock *apiClientMock) ListCodespaces(ctx context.Context) ([]*api.Codespace, error) {
|
||||
func (mock *apiClientMock) ListCodespaces(ctx context.Context, limit int) ([]*api.Codespace, error) {
|
||||
if mock.ListCodespacesFunc == nil {
|
||||
panic("apiClientMock.ListCodespacesFunc: method is nil but apiClient.ListCodespaces was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
Ctx context.Context
|
||||
Ctx context.Context
|
||||
Limit int
|
||||
}{
|
||||
Ctx: ctx,
|
||||
Ctx: ctx,
|
||||
Limit: limit,
|
||||
}
|
||||
mock.lockListCodespaces.Lock()
|
||||
mock.calls.ListCodespaces = append(mock.calls.ListCodespaces, callInfo)
|
||||
mock.lockListCodespaces.Unlock()
|
||||
return mock.ListCodespacesFunc(ctx)
|
||||
return mock.ListCodespacesFunc(ctx, limit)
|
||||
}
|
||||
|
||||
// ListCodespacesCalls gets all the calls that were made to ListCodespaces.
|
||||
// Check the length with:
|
||||
// len(mockedapiClient.ListCodespacesCalls())
|
||||
func (mock *apiClientMock) ListCodespacesCalls() []struct {
|
||||
Ctx context.Context
|
||||
Ctx context.Context
|
||||
Limit int
|
||||
} {
|
||||
var calls []struct {
|
||||
Ctx context.Context
|
||||
Ctx context.Context
|
||||
Limit int
|
||||
}
|
||||
mock.lockListCodespaces.RLock()
|
||||
calls = mock.calls.ListCodespaces
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ func NewManager(io *iostreams.IOStreams) *Manager {
|
|||
platform: func() string {
|
||||
return fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||
},
|
||||
io: io,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -25,6 +27,7 @@ type DiffOptions struct {
|
|||
|
||||
SelectorArg string
|
||||
UseColor string
|
||||
Patch bool
|
||||
}
|
||||
|
||||
func NewCmdDiff(f *cmdutil.Factory, runF func(*DiffOptions) error) *cobra.Command {
|
||||
|
|
@ -70,6 +73,7 @@ func NewCmdDiff(f *cmdutil.Factory, runF func(*DiffOptions) error) *cobra.Comman
|
|||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.UseColor, "color", "auto", "Use color in diff output: {always|never|auto}")
|
||||
cmd.Flags().BoolVar(&opts.Patch, "patch", false, "Display diff in patch format")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -88,9 +92,8 @@ func diffRun(opts *DiffOptions) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
diff, err := apiClient.PullRequestDiff(baseRepo, pr.Number)
|
||||
diff, err := fetchDiff(httpClient, baseRepo, pr.Number, opts.Patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find pull request diff: %w", err)
|
||||
}
|
||||
|
|
@ -132,6 +135,36 @@ func diffRun(opts *DiffOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func fetchDiff(httpClient *http.Client, baseRepo ghrepo.Interface, prNumber int, asPatch bool) (io.ReadCloser, error) {
|
||||
url := fmt.Sprintf(
|
||||
"%srepos/%s/pulls/%d",
|
||||
ghinstance.RESTPrefix(baseRepo.RepoHost()),
|
||||
ghrepo.FullName(baseRepo),
|
||||
prNumber,
|
||||
)
|
||||
acceptType := "application/vnd.github.v3.diff"
|
||||
if asPatch {
|
||||
acceptType = "application/vnd.github.v3.patch"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", acceptType)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, api.HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
var diffHeaderPrefixes = []string{"+++", "---", "diff", "index"}
|
||||
|
||||
func isHeaderLine(dl string) bool {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
|
|
@ -140,26 +141,67 @@ func runCommand(rt http.RoundTripper, remotes context.Remotes, isTTY bool, cli s
|
|||
}, err
|
||||
}
|
||||
|
||||
func TestPRDiff_notty(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
func TestPRDiff_notty_diff(t *testing.T) {
|
||||
httpReg := &httpmock.Registry{}
|
||||
defer httpReg.Verify(t)
|
||||
|
||||
shared.RunCommandFinder("", &api.PullRequest{Number: 123}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
http.Register(
|
||||
var gotAccept string
|
||||
httpReg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/pulls/123"),
|
||||
httpmock.StringResponse(testDiff))
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
gotAccept = req.Header.Get("Accept")
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Request: req,
|
||||
Body: ioutil.NopCloser(strings.NewReader(testDiff)),
|
||||
}, nil
|
||||
})
|
||||
|
||||
output, err := runCommand(http, nil, false, "")
|
||||
output, err := runCommand(httpReg, nil, false, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(testDiff, output.String()); diff != "" {
|
||||
t.Errorf("command output did not match:\n%s", diff)
|
||||
}
|
||||
if gotAccept != "application/vnd.github.v3.diff" {
|
||||
t.Errorf("unexpected Accept header: %s", gotAccept)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRDiff_tty(t *testing.T) {
|
||||
func TestPRDiff_notty_patch(t *testing.T) {
|
||||
httpReg := &httpmock.Registry{}
|
||||
defer httpReg.Verify(t)
|
||||
|
||||
shared.RunCommandFinder("", &api.PullRequest{Number: 123}, ghrepo.New("OWNER", "REPO"))
|
||||
|
||||
var gotAccept string
|
||||
httpReg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/pulls/123"),
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
gotAccept = req.Header.Get("Accept")
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Request: req,
|
||||
Body: ioutil.NopCloser(strings.NewReader(testDiff)),
|
||||
}, nil
|
||||
})
|
||||
|
||||
output, err := runCommand(httpReg, nil, false, "--patch")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(testDiff, output.String()); diff != "" {
|
||||
t.Errorf("command output did not match:\n%s", diff)
|
||||
}
|
||||
if gotAccept != "application/vnd.github.v3.patch" {
|
||||
t.Errorf("unexpected Accept header: %s", gotAccept)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRDiff_tty_diff(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
|
|
|
|||
99
pkg/cmd/repo/archive/archive.go
Normal file
99
pkg/cmd/repo/archive/archive.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ArchiveOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
RepoArg string
|
||||
}
|
||||
|
||||
func NewCmdArchive(f *cmdutil.Factory, runF func(*ArchiveOptions) error) *cobra.Command {
|
||||
opts := &ArchiveOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
|
||||
Use: "archive <repository>",
|
||||
Short: "Archive a repository",
|
||||
Long: "Archive a GitHub repository.",
|
||||
Args: cmdutil.ExactArgs(1, "cannot archive: repository argument required"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.RepoArg = args[0]
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return archiveRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func archiveRun(opts *ArchiveOptions) error {
|
||||
cs := opts.IO.ColorScheme()
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
var toArchive ghrepo.Interface
|
||||
|
||||
archiveURL := opts.RepoArg
|
||||
if !strings.Contains(archiveURL, "/") {
|
||||
currentUser, err := api.CurrentLoginName(apiClient, ghinstance.Default())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
archiveURL = currentUser + "/" + archiveURL
|
||||
}
|
||||
toArchive, err = ghrepo.FromFullName(archiveURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("argument error: %w", err)
|
||||
}
|
||||
|
||||
fields := []string{"name", "owner", "isArchived", "id"}
|
||||
repo, err := api.FetchRepository(apiClient, toArchive, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullName := ghrepo.FullName(toArchive)
|
||||
if repo.IsArchived {
|
||||
fmt.Fprintf(opts.IO.ErrOut,
|
||||
"%s Repository %s is already archived\n",
|
||||
cs.WarningIcon(),
|
||||
fullName)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = archiveRepo(httpClient, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("API called failed: %w", err)
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.Out,
|
||||
"%s Archived repository %s\n",
|
||||
cs.SuccessIcon(),
|
||||
fullName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
165
pkg/cmd/repo/archive/archive_test.go
Normal file
165
pkg/cmd/repo/archive/archive_test.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// probably redundant
|
||||
func TestNewCmdArchive(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
tty bool
|
||||
output ArchiveOptions
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid repo",
|
||||
input: "cli/cli",
|
||||
tty: true,
|
||||
output: ArchiveOptions{
|
||||
RepoArg: "cli/cli",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no argument",
|
||||
input: "",
|
||||
wantErr: true,
|
||||
tty: true,
|
||||
output: ArchiveOptions{
|
||||
RepoArg: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, _, _ := iostreams.Test()
|
||||
io.SetStdinTTY(tt.tty)
|
||||
io.SetStdoutTTY(tt.tty)
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
}
|
||||
argv, err := shlex.Split(tt.input)
|
||||
assert.NoError(t, err)
|
||||
var gotOpts *ArchiveOptions
|
||||
cmd := NewCmdArchive(f, func(opts *ArchiveOptions) error {
|
||||
gotOpts = opts
|
||||
return nil
|
||||
})
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.output.RepoArg, gotOpts.RepoArg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ArchiveRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts ArchiveOptions
|
||||
httpStubs func(*httpmock.Registry)
|
||||
isTTY bool
|
||||
wantStdout string
|
||||
wantStderr string
|
||||
}{
|
||||
{
|
||||
name: "unarchived repo tty",
|
||||
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
|
||||
wantStdout: "✓ Archived repository OWNER/REPO\n",
|
||||
isTTY: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||
httpmock.StringResponse(`{ "data": { "repository": {
|
||||
"id": "THE-ID",
|
||||
"isArchived": false} } }`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation ArchiveRepository\b`),
|
||||
httpmock.StringResponse(`{}`))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unarchived repo notty",
|
||||
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
|
||||
isTTY: false,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||
httpmock.StringResponse(`{ "data": { "repository": {
|
||||
"id": "THE-ID",
|
||||
"isArchived": false} } }`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation ArchiveRepository\b`),
|
||||
httpmock.StringResponse(`{}`))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "archived repo tty",
|
||||
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
|
||||
wantStderr: "! Repository OWNER/REPO is already archived\n",
|
||||
isTTY: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||
httpmock.StringResponse(`{ "data": { "repository": {
|
||||
"id": "THE-ID",
|
||||
"isArchived": true } } }`))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "archived repo notty",
|
||||
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
|
||||
isTTY: false,
|
||||
wantStderr: "! Repository OWNER/REPO is already archived\n",
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||
httpmock.StringResponse(`{ "data": { "repository": {
|
||||
"id": "THE-ID",
|
||||
"isArchived": true } } }`))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
reg := &httpmock.Registry{}
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
tt.opts.IO = io
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer reg.Verify(t)
|
||||
io.SetStdoutTTY(tt.isTTY)
|
||||
io.SetStderrTTY(tt.isTTY)
|
||||
|
||||
err := archiveRun(&tt.opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantStdout, stdout.String())
|
||||
assert.Equal(t, tt.wantStderr, stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
32
pkg/cmd/repo/archive/http.go
Normal file
32
pkg/cmd/repo/archive/http.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/shurcooL/githubv4"
|
||||
"github.com/shurcooL/graphql"
|
||||
)
|
||||
|
||||
func archiveRepo(client *http.Client, repo *api.Repository) error {
|
||||
var mutation struct {
|
||||
ArchiveRepository struct {
|
||||
Repository struct {
|
||||
ID string
|
||||
}
|
||||
} `graphql:"archiveRepository(input: $input)"`
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"input": githubv4.ArchiveRepositoryInput{
|
||||
RepositoryID: repo.ID,
|
||||
},
|
||||
}
|
||||
|
||||
host := repo.RepoHost()
|
||||
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(host), client)
|
||||
err := gql.MutateNamed(context.Background(), "ArchiveRepository", &mutation, variables)
|
||||
return err
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package repo
|
|||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
repoArchiveCmd "github.com/cli/cli/v2/pkg/cmd/repo/archive"
|
||||
repoCloneCmd "github.com/cli/cli/v2/pkg/cmd/repo/clone"
|
||||
repoCreateCmd "github.com/cli/cli/v2/pkg/cmd/repo/create"
|
||||
creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
|
||||
|
|
@ -44,6 +45,7 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command {
|
|||
cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil))
|
||||
cmd.AddCommand(gardenCmd.NewCmdGarden(f, nil))
|
||||
cmd.AddCommand(repoRenameCmd.NewCmdRename(f, nil))
|
||||
cmd.AddCommand(repoArchiveCmd.NewCmdArchive(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,25 +12,6 @@ import (
|
|||
|
||||
var NotFoundError = errors.New("not found")
|
||||
|
||||
func fetchRepository(apiClient *api.Client, repo ghrepo.Interface, fields []string) (*api.Repository, error) {
|
||||
query := fmt.Sprintf(`query RepositoryInfo($owner: String!, $name: String!) {
|
||||
repository(owner: $owner, name: $name) {%s}
|
||||
}`, api.RepositoryGraphQL(fields))
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"owner": repo.RepoOwner(),
|
||||
"name": repo.RepoName(),
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Repository api.Repository
|
||||
}
|
||||
if err := apiClient.GraphQL(repo.RepoHost(), query, variables, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.InitRepoHostname(&result.Repository, repo.RepoHost()), nil
|
||||
}
|
||||
|
||||
type RepoReadme struct {
|
||||
Filename string
|
||||
Content string
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
fields = opts.Exporter.Fields()
|
||||
}
|
||||
|
||||
repo, err := fetchRepository(apiClient, toView, fields)
|
||||
repo, err := api.FetchRepository(apiClient, toView, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue