Skip to content
Merged
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
42 changes: 34 additions & 8 deletions internal/cli/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -460,11 +465,32 @@ func repoForkCmd() *cobra.Command {

func repoCloneCmd() *cobra.Command {
return &cobra.Command{
Use: "clone <OWNER/REPO>",
Use: "clone <OWNER/REPO> [PATH] [-- <gitflags>...]",
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
}
Expand All @@ -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()
Expand Down
31 changes: 26 additions & 5 deletions internal/cli/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand Down
Loading