Skip to content

Commit c5c83bf

Browse files
author
Nate Smith
authored
Merge pull request cli#4654 from cli/issue-4590
add base repo resolution to archive
2 parents b5d90e1 + 3513105 commit c5c83bf

File tree

2 files changed

+97
-71
lines changed

2 files changed

+97
-71
lines changed

pkg/cmd/repo/archive/archive.go

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ import (
55
"net/http"
66
"strings"
77

8+
"github.com/AlecAivazis/survey/v2"
9+
"github.com/MakeNowJust/heredoc"
810
"github.com/cli/cli/v2/api"
911
"github.com/cli/cli/v2/internal/config"
1012
"github.com/cli/cli/v2/internal/ghrepo"
1113
"github.com/cli/cli/v2/pkg/cmdutil"
14+
"github.com/cli/cli/v2/pkg/prompt"
1215

1316
"github.com/cli/cli/v2/pkg/iostreams"
1417
"github.com/spf13/cobra"
1518
)
1619

1720
type ArchiveOptions struct {
1821
HttpClient func() (*http.Client, error)
19-
IO *iostreams.IOStreams
2022
Config func() (config.Config, error)
23+
BaseRepo func() (ghrepo.Interface, error)
24+
Confirmed bool
25+
IO *iostreams.IOStreams
2126
RepoArg string
2227
}
2328

@@ -26,24 +31,32 @@ func NewCmdArchive(f *cmdutil.Factory, runF func(*ArchiveOptions) error) *cobra.
2631
IO: f.IOStreams,
2732
HttpClient: f.HttpClient,
2833
Config: f.Config,
34+
BaseRepo: f.BaseRepo,
2935
}
3036

3137
cmd := &cobra.Command{
32-
DisableFlagsInUseLine: true,
33-
34-
Use: "archive <repository>",
38+
Use: "archive [<repository>]",
3539
Short: "Archive a repository",
36-
Long: "Archive a GitHub repository.",
37-
Args: cmdutil.ExactArgs(1, "cannot archive: repository argument required"),
40+
Long: heredoc.Doc(`Archive a GitHub repository.
41+
42+
With no argument, archives the current repository.`),
43+
Args: cobra.MaximumNArgs(1),
3844
RunE: func(cmd *cobra.Command, args []string) error {
39-
opts.RepoArg = args[0]
45+
if len(args) > 0 {
46+
opts.RepoArg = args[0]
47+
}
48+
49+
if !opts.Confirmed && !opts.IO.CanPrompt() {
50+
return cmdutil.FlagErrorf("--confirm required when not running interactively")
51+
}
4052
if runF != nil {
4153
return runF(opts)
4254
}
4355
return archiveRun(opts)
4456
},
4557
}
4658

59+
cmd.Flags().BoolVarP(&opts.Confirmed, "confirm", "y", false, "Skip the confirmation prompt")
4760
return cmd
4861
}
4962

@@ -57,26 +70,35 @@ func archiveRun(opts *ArchiveOptions) error {
5770

5871
var toArchive ghrepo.Interface
5972

60-
archiveURL := opts.RepoArg
61-
if !strings.Contains(archiveURL, "/") {
62-
cfg, err := opts.Config()
73+
if opts.RepoArg == "" {
74+
toArchive, err = opts.BaseRepo()
6375
if err != nil {
6476
return err
6577
}
66-
hostname, err := cfg.DefaultHost()
67-
if err != nil {
68-
return err
78+
} else {
79+
repoSelector := opts.RepoArg
80+
if !strings.Contains(repoSelector, "/") {
81+
cfg, err := opts.Config()
82+
if err != nil {
83+
return err
84+
}
85+
86+
hostname, err := cfg.DefaultHost()
87+
if err != nil {
88+
return err
89+
}
90+
91+
currentUser, err := api.CurrentLoginName(apiClient, hostname)
92+
if err != nil {
93+
return err
94+
}
95+
repoSelector = currentUser + "/" + repoSelector
6996
}
7097

71-
currentUser, err := api.CurrentLoginName(apiClient, hostname)
98+
toArchive, err = ghrepo.FromFullName(repoSelector)
7299
if err != nil {
73100
return err
74101
}
75-
archiveURL = currentUser + "/" + archiveURL
76-
}
77-
toArchive, err = ghrepo.FromFullName(archiveURL)
78-
if err != nil {
79-
return fmt.Errorf("argument error: %w", err)
80102
}
81103

82104
fields := []string{"name", "owner", "isArchived", "id"}
@@ -87,16 +109,27 @@ func archiveRun(opts *ArchiveOptions) error {
87109

88110
fullName := ghrepo.FullName(toArchive)
89111
if repo.IsArchived {
90-
fmt.Fprintf(opts.IO.ErrOut,
91-
"%s Repository %s is already archived\n",
92-
cs.WarningIcon(),
93-
fullName)
112+
fmt.Fprintf(opts.IO.ErrOut, "%s Repository %s is already archived\n", cs.WarningIcon(), fullName)
94113
return nil
95114
}
96115

116+
if !opts.Confirmed {
117+
p := &survey.Confirm{
118+
Message: fmt.Sprintf("Archive %s?", fullName),
119+
Default: false,
120+
}
121+
err = prompt.SurveyAskOne(p, &opts.Confirmed)
122+
if err != nil {
123+
return fmt.Errorf("failed to prompt: %w", err)
124+
}
125+
if !opts.Confirmed {
126+
return cmdutil.CancelError
127+
}
128+
}
129+
97130
err = archiveRepo(httpClient, repo)
98131
if err != nil {
99-
return fmt.Errorf("API called failed: %w", err)
132+
return err
100133
}
101134

102135
if opts.IO.IsStdoutTTY() {

pkg/cmd/repo/archive/archive_test.go

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,42 @@ package archive
22

33
import (
44
"bytes"
5+
"fmt"
56
"net/http"
67
"testing"
78

9+
"github.com/cli/cli/v2/internal/ghrepo"
810
"github.com/cli/cli/v2/pkg/cmdutil"
911
"github.com/cli/cli/v2/pkg/httpmock"
1012
"github.com/cli/cli/v2/pkg/iostreams"
13+
"github.com/cli/cli/v2/pkg/prompt"
1114
"github.com/google/shlex"
1215
"github.com/stretchr/testify/assert"
1316
)
1417

15-
// probably redundant
1618
func TestNewCmdArchive(t *testing.T) {
1719
tests := []struct {
1820
name string
1921
input string
20-
tty bool
21-
output ArchiveOptions
2222
wantErr bool
23+
output ArchiveOptions
2324
errMsg string
2425
}{
2526
{
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",
27+
name: "no arguments no tty",
3528
input: "",
29+
errMsg: "--confirm required when not running interactively",
3630
wantErr: true,
37-
tty: true,
38-
output: ArchiveOptions{
39-
RepoArg: "",
40-
},
31+
},
32+
{
33+
name: "repo argument tty",
34+
input: "OWNER/REPO --confirm",
35+
output: ArchiveOptions{RepoArg: "OWNER/REPO", Confirmed: true},
4136
},
4237
}
4338
for _, tt := range tests {
4439
t.Run(tt.name, func(t *testing.T) {
4540
io, _, _, _ := iostreams.Test()
46-
io.SetStdinTTY(tt.tty)
47-
io.SetStdoutTTY(tt.tty)
4841
f := &cmdutil.Factory{
4942
IOStreams: io,
5043
}
@@ -67,90 +60,90 @@ func TestNewCmdArchive(t *testing.T) {
6760
}
6861
assert.NoError(t, err)
6962
assert.Equal(t, tt.output.RepoArg, gotOpts.RepoArg)
63+
assert.Equal(t, tt.output.Confirmed, gotOpts.Confirmed)
7064
})
7165
}
7266
}
7367

7468
func Test_ArchiveRun(t *testing.T) {
69+
queryResponse := `{ "data": { "repository": { "id": "THE-ID","isArchived": %s} } }`
7570
tests := []struct {
7671
name string
7772
opts ArchiveOptions
7873
httpStubs func(*httpmock.Registry)
74+
askStubs func(*prompt.AskStubber)
7975
isTTY bool
8076
wantStdout string
8177
wantStderr string
8278
}{
8379
{
8480
name: "unarchived repo tty",
85-
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
8681
wantStdout: "✓ Archived repository OWNER/REPO\n",
87-
isTTY: true,
82+
askStubs: func(q *prompt.AskStubber) {
83+
q.StubOne(true)
84+
},
85+
isTTY: true,
86+
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
8887
httpStubs: func(reg *httpmock.Registry) {
8988
reg.Register(
9089
httpmock.GraphQL(`query RepositoryInfo\b`),
91-
httpmock.StringResponse(`{ "data": { "repository": {
92-
"id": "THE-ID",
93-
"isArchived": false} } }`))
90+
httpmock.StringResponse(fmt.Sprintf(queryResponse, "false")))
9491
reg.Register(
9592
httpmock.GraphQL(`mutation ArchiveRepository\b`),
9693
httpmock.StringResponse(`{}`))
9794
},
9895
},
9996
{
100-
name: "unarchived repo notty",
101-
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
102-
isTTY: false,
97+
name: "infer base repo",
98+
wantStdout: "✓ Archived repository OWNER/REPO\n",
99+
opts: ArchiveOptions{},
100+
askStubs: func(q *prompt.AskStubber) {
101+
q.StubOne(true)
102+
},
103+
isTTY: true,
103104
httpStubs: func(reg *httpmock.Registry) {
104105
reg.Register(
105106
httpmock.GraphQL(`query RepositoryInfo\b`),
106-
httpmock.StringResponse(`{ "data": { "repository": {
107-
"id": "THE-ID",
108-
"isArchived": false} } }`))
107+
httpmock.StringResponse(fmt.Sprintf(queryResponse, "false")))
109108
reg.Register(
110109
httpmock.GraphQL(`mutation ArchiveRepository\b`),
111110
httpmock.StringResponse(`{}`))
112111
},
113112
},
114113
{
115114
name: "archived repo tty",
116-
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
117115
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",
129116
opts: ArchiveOptions{RepoArg: "OWNER/REPO"},
130-
isTTY: false,
131-
wantStderr: "! Repository OWNER/REPO is already archived\n",
132117
httpStubs: func(reg *httpmock.Registry) {
133118
reg.Register(
134119
httpmock.GraphQL(`query RepositoryInfo\b`),
135-
httpmock.StringResponse(`{ "data": { "repository": {
136-
"id": "THE-ID",
137-
"isArchived": true } } }`))
120+
httpmock.StringResponse(fmt.Sprintf(queryResponse, "true")))
138121
},
139122
},
140123
}
141124

142125
for _, tt := range tests {
126+
repo, _ := ghrepo.FromFullName("OWNER/REPO")
143127
reg := &httpmock.Registry{}
144128
if tt.httpStubs != nil {
145129
tt.httpStubs(reg)
146130
}
131+
132+
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
133+
return repo, nil
134+
}
147135
tt.opts.HttpClient = func() (*http.Client, error) {
148136
return &http.Client{Transport: reg}, nil
149137
}
150-
151138
io, _, stdout, stderr := iostreams.Test()
152139
tt.opts.IO = io
153140

141+
q, teardown := prompt.InitAskStubber()
142+
defer teardown()
143+
if tt.askStubs != nil {
144+
tt.askStubs(q)
145+
}
146+
154147
t.Run(tt.name, func(t *testing.T) {
155148
defer reg.Verify(t)
156149
io.SetStdoutTTY(tt.isTTY)

0 commit comments

Comments
 (0)