Add repo archive command (#4410)

Co-authored-by: meiji163 <mysatellite99@gmail.com>
This commit is contained in:
Parth 2021-10-12 06:48:40 -04:00 committed by GitHub
parent cedbbe3c2a
commit ec554822b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 318 additions and 20 deletions

View file

@ -230,6 +230,25 @@ func (r Repository) ViewerCanTriage() bool {
}
}
func FetchRepository(client *Client, repo ghrepo.Interface, fields []string) (*Repository, error) {
query := fmt.Sprintf(`query RepositoryInfo($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {%s}
}`, RepositoryGraphQL(fields))
variables := map[string]interface{}{
"owner": repo.RepoOwner(),
"name": repo.RepoName(),
}
var result struct {
Repository Repository
}
if err := client.GraphQL(repo.RepoHost(), query, variables, &result); err != nil {
return nil, err
}
return InitRepoHostname(&result.Repository, repo.RepoHost()), nil
}
func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
query := `
fragment repo on Repository {

View 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
}

View 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())
})
}
}

View 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
}

View file

@ -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"
@ -42,6 +43,7 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(repoSyncCmd.NewCmdSync(f, nil))
cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil))
cmd.AddCommand(gardenCmd.NewCmdGarden(f, nil))
cmd.AddCommand(repoArchiveCmd.NewCmdArchive(f, nil))
return cmd
}

View file

@ -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

View file

@ -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
}