X Tutup
Skip to content

Commit ec55482

Browse files
pxrth9meiji163
andauthored
Add repo archive command (cli#4410)
Co-authored-by: meiji163 <mysatellite99@gmail.com>
1 parent cedbbe3 commit ec55482

File tree

7 files changed

+318
-20
lines changed

7 files changed

+318
-20
lines changed

api/queries_repo.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,25 @@ func (r Repository) ViewerCanTriage() bool {
230230
}
231231
}
232232

233+
func FetchRepository(client *Client, repo ghrepo.Interface, fields []string) (*Repository, error) {
234+
query := fmt.Sprintf(`query RepositoryInfo($owner: String!, $name: String!) {
235+
repository(owner: $owner, name: $name) {%s}
236+
}`, RepositoryGraphQL(fields))
237+
238+
variables := map[string]interface{}{
239+
"owner": repo.RepoOwner(),
240+
"name": repo.RepoName(),
241+
}
242+
243+
var result struct {
244+
Repository Repository
245+
}
246+
if err := client.GraphQL(repo.RepoHost(), query, variables, &result); err != nil {
247+
return nil, err
248+
}
249+
return InitRepoHostname(&result.Repository, repo.RepoHost()), nil
250+
}
251+
233252
func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
234253
query := `
235254
fragment repo on Repository {

pkg/cmd/repo/archive/archive.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package archive
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strings"
7+
8+
"github.com/cli/cli/v2/api"
9+
"github.com/cli/cli/v2/internal/ghinstance"
10+
"github.com/cli/cli/v2/internal/ghrepo"
11+
"github.com/cli/cli/v2/pkg/cmdutil"
12+
13+
"github.com/cli/cli/v2/pkg/iostreams"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
type ArchiveOptions struct {
18+
HttpClient func() (*http.Client, error)
19+
IO *iostreams.IOStreams
20+
RepoArg string
21+
}
22+
23+
func NewCmdArchive(f *cmdutil.Factory, runF func(*ArchiveOptions) error) *cobra.Command {
24+
opts := &ArchiveOptions{
25+
IO: f.IOStreams,
26+
HttpClient: f.HttpClient,
27+
}
28+
29+
cmd := &cobra.Command{
30+
DisableFlagsInUseLine: true,
31+
32+
Use: "archive <repository>",
33+
Short: "Archive a repository",
34+
Long: "Archive a GitHub repository.",
35+
Args: cmdutil.ExactArgs(1, "cannot archive: repository argument required"),
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
opts.RepoArg = args[0]
38+
if runF != nil {
39+
return runF(opts)
40+
}
41+
return archiveRun(opts)
42+
},
43+
}
44+
45+
return cmd
46+
}
47+
48+
func archiveRun(opts *ArchiveOptions) error {
49+
cs := opts.IO.ColorScheme()
50+
httpClient, err := opts.HttpClient()
51+
if err != nil {
52+
return err
53+
}
54+
apiClient := api.NewClientFromHTTP(httpClient)
55+
56+
var toArchive ghrepo.Interface
57+
58+
archiveURL := opts.RepoArg
59+
if !strings.Contains(archiveURL, "/") {
60+
currentUser, err := api.CurrentLoginName(apiClient, ghinstance.Default())
61+
if err != nil {
62+
return err
63+
}
64+
archiveURL = currentUser + "/" + archiveURL
65+
}
66+
toArchive, err = ghrepo.FromFullName(archiveURL)
67+
if err != nil {
68+
return fmt.Errorf("argument error: %w", err)
69+
}
70+
71+
fields := []string{"name", "owner", "isArchived", "id"}
72+
repo, err := api.FetchRepository(apiClient, toArchive, fields)
73+
if err != nil {
74+
return err
75+
}
76+
77+
fullName := ghrepo.FullName(toArchive)
78+
if repo.IsArchived {
79+
fmt.Fprintf(opts.IO.ErrOut,
80+
"%s Repository %s is already archived\n",
81+
cs.WarningIcon(),
82+
fullName)
83+
return nil
84+
}
85+
86+
err = archiveRepo(httpClient, repo)
87+
if err != nil {
88+
return fmt.Errorf("API called failed: %w", err)
89+
}
90+
91+
if opts.IO.IsStdoutTTY() {
92+
fmt.Fprintf(opts.IO.Out,
93+
"%s Archived repository %s\n",
94+
cs.SuccessIcon(),
95+
fullName)
96+
}
97+
98+
return nil
99+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package archive
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/cli/cli/v2/pkg/cmdutil"
9+
"github.com/cli/cli/v2/pkg/httpmock"
10+
"github.com/cli/cli/v2/pkg/iostreams"
11+
"github.com/google/shlex"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
// probably redundant
16+
func TestNewCmdArchive(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
input string
20+
tty bool
21+
output ArchiveOptions
22+
wantErr bool
23+
errMsg string
24+
}{
25+
{
26+
name: "valid repo",
27+
input: "cli/cli",
28+
tty: true,
29+
output: ArchiveOptions{
30+
RepoArg: "cli/cli",
31+
},
32+
},
33+
{
34+
name: "no argument",
35+
input: "",
36+
wantErr: true,
37+
tty: true,
38+
output: ArchiveOptions{
39+
RepoArg: "",
40+
},
41+
},
42+
}
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
io, _, _, _ := iostreams.Test()
46+
io.SetStdinTTY(tt.tty)
47+
io.SetStdoutTTY(tt.tty)
48+
f := &cmdutil.Factory{
49+
IOStreams: io,
50+
}
51+
argv, err := shlex.Split(tt.input)
52+
assert.NoError(t, err)
53+
var gotOpts *ArchiveOptions
54+
cmd := NewCmdArchive(f, func(opts *ArchiveOptions) error {
55+
gotOpts = opts
56+
return nil
57+
})
58+
cmd.SetArgs(argv)
59+
cmd.SetIn(&bytes.Buffer{})
60+
cmd.SetOut(&bytes.Buffer{})
61+
cmd.SetErr(&bytes.Buffer{})
62+
63+
_, err = cmd.ExecuteC()
64+
if tt.wantErr {
65+
assert.Error(t, err)
66+
return
67+
}
68+
assert.NoError(t, err)
69+
assert.Equal(t, tt.output.RepoArg, gotOpts.RepoArg)
70+
})
71+
}
72+
}
73+
74+
func Test_ArchiveRun(t *testing.T) {
75+
tests := []struct {
76+
name string
77+
opts ArchiveOptions
78+
httpStubs func(*httpmock.Registry)
79+
isTTY bool
80+
wantStdout string
81+
wantStderr string
82+
}{
83+
{
84+
name: "unarchived repo tty",
85+
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
86+
wantStdout: "✓ Archived repository OWNER/REPO\n",
87+
isTTY: true,
88+
httpStubs: func(reg *httpmock.Registry) {
89+
reg.Register(
90+
httpmock.GraphQL(`query RepositoryInfo\b`),
91+
httpmock.StringResponse(`{ "data": { "repository": {
92+
"id": "THE-ID",
93+
"isArchived": false} } }`))
94+
reg.Register(
95+
httpmock.GraphQL(`mutation ArchiveRepository\b`),
96+
httpmock.StringResponse(`{}`))
97+
},
98+
},
99+
{
100+
name: "unarchived repo notty",
101+
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
102+
isTTY: false,
103+
httpStubs: func(reg *httpmock.Registry) {
104+
reg.Register(
105+
httpmock.GraphQL(`query RepositoryInfo\b`),
106+
httpmock.StringResponse(`{ "data": { "repository": {
107+
"id": "THE-ID",
108+
"isArchived": false} } }`))
109+
reg.Register(
110+
httpmock.GraphQL(`mutation ArchiveRepository\b`),
111+
httpmock.StringResponse(`{}`))
112+
},
113+
},
114+
{
115+
name: "archived repo tty",
116+
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
117+
wantStderr: "! Repository OWNER/REPO is already archived\n",
118+
isTTY: true,
119+
httpStubs: func(reg *httpmock.Registry) {
120+
reg.Register(
121+
httpmock.GraphQL(`query RepositoryInfo\b`),
122+
httpmock.StringResponse(`{ "data": { "repository": {
123+
"id": "THE-ID",
124+
"isArchived": true } } }`))
125+
},
126+
},
127+
{
128+
name: "archived repo notty",
129+
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
130+
isTTY: false,
131+
wantStderr: "! Repository OWNER/REPO is already archived\n",
132+
httpStubs: func(reg *httpmock.Registry) {
133+
reg.Register(
134+
httpmock.GraphQL(`query RepositoryInfo\b`),
135+
httpmock.StringResponse(`{ "data": { "repository": {
136+
"id": "THE-ID",
137+
"isArchived": true } } }`))
138+
},
139+
},
140+
}
141+
142+
for _, tt := range tests {
143+
reg := &httpmock.Registry{}
144+
if tt.httpStubs != nil {
145+
tt.httpStubs(reg)
146+
}
147+
tt.opts.HttpClient = func() (*http.Client, error) {
148+
return &http.Client{Transport: reg}, nil
149+
}
150+
151+
io, _, stdout, stderr := iostreams.Test()
152+
tt.opts.IO = io
153+
154+
t.Run(tt.name, func(t *testing.T) {
155+
defer reg.Verify(t)
156+
io.SetStdoutTTY(tt.isTTY)
157+
io.SetStderrTTY(tt.isTTY)
158+
159+
err := archiveRun(&tt.opts)
160+
assert.NoError(t, err)
161+
assert.Equal(t, tt.wantStdout, stdout.String())
162+
assert.Equal(t, tt.wantStderr, stderr.String())
163+
})
164+
}
165+
}

pkg/cmd/repo/archive/http.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package archive
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/cli/cli/v2/api"
8+
"github.com/cli/cli/v2/internal/ghinstance"
9+
"github.com/shurcooL/githubv4"
10+
"github.com/shurcooL/graphql"
11+
)
12+
13+
func archiveRepo(client *http.Client, repo *api.Repository) error {
14+
var mutation struct {
15+
ArchiveRepository struct {
16+
Repository struct {
17+
ID string
18+
}
19+
} `graphql:"archiveRepository(input: $input)"`
20+
}
21+
22+
variables := map[string]interface{}{
23+
"input": githubv4.ArchiveRepositoryInput{
24+
RepositoryID: repo.ID,
25+
},
26+
}
27+
28+
host := repo.RepoHost()
29+
gql := graphql.NewClient(ghinstance.GraphQLEndpoint(host), client)
30+
err := gql.MutateNamed(context.Background(), "ArchiveRepository", &mutation, variables)
31+
return err
32+
}

pkg/cmd/repo/repo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package repo
22

33
import (
44
"github.com/MakeNowJust/heredoc"
5+
repoArchiveCmd "github.com/cli/cli/v2/pkg/cmd/repo/archive"
56
repoCloneCmd "github.com/cli/cli/v2/pkg/cmd/repo/clone"
67
repoCreateCmd "github.com/cli/cli/v2/pkg/cmd/repo/create"
78
creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
@@ -42,6 +43,7 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command {
4243
cmd.AddCommand(repoSyncCmd.NewCmdSync(f, nil))
4344
cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil))
4445
cmd.AddCommand(gardenCmd.NewCmdGarden(f, nil))
46+
cmd.AddCommand(repoArchiveCmd.NewCmdArchive(f, nil))
4547

4648
return cmd
4749
}

pkg/cmd/repo/view/http.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,6 @@ import (
1212

1313
var NotFoundError = errors.New("not found")
1414

15-
func fetchRepository(apiClient *api.Client, repo ghrepo.Interface, fields []string) (*api.Repository, error) {
16-
query := fmt.Sprintf(`query RepositoryInfo($owner: String!, $name: String!) {
17-
repository(owner: $owner, name: $name) {%s}
18-
}`, api.RepositoryGraphQL(fields))
19-
20-
variables := map[string]interface{}{
21-
"owner": repo.RepoOwner(),
22-
"name": repo.RepoName(),
23-
}
24-
25-
var result struct {
26-
Repository api.Repository
27-
}
28-
if err := apiClient.GraphQL(repo.RepoHost(), query, variables, &result); err != nil {
29-
return nil, err
30-
}
31-
return api.InitRepoHostname(&result.Repository, repo.RepoHost()), nil
32-
}
33-
3415
type RepoReadme struct {
3516
Filename string
3617
Content string

pkg/cmd/repo/view/view.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func viewRun(opts *ViewOptions) error {
111111
fields = opts.Exporter.Fields()
112112
}
113113

114-
repo, err := fetchRepository(apiClient, toView, fields)
114+
repo, err := api.FetchRepository(apiClient, toView, fields)
115115
if err != nil {
116116
return err
117117
}

0 commit comments

Comments
 (0)
X Tutup