diff --git a/pkg/cmd/repo/rename/rename.go b/pkg/cmd/repo/rename/rename.go index 43bd3c455..2999aa211 100644 --- a/pkg/cmd/repo/rename/rename.go +++ b/pkg/cmd/repo/rename/rename.go @@ -8,6 +8,7 @@ import ( "net/http" "github.com/AlecAivazis/survey/v2" + "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/context" "github.com/cli/cli/v2/git" @@ -25,7 +26,6 @@ type RenameOptions struct { Config func() (config.Config, error) BaseRepo func() (ghrepo.Interface, error) Remotes func() (context.Remotes, error) - oldRepoSelector string newRepoSelector string } @@ -46,32 +46,39 @@ func NewCmdRename(f *cmdutil.Factory, runf func(*RenameOptions) error) *cobra.Co } cmd := &cobra.Command{ - Use: "rename [-R] [] []", + Use: "rename [] []", Short: "Rename a repository", - Long: `Rename a GitHub repository + Long: heredoc.Doc(`Rename a GitHub repository + With no argument, the repository for the current directory is renamed using a prompt + With one argument, the repository of the current directory is renamed using the argument - With '-R', and two arguments the given repository is replaced with the new name `, + + With '-R', and two arguments the given repository is replaced with the new name`), Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { + opts.BaseRepo = f.BaseRepo opts.newRepoSelector = args[0] - } else { - if !opts.IO.CanPrompt() { - return &cmdutil.FlagError{ - Err: errors.New("could not prompt: proceed with prompt")} - } + } else if !opts.IO.CanPrompt() { + return &cmdutil.FlagError{ + Err: errors.New("could not prompt: proceed with a repo name")} + } + + if runf != nil { + return runf(opts) } return renameRun(opts) }, } - cmd.Flags().StringVarP(&opts.oldRepoSelector, "repo", "R", "", "pass in two arguments to rename a repository") + cmdutil.EnableRepoOverride(cmd, f) return cmd } func renameRun(opts *RenameOptions) error { + cs := opts.IO.ColorScheme() httpClient, err := opts.HttpClient() if err != nil { @@ -80,71 +87,48 @@ func renameRun(opts *RenameOptions) error { apiClient := api.NewClientFromHTTP(httpClient) var input renameRepo - var oldRepo ghrepo.Interface var newRepo ghrepo.Interface + var baseRemote *context.Remote + newRepoName := opts.newRepoSelector - if opts.oldRepoSelector == "" { - var newRepoName string - currRepo, err := opts.BaseRepo() + currRepo, err := opts.BaseRepo() + if err != nil { + return err + } + + if opts.newRepoSelector == "" { + err = prompt.SurveyAskOne( + &survey.Input{ + Message: "Rename current repo to: ", + }, + &newRepoName, + ) if err != nil { return err } - - if opts.newRepoSelector != "" && opts.oldRepoSelector == "" { - newRepoName = opts.newRepoSelector - } else { - err = prompt.SurveyAskOne( - &survey.Input{ - Message: "Rename current repo to: ", - }, - &newRepoName, - ) - if err != nil { - return err - } - } - - oldRepo = ghrepo.NewWithHost(currRepo.RepoOwner(), currRepo.RepoName(), currRepo.RepoHost()) - newRepo = ghrepo.NewWithHost(currRepo.RepoOwner(), newRepoName, currRepo.RepoHost()) - - input = renameRepo{ - RepoHost: currRepo.RepoHost(), - RepoOwner: currRepo.RepoOwner(), - RepoName: currRepo.RepoName(), - Name: newRepoName, - } - - } else { - oldRepoURL := opts.oldRepoSelector - newRepoName := opts.newRepoSelector - - currRepo, err := ghrepo.FromFullName(oldRepoURL) - if err != nil { - return fmt.Errorf("argument error: %w", err) - } - - oldRepo = ghrepo.NewWithHost(currRepo.RepoOwner(), currRepo.RepoName(), currRepo.RepoHost()) - - input = renameRepo{ - RepoHost: currRepo.RepoHost(), - RepoOwner: currRepo.RepoOwner(), - RepoName: currRepo.RepoName(), - Name: newRepoName, - } } - err = runRename(apiClient, oldRepo.RepoHost(), input) + input = renameRepo{ + RepoHost: currRepo.RepoHost(), + RepoOwner: currRepo.RepoOwner(), + RepoName: currRepo.RepoName(), + Name: newRepoName, + } + + newRepo = ghrepo.NewWithHost(currRepo.RepoOwner(), newRepoName, currRepo.RepoHost()) + + err = runRename(apiClient, currRepo.RepoHost(), input) if err != nil { return fmt.Errorf("API called failed: %s, please check your parameters", err) } - if opts.oldRepoSelector == "" { + if opts.newRepoSelector == "" { cfg, err := opts.Config() if err != nil { return err } - protocol, err := cfg.Get(oldRepo.RepoHost(), "git_protocol") + protocol, err := cfg.Get(currRepo.RepoHost(), "git_protocol") if err != nil { return err } @@ -154,7 +138,7 @@ func renameRun(opts *RenameOptions) error { return err } - baseRemote, err := remotes.FindByRepo(oldRepo.RepoOwner(), oldRepo.RepoName()) + baseRemote, err = remotes.FindByRepo(currRepo.RepoOwner(), currRepo.RepoName()) if err != nil { return err } @@ -168,8 +152,8 @@ func renameRun(opts *RenameOptions) error { if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.Out, "%s Renamed repository %s\n", cs.SuccessIcon(), input.RepoOwner+"/"+input.Name) - if opts.oldRepoSelector == "" { - fmt.Fprintf(opts.IO.Out, `%s Updated the "origin" remote`, cs.SuccessIcon()) + if opts.newRepoSelector == "" { + fmt.Fprintf(opts.IO.Out, "%s Updated the %q remote", cs.SuccessIcon(), baseRemote.Name) } } diff --git a/pkg/cmd/repo/rename/rename_test.go b/pkg/cmd/repo/rename/rename_test.go index 173a79bc0..f2598abac 100644 --- a/pkg/cmd/repo/rename/rename_test.go +++ b/pkg/cmd/repo/rename/rename_test.go @@ -1,186 +1,193 @@ package rename -// import ( -// "fmt" -// "net/http" -// "testing" +import ( + "bytes" + "net/http" + "testing" -// "github.com/cli/cli/v2/pkg/cmdutil" -// "github.com/cli/cli/v2/internal/ghrepo" -// "github.com/cli/cli/v2/pkg/httpmock" -// "github.com/cli/cli/v2/pkg/iostreams" -// "github.com/cli/cli/v2/pkg/prompt" + "github.com/cli/cli/v2/context" + "github.com/cli/cli/v2/git" + "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/httpmock" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/cli/cli/v2/pkg/prompt" + "github.com/google/shlex" + "github.com/stretchr/testify/assert" +) -// "github.com/google/shlex" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// ) +func TestNewCmdRename(t *testing.T) { + testCases := []struct { + name string + input string + output RenameOptions + errMsg string + tty bool + wantErr bool + }{ + { + name: "no arguments no tty", + input: "", + errMsg: "could not prompt: proceed with prompt", + wantErr: true, + }, + { + name: "one argument", + input: "REPO", + output: RenameOptions{ + newRepoSelector: "REPO", + }, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + io, _, _, _ := iostreams.Test() + io.SetStdinTTY(tt.tty) + io.SetStdoutTTY(tt.tty) + f := &cmdutil.Factory{ + IOStreams: io, + } -// func TestNewCmdRename(t *testing.T) { -// testCases := []struct { -// name string -// args string -// wantOpts RenameOptions -// wantErr string -// }{ -// { -// name: "no arguments", -// args: "", -// wantErr: "cannot rename: repository argument required", -// }, -// { -// name: "correct argument", -// args: "OWNER/REPO REPOS", -// wantOpts: RenameOptions{ -// oldRepoSelector: "OWNER/REPO", -// newRepoSelector: "REPOS", -// }, -// }, -// } -// for _, tt := range testCases { -// t.Run(tt.name, func(t *testing.T) { -// io, stdin, stdout, stderr := iostreams.Test() -// fac := &cmdutil.Factory{IOStreams: io} + argv, err := shlex.Split(tt.input) + assert.NoError(t, err) + var gotOpts *RenameOptions + cmd := NewCmdRename(f, func(opts *RenameOptions) error { + gotOpts = opts + return nil + }) + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) -// var opts *RenameOptions -// cmd := NewCmdRename(fac, func(co *RenameOptions) error { -// opts = co -// return nil -// }) + _, err = cmd.ExecuteC() + if tt.wantErr { + assert.Error(t, err) + assert.Equal(t, tt.errMsg, err.Error()) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.output.newRepoSelector, gotOpts.newRepoSelector) + }) + } +} -// argv, err := shlex.Split(tt.args) -// require.NoError(t, err) -// cmd.SetArgs(argv) +func TestRenameRun(t *testing.T) { + testCases := []struct { + name string + opts RenameOptions + httpStubs func(*httpmock.Registry) + askStubs func(*prompt.AskStubber) + wantOut string + tty bool + prompt bool + }{ + { + name: "none argument", + wantOut: "✓ Renamed repository OWNER/NEW_REPO\n✓ Updated the \"origin\" remote", + askStubs: func(q *prompt.AskStubber) { + q.StubOne("NEW_REPO") + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("PATCH", "repos/OWNER/REPO"), + httpmock.StatusStringResponse(204, "{}")) + }, + tty: true, + }, + { + name: "owner repo change name prompt", + opts: RenameOptions{ + BaseRepo: func() (ghrepo.Interface, error) { + return ghrepo.New("OWNER", "REPO"), nil + }, + }, + wantOut: "✓ Renamed repository OWNER/NEW_REPO\n", + askStubs: func(q *prompt.AskStubber) { + q.StubOne("NEW_REPO") + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("PATCH", "repos/OWNER/REPO"), + httpmock.StatusStringResponse(204, "{}")) + }, + prompt: true, + }, + { + name: "owner repo change name argument ", + opts: RenameOptions{ + newRepoSelector: "REPO", + }, + askStubs: func(q *prompt.AskStubber) { + q.StubOne("OWNER/REPO") + }, + httpStubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { + "data": { + "repository": { + "id": "THE-ID", + "name": "REPO", + "owner": { + "login": "OWNER" + } + } + } + }`)) + reg.Register( + httpmock.REST("PATCH", "repos/OWNER/REPO"), + httpmock.StatusStringResponse(204, "{}")) + }, + }, + } -// cmd.SetIn(stdin) -// cmd.SetOut(stdout) -// cmd.SetErr(stderr) + for _, tt := range testCases { + q, teardown := prompt.InitAskStubber() + defer teardown() + if tt.askStubs != nil { + tt.askStubs(q) + } -// _, err = cmd.ExecuteC() -// if tt.wantErr != "" { -// assert.EqualError(t, err, tt.wantErr) -// return -// } else { -// assert.NoError(t, err) -// } + tt.opts.BaseRepo = func() (ghrepo.Interface, error) { + repo, _ := ghrepo.FromFullName("OWNER/REPO") + return repo, nil + } -// assert.Equal(t, "", stdout.String()) -// assert.Equal(t, "", stderr.String()) + tt.opts.Config = func() (config.Config, error) { + return config.NewBlankConfig(), nil + } -// assert.Equal(t, tt.wantOpts.oldRepoSelector, opts.oldRepoSelector) -// assert.Equal(t, tt.wantOpts.newRepoSelector, opts.newRepoSelector) -// }) -// } -// } + tt.opts.Remotes = func() (context.Remotes, error) { + r, _ := ghrepo.FromFullName("OWNER/REPO") + var remotes context.Remotes + remotes = append(remotes, &context.Remote{ + Remote: &git.Remote{Name: "origin"}, + Repo: r, + }) + return remotes, nil + } -// func TestRenameRun(t *testing.T) { -// testCases := []struct { -// name string -// opts RenameOptions -// httpStubs func(*httpmock.Registry) -// askStubs func(*prompt.AskStubber) -// wantOut string -// tty bool -// prompt bool -// }{ -// { -// name: "owner repo change name using flag", -// opts: RenameOptions{ -// oldRepoSelector: "OWNER/REPO", -// newRepoSelector: "NEW_REPO", -// flagRepo: true, -// }, -// wantOut: "✓ Renamed repository OWNER/NEW_REPO\n", -// httpStubs: func(reg *httpmock.Registry) { -// reg.Register( -// httpmock.GraphQL(`query UserCurrent\b`), -// httpmock.StringResponse(`{"data":{"viewer":{"login":"OWNER"}}}`)) -// reg.Register( -// httpmock.REST("PATCH", "repos/OWNER/REPO"), -// httpmock.StatusStringResponse(204, "{}")) -// }, -// tty: true, -// }, -// { -// name: "owner repo change name prompt", -// opts: RenameOptions{ -// BaseRepo: func() (ghrepo.Interface, error) { -// return ghrepo.New("OWNER", "REPO"), nil -// }, -// oldRepoSelector: "NEW_REPO", -// }, -// wantOut: "✓ Renamed repository OWNER/NEW_REPO\n", -// askStubs: func(q *prompt.AskStubber) { -// q.StubOne("NEW_REPO") -// }, -// httpStubs: func(reg *httpmock.Registry) { -// reg.Register( -// httpmock.REST("PATCH", "repos/OWNER/REPO"), -// httpmock.StatusStringResponse(204, "{}")) -// }, -// prompt: true, -// }, -// { -// name: "owner repo change name argument ", -// opts: RenameOptions{ -// newRepoSelector: "REPO", -// flagRepo: false, -// }, -// askStubs: func(q *prompt.AskStubber) { -// q.StubOne("OWNER/REPO") -// }, -// httpStubs: func(reg *httpmock.Registry) { -// reg.Register( -// httpmock.GraphQL(`query RepositoryInfo\b`), -// httpmock.StringResponse(` -// { -// "data": { -// "repository": { -// "id": "THE-ID", -// "name": "REPO", -// "owner": { -// "login": "OWNER" -// } -// } -// } -// }`)) -// reg.Register( -// httpmock.REST("PATCH", "repos/OWNER/REPO"), -// httpmock.StatusStringResponse(204, "{}")) -// }, -// }, -// } + reg := &httpmock.Registry{} + if tt.httpStubs != nil { + tt.httpStubs(reg) + } + tt.opts.HttpClient = func() (*http.Client, error) { + return &http.Client{Transport: reg}, nil + } -// for _, tt := range testCases { -// q, teardown := prompt.InitAskStubber() -// defer teardown() -// if tt.askStubs != nil { -// tt.askStubs(q) -// } + io, _, stdout, _ := iostreams.Test() + io.SetStdinTTY(tt.tty) + io.SetStdoutTTY(tt.tty) + tt.opts.IO = io -// tt.opts.BaseRepo = func() (ghrepo.Interface, error) { -// repo, _ := ghrepo.FromFullName(tt.opts.oldRepoSelector) -// return repo, nil -// } - -// 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, _ := iostreams.Test() -// io.SetStdinTTY(tt.tty) -// io.SetStdoutTTY(tt.tty) -// tt.opts.IO = io - -// t.Run(tt.name, func(t *testing.T) { -// defer reg.Verify(t) -// err := renameRun(&tt.opts) -// assert.NoError(t, err) -// assert.Equal(t, tt.wantOut, stdout.String()) -// }) -// } -// } + t.Run(tt.name, func(t *testing.T) { + defer reg.Verify(t) + err := renameRun(&tt.opts) + assert.NoError(t, err) + assert.Equal(t, tt.wantOut, stdout.String()) + }) + } +}