diff --git a/internal/cli/repo.go b/internal/cli/repo.go index 1edf80e..b0627dd 100644 --- a/internal/cli/repo.go +++ b/internal/cli/repo.go @@ -29,8 +29,13 @@ var repoCmd = &cobra.Command{ // gitCloneArgs builds the argv for git clone. The -- separator stops git // from parsing a server-supplied CloneURL as an option (a malicious forge // could otherwise return something like --upload-pack=...). -func gitCloneArgs(url string) []string { - return []string{"clone", "--", url} +func gitCloneArgs(url string, dest string, gitFlags []string) []string { + args := append([]string{"clone"}, gitFlags...) + args = append(args, "--", url) + if dest != "" { + args = append(args, dest) + } + return args } func init() { @@ -274,7 +279,7 @@ func repoCreateCmd() *cobra.Command { _, _ = fmt.Fprintf(os.Stdout, "%s\n", repo.HTMLURL) if flagClone && repo.CloneURL != "" { - cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(repo.CloneURL)...) + cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(repo.CloneURL, "", nil)...) cloneCmd.Stdout = os.Stdout cloneCmd.Stderr = os.Stderr return cloneCmd.Run() @@ -442,7 +447,7 @@ func repoForkCmd() *cobra.Command { _, _ = fmt.Fprintf(os.Stdout, "%s\n", r.HTMLURL) if flagClone && r.CloneURL != "" { - cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(r.CloneURL)...) + cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(r.CloneURL, "", nil)...) cloneCmd.Stdout = os.Stdout cloneCmd.Stderr = os.Stderr return cloneCmd.Run() @@ -460,11 +465,32 @@ func repoForkCmd() *cobra.Command { func repoCloneCmd() *cobra.Command { return &cobra.Command{ - Use: "clone ", + Use: "clone [PATH] [-- ...]", Short: "Clone a repository locally", - Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - forge, owner, repoName, _, err := resolve.Repo(args[0], flagForgeType) + dashIdx := cmd.ArgsLenAtDash() + positional := len(args) + if dashIdx != -1 { + positional = dashIdx + } + if positional < 1 { + return fmt.Errorf("repository argument required") + } + if positional > 2 { + return fmt.Errorf("accepts at most 2 arg(s) before --, received %d", positional) + } + + repoArg := args[0] + var dest string + if positional > 1 { + dest = args[1] + } + var gitFlags []string + if dashIdx != -1 { + gitFlags = args[dashIdx:] + } + + forge, owner, repoName, _, err := resolve.Repo(repoArg, flagForgeType) if err != nil { return err } @@ -479,7 +505,7 @@ func repoCloneCmd() *cobra.Command { cloneURL = r.HTMLURL + ".git" } - cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(cloneURL)...) + cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(cloneURL, dest, gitFlags)...) cloneCmd.Stdout = os.Stdout cloneCmd.Stderr = os.Stderr return cloneCmd.Run() diff --git a/internal/cli/repo_test.go b/internal/cli/repo_test.go index 3640212..3babd20 100644 --- a/internal/cli/repo_test.go +++ b/internal/cli/repo_test.go @@ -111,9 +111,11 @@ func TestDomainFromFlags(t *testing.T) { func TestGitCloneArgs(t *testing.T) { tests := []struct { - name string - url string - want []string + name string + url string + dest string + gitFlags []string + want []string }{ { name: "https url", @@ -130,13 +132,32 @@ func TestGitCloneArgs(t *testing.T) { url: "--upload-pack=evil", want: []string{"clone", "--", "--upload-pack=evil"}, }, + { + name: "with destination", + url: "https://github.com/owner/repo.git", + dest: "myrepo", + want: []string{"clone", "--", "https://github.com/owner/repo.git", "myrepo"}, + }, + { + name: "with git flags", + url: "https://github.com/owner/repo.git", + gitFlags: []string{"--depth", "1"}, + want: []string{"clone", "--depth", "1", "--", "https://github.com/owner/repo.git"}, + }, + { + name: "with destination and git flags", + url: "https://github.com/owner/repo.git", + dest: "myrepo", + gitFlags: []string{"--bare"}, + want: []string{"clone", "--bare", "--", "https://github.com/owner/repo.git", "myrepo"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := gitCloneArgs(tt.url) + got := gitCloneArgs(tt.url, tt.dest, tt.gitFlags) if !slices.Equal(got, tt.want) { - t.Errorf("gitCloneArgs(%q) = %v, want %v", tt.url, got, tt.want) + t.Errorf("gitCloneArgs(%q, %q, %v) = %v, want %v", tt.url, tt.dest, tt.gitFlags, got, tt.want) } // The url must appear after the -- separator so git cannot