diff --git a/command/repo.go b/command/repo.go index afc6ddec7..5e16f9645 100644 --- a/command/repo.go +++ b/command/repo.go @@ -51,12 +51,12 @@ A repository can be supplied as an argument in any of the following formats: } var repoCloneCmd = &cobra.Command{ - Use: "clone ", + Use: "clone []", Args: cobra.MinimumNArgs(1), Short: "Clone a repository locally", Long: `Clone a GitHub repository locally. -To pass 'git clone' options, separate them with '--'.`, +To pass 'git clone' flags, separate them with '--'.`, RunE: repoClone, } @@ -87,6 +87,41 @@ With no argument, the repository for the current directory is displayed.`, RunE: repoView, } +func parseCloneArgs(extraArgs []string) (args []string, target string) { + args = extraArgs + + if len(args) > 0 { + if !strings.HasPrefix(args[0], "-") { + target, args = args[0], args[1:] + } + } + return +} + +func runClone(cloneURL string, args []string) (target string, err error) { + cloneArgs, target := parseCloneArgs(args) + + cloneArgs = append(cloneArgs, cloneURL) + + // If the args contain an explicit target, pass it to clone + // otherwise, parse the URL to determine where git cloned it to so we can return it + if target != "" { + cloneArgs = append(cloneArgs, target) + } else { + target = path.Base(strings.TrimSuffix(cloneURL, ".git")) + } + + cloneArgs = append([]string{"clone"}, cloneArgs...) + + cloneCmd := git.GitCommand(cloneArgs...) + cloneCmd.Stdin = os.Stdin + cloneCmd.Stdout = os.Stdout + cloneCmd.Stderr = os.Stderr + + err = run.PrepareCmd(cloneCmd).Run() + return +} + func repoClone(cmd *cobra.Command, args []string) error { cloneURL := args[0] if !strings.Contains(cloneURL, ":") { @@ -115,21 +150,13 @@ func repoClone(cmd *cobra.Command, args []string) error { } } - cloneArgs := []string{"clone"} - cloneArgs = append(cloneArgs, args[1:]...) - cloneArgs = append(cloneArgs, cloneURL) - - cloneCmd := git.GitCommand(cloneArgs...) - cloneCmd.Stdin = os.Stdin - cloneCmd.Stdout = os.Stdout - cloneCmd.Stderr = os.Stderr - err := run.PrepareCmd(cloneCmd).Run() + cloneDir, err := runClone(cloneURL, args[1:]) if err != nil { return err } if parentRepo != nil { - err := addUpstreamRemote(parentRepo, cloneURL) + err := addUpstreamRemote(parentRepo, cloneDir) if err != nil { return err } @@ -138,10 +165,9 @@ func repoClone(cmd *cobra.Command, args []string) error { return nil } -func addUpstreamRemote(parentRepo ghrepo.Interface, cloneURL string) error { +func addUpstreamRemote(parentRepo ghrepo.Interface, cloneDir string) error { // TODO: support SSH remote URLs upstreamURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(parentRepo)) - cloneDir := path.Base(strings.TrimSuffix(cloneURL, ".git")) cloneCmd := git.GitCommand("-C", cloneDir, "remote", "add", "upstream", upstreamURL) cloneCmd.Stdout = os.Stdout @@ -425,16 +451,12 @@ func repoFork(cmd *cobra.Command, args []string) error { } } if cloneDesired { - cloneCmd := git.GitCommand("clone", forkedRepo.CloneURL) - cloneCmd.Stdin = os.Stdin - cloneCmd.Stdout = os.Stdout - cloneCmd.Stderr = os.Stderr - err = run.PrepareCmd(cloneCmd).Run() + cloneDir, err := runClone(forkedRepo.CloneURL, []string{}) if err != nil { return fmt.Errorf("failed to clone fork: %w", err) } - err = addUpstreamRemote(toFork, forkedRepo.CloneURL) + err = addUpstreamRemote(toFork, cloneDir) if err != nil { return err } diff --git a/command/repo_test.go b/command/repo_test.go index e82d599bb..e52df1313 100644 --- a/command/repo_test.go +++ b/command/repo_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io/ioutil" "os/exec" + "reflect" "regexp" "strings" "testing" @@ -337,6 +338,65 @@ func TestRepoFork_in_parent_survey_no(t *testing.T) { } } +func TestParseExtraArgs(t *testing.T) { + type Wanted struct { + args []string + dir string + } + tests := []struct { + name string + args []string + want Wanted + }{ + { + name: "args and target", + args: []string{"target_directory", "-o", "upstream", "--depth", "1"}, + want: Wanted{ + args: []string{"-o", "upstream", "--depth", "1"}, + dir: "target_directory", + }, + }, + { + name: "only args", + args: []string{"-o", "upstream", "--depth", "1"}, + want: Wanted{ + args: []string{"-o", "upstream", "--depth", "1"}, + dir: "", + }, + }, + { + name: "only target", + args: []string{"target_directory"}, + want: Wanted{ + args: []string{}, + dir: "target_directory", + }, + }, + { + name: "no args", + args: []string{}, + want: Wanted{ + args: []string{}, + dir: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args, dir := parseCloneArgs(tt.args) + got := Wanted{ + args: args, + dir: dir, + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("got %#v want %#v", got, tt.want) + } + }) + } + +} + func TestRepoClone(t *testing.T) { tests := []struct { name string @@ -348,11 +408,21 @@ func TestRepoClone(t *testing.T) { args: "repo clone OWNER/REPO", want: "git clone https://github.com/OWNER/REPO.git", }, + { + name: "shorthand with directory", + args: "repo clone OWNER/REPO target_directory", + want: "git clone https://github.com/OWNER/REPO.git target_directory", + }, { name: "clone arguments", args: "repo clone OWNER/REPO -- -o upstream --depth 1", want: "git clone -o upstream --depth 1 https://github.com/OWNER/REPO.git", }, + { + name: "clone arguments with directory", + args: "repo clone OWNER/REPO target_directory -- -o upstream --depth 1", + want: "git clone -o upstream --depth 1 https://github.com/OWNER/REPO.git target_directory", + }, { name: "HTTPS URL", args: "repo clone https://github.com/OWNER/REPO",