Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions pkg/cmd/repo/setdefault/setdefault.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,6 @@ func NewCmdSetDefault(f *cmdutil.Factory, runF func(*SetDefaultOptions) error) *
`),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
var err error
opts.Repo, err = ghrepo.FromFullName(args[0])
if err != nil {
return err
}
}

Comment on lines -81 to -88
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to move processing opts.Repo out of RunE and into setDefaultRun?

The spf13/cobra command hooks should be focused on translating cobra arguments and flags into variables that the application logic cares about, however this is no longer as simple as turning owner/repo into ghrepo.Interface. With the changes as-is, we're duplicating logic around remotes and starting to do more application-level behavior outside of the application code.

It might be cleaner and easier to test by changing opts.Repo to a string like opts.RepoOrRemote and defer checking remotes or parsing this as a repo until later in the logic:

if opts.Repo != nil {
for _, knownRepo := range knownRepos {
if ghrepo.IsSame(opts.Repo, knownRepo) {
selectedRepo = opts.Repo
break
}
}
if selectedRepo == nil {
return fmt.Errorf("%s does not correspond to any git remotes", ghrepo.FullName(opts.Repo))
}
}

  1. Error handling around remotes happened earlier in the logic
  2. Application has remote and resolved remote information
  3. RunE logic keeps this logic within the testable code

if !opts.ViewMode && !opts.IO.CanPrompt() && opts.Repo == nil {
return cmdutil.FlagErrorf("repository required when not running interactively")
}
Expand All @@ -96,6 +88,14 @@ func NewCmdSetDefault(f *cmdutil.Factory, runF func(*SetDefaultOptions) error) *
return errors.New("must be run from inside a git repository")
}

if len(args) > 0 {
var err error
opts.Repo, err = parseRepo(args[0], opts)
if err != nil {
return err
}
}

Comment on lines +91 to +98
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this logic needed to be moved to later within RunE? Does this create a problem above because opts.Repo is unset?

Depending on the motivation for moving the logic, this might have to be undone as non-interactive usage will break because opts.Repo is unset.

RunE: func(cmd *cobra.Command, args []string) error {
if !opts.ViewMode && !opts.IO.CanPrompt() && opts.Repo == nil {
return cmdutil.FlagErrorf("repository required when not running interactively")
}

if runF != nil {
return runF(opts)
}
Expand Down Expand Up @@ -252,3 +252,22 @@ func displayRemoteRepoName(remote *context.Remote) string {

return ghrepo.FullName(repo)
}

func parseRepo(name string, opts *SetDefaultOptions) (ghrepo.Interface, error) {
if repo, err := ghrepo.FromFullName(name); err == nil {
return repo, nil
}

remotes, err := opts.Remotes()
if err != nil {
return nil, err
}
Comment on lines +261 to +264
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My major concern with the code as-is is that multiple calls to resolve git remotes via opts.Remotes() does not reuse previously cached results as stated in #10103.

Perhaps the expense isn't significant enough to move forward here. It is mostly a question whether instead of providing a function to resolve the remotes if an actual remote resolver is provided 🤔


for _, remote := range remotes {
if remote.Name == name {
return remote.Repo, nil
}
}

return nil, fmt.Errorf(`expected the "[HOST/]OWNER/REPO" format or a remote name, got %q`, name)
}
114 changes: 88 additions & 26 deletions pkg/cmd/repo/setdefault/setdefault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func TestNewCmdSetDefault(t *testing.T) {
tests := []struct {
name string
gitStubs func(*run.CommandStubber)
remotes []*context.Remote
input string
output SetDefaultOptions
wantErr bool
Expand All @@ -31,45 +32,104 @@ func TestNewCmdSetDefault(t *testing.T) {
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
input: "",
output: SetDefaultOptions{},
remotes: []*context.Remote{},
input: "",
output: SetDefaultOptions{},
},
{
name: "repo argument",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
input: "cli/cli",
output: SetDefaultOptions{Repo: ghrepo.New("cli", "cli")},
remotes: []*context.Remote{},
input: "cli/cli",
output: SetDefaultOptions{Repo: ghrepo.New("cli", "cli")},
},
{
name: "invalid repo argument",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
remotes: []*context.Remote{},
input: "some_invalid_format",
wantErr: true,
errMsg: `expected the "[HOST/]OWNER/REPO" format or a remote name, got "some_invalid_format"`,
},
{
name: "invalid repo argument",
gitStubs: func(cs *run.CommandStubber) {},
input: "some_invalid_format",
wantErr: true,
errMsg: `expected the "[HOST/]OWNER/REPO" format, got "some_invalid_format"`,
name: "repo argument is first remote name",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
remotes: []*context.Remote{
{
Remote: &git.Remote{Name: "origin"},
Repo: ghrepo.New("OWNER", "REPO"),
},
},
input: "origin",
output: SetDefaultOptions{Repo: ghrepo.New("OWNER", "REPO")},
},
{
name: "repo argument is arbitrary remote name",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
remotes: []*context.Remote{
{
Remote: &git.Remote{Name: "origin"},
Repo: ghrepo.New("OWNER", "REPO"),
},
{
Remote: &git.Remote{Name: "upstream"},
Repo: ghrepo.New("OWNER2", "REPO2"),
},
{
Remote: &git.Remote{Name: "other"},
Repo: ghrepo.New("OWNER3", "REPO3"),
},
},
input: "upstream",
output: SetDefaultOptions{Repo: ghrepo.New("OWNER2", "REPO2")},
},
{
name: "repo argument is a remote name, but no such remote exists",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
remotes: []*context.Remote{
{
Remote: &git.Remote{Name: "origin"},
Repo: ghrepo.New("OWNER", "REPO"),
},
},
input: "upstream",
wantErr: true,
errMsg: `expected the "[HOST/]OWNER/REPO" format or a remote name, got "upstream"`,
},
{
name: "view flag",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
input: "--view",
output: SetDefaultOptions{ViewMode: true},
remotes: []*context.Remote{},
input: "--view",
output: SetDefaultOptions{ViewMode: true},
},
{
name: "unset flag",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 0, ".git")
},
input: "--unset",
output: SetDefaultOptions{UnsetMode: true},
input: "--unset",
remotes: []*context.Remote{},
output: SetDefaultOptions{UnsetMode: true},
},
{
name: "run from non-git directory",
gitStubs: func(cs *run.CommandStubber) {
cs.Register(`git rev-parse --git-dir`, 128, "")
},
remotes: []*context.Remote{},
input: "",
wantErr: true,
errMsg: "must be run from inside a git repository",
Expand All @@ -81,24 +141,26 @@ func TestNewCmdSetDefault(t *testing.T) {
io.SetStdoutTTY(true)
io.SetStdinTTY(true)
io.SetStderrTTY(true)
f := &cmdutil.Factory{
IOStreams: io,
GitClient: &git.Client{GitPath: "/fake/path/to/git"},
}

var gotOpts *SetDefaultOptions
cmd := NewCmdSetDefault(f, func(opts *SetDefaultOptions) error {
gotOpts = opts
return nil
})
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})

t.Run(tt.name, func(t *testing.T) {
argv, err := shlex.Split(tt.input)
assert.NoError(t, err)

f := &cmdutil.Factory{
IOStreams: io,
GitClient: &git.Client{GitPath: "/fake/path/to/git"},
Remotes: func() (context.Remotes, error) { return tt.remotes, nil },
}

var gotOpts *SetDefaultOptions
cmd := NewCmdSetDefault(f, func(opts *SetDefaultOptions) error {
gotOpts = opts
return nil
})
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})

cmd.SetArgs(argv)

cs, teardown := run.Stub()
Expand Down