From 9cfeafc2c300b47bed8022c4199eafa91882bf8d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 24 May 2026 16:18:19 +0200 Subject: [PATCH 1/2] Support dest and gitflags in `forge repo clone` Fix #98 --- internal/cli/repo.go | 38 ++++++++++++++++++++++++++++++-------- internal/cli/repo_test.go | 31 ++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/internal/cli/repo.go b/internal/cli/repo.go index 1edf80e..a978d57 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,28 @@ 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), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - forge, owner, repoName, _, err := resolve.Repo(args[0], flagForgeType) + var repoArg, dest string + var gitFlags []string + + dashIdx := cmd.ArgsLenAtDash() + if dashIdx == -1 { + repoArg = args[0] + if len(args) > 1 { + dest = args[1] + } + } else { + repoArg = args[0] + if dashIdx > 1 { + dest = args[1] + } + gitFlags = args[dashIdx:] + } + + forge, owner, repoName, _, err := resolve.Repo(repoArg, flagForgeType) if err != nil { return err } @@ -479,7 +501,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 From eb3ef7edf53cb6f20187e4acc27aa9a8ea82a240 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 25 May 2026 12:59:31 +0200 Subject: [PATCH 2/2] Review feedback --- internal/cli/repo.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/internal/cli/repo.go b/internal/cli/repo.go index a978d57..b0627dd 100644 --- a/internal/cli/repo.go +++ b/internal/cli/repo.go @@ -467,22 +467,26 @@ func repoCloneCmd() *cobra.Command { return &cobra.Command{ Use: "clone [PATH] [-- ...]", Short: "Clone a repository locally", - Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - var repoArg, dest string - var gitFlags []string - dashIdx := cmd.ArgsLenAtDash() - if dashIdx == -1 { - repoArg = args[0] - if len(args) > 1 { - dest = args[1] - } - } else { - repoArg = args[0] - if dashIdx > 1 { - dest = args[1] - } + 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:] }