Skip to content

Commit 8eed915

Browse files
authored
Support dest and gitflags in forge repo clone (#101)
* Support dest and gitflags in `forge repo clone` Fix #98 * Review feedback
1 parent c810120 commit 8eed915

2 files changed

Lines changed: 60 additions & 13 deletions

File tree

internal/cli/repo.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ var repoCmd = &cobra.Command{
2929
// gitCloneArgs builds the argv for git clone. The -- separator stops git
3030
// from parsing a server-supplied CloneURL as an option (a malicious forge
3131
// could otherwise return something like --upload-pack=...).
32-
func gitCloneArgs(url string) []string {
33-
return []string{"clone", "--", url}
32+
func gitCloneArgs(url string, dest string, gitFlags []string) []string {
33+
args := append([]string{"clone"}, gitFlags...)
34+
args = append(args, "--", url)
35+
if dest != "" {
36+
args = append(args, dest)
37+
}
38+
return args
3439
}
3540

3641
func init() {
@@ -274,7 +279,7 @@ func repoCreateCmd() *cobra.Command {
274279
_, _ = fmt.Fprintf(os.Stdout, "%s\n", repo.HTMLURL)
275280

276281
if flagClone && repo.CloneURL != "" {
277-
cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(repo.CloneURL)...)
282+
cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(repo.CloneURL, "", nil)...)
278283
cloneCmd.Stdout = os.Stdout
279284
cloneCmd.Stderr = os.Stderr
280285
return cloneCmd.Run()
@@ -442,7 +447,7 @@ func repoForkCmd() *cobra.Command {
442447
_, _ = fmt.Fprintf(os.Stdout, "%s\n", r.HTMLURL)
443448

444449
if flagClone && r.CloneURL != "" {
445-
cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(r.CloneURL)...)
450+
cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(r.CloneURL, "", nil)...)
446451
cloneCmd.Stdout = os.Stdout
447452
cloneCmd.Stderr = os.Stderr
448453
return cloneCmd.Run()
@@ -460,11 +465,32 @@ func repoForkCmd() *cobra.Command {
460465

461466
func repoCloneCmd() *cobra.Command {
462467
return &cobra.Command{
463-
Use: "clone <OWNER/REPO>",
468+
Use: "clone <OWNER/REPO> [PATH] [-- <gitflags>...]",
464469
Short: "Clone a repository locally",
465-
Args: cobra.ExactArgs(1),
466470
RunE: func(cmd *cobra.Command, args []string) error {
467-
forge, owner, repoName, _, err := resolve.Repo(args[0], flagForgeType)
471+
dashIdx := cmd.ArgsLenAtDash()
472+
positional := len(args)
473+
if dashIdx != -1 {
474+
positional = dashIdx
475+
}
476+
if positional < 1 {
477+
return fmt.Errorf("repository argument required")
478+
}
479+
if positional > 2 {
480+
return fmt.Errorf("accepts at most 2 arg(s) before --, received %d", positional)
481+
}
482+
483+
repoArg := args[0]
484+
var dest string
485+
if positional > 1 {
486+
dest = args[1]
487+
}
488+
var gitFlags []string
489+
if dashIdx != -1 {
490+
gitFlags = args[dashIdx:]
491+
}
492+
493+
forge, owner, repoName, _, err := resolve.Repo(repoArg, flagForgeType)
468494
if err != nil {
469495
return err
470496
}
@@ -479,7 +505,7 @@ func repoCloneCmd() *cobra.Command {
479505
cloneURL = r.HTMLURL + ".git"
480506
}
481507

482-
cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(cloneURL)...)
508+
cloneCmd := exec.CommandContext(cmd.Context(), "git", gitCloneArgs(cloneURL, dest, gitFlags)...)
483509
cloneCmd.Stdout = os.Stdout
484510
cloneCmd.Stderr = os.Stderr
485511
return cloneCmd.Run()

internal/cli/repo_test.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@ func TestDomainFromFlags(t *testing.T) {
111111

112112
func TestGitCloneArgs(t *testing.T) {
113113
tests := []struct {
114-
name string
115-
url string
116-
want []string
114+
name string
115+
url string
116+
dest string
117+
gitFlags []string
118+
want []string
117119
}{
118120
{
119121
name: "https url",
@@ -130,13 +132,32 @@ func TestGitCloneArgs(t *testing.T) {
130132
url: "--upload-pack=evil",
131133
want: []string{"clone", "--", "--upload-pack=evil"},
132134
},
135+
{
136+
name: "with destination",
137+
url: "https://github.com/owner/repo.git",
138+
dest: "myrepo",
139+
want: []string{"clone", "--", "https://github.com/owner/repo.git", "myrepo"},
140+
},
141+
{
142+
name: "with git flags",
143+
url: "https://github.com/owner/repo.git",
144+
gitFlags: []string{"--depth", "1"},
145+
want: []string{"clone", "--depth", "1", "--", "https://github.com/owner/repo.git"},
146+
},
147+
{
148+
name: "with destination and git flags",
149+
url: "https://github.com/owner/repo.git",
150+
dest: "myrepo",
151+
gitFlags: []string{"--bare"},
152+
want: []string{"clone", "--bare", "--", "https://github.com/owner/repo.git", "myrepo"},
153+
},
133154
}
134155

135156
for _, tt := range tests {
136157
t.Run(tt.name, func(t *testing.T) {
137-
got := gitCloneArgs(tt.url)
158+
got := gitCloneArgs(tt.url, tt.dest, tt.gitFlags)
138159
if !slices.Equal(got, tt.want) {
139-
t.Errorf("gitCloneArgs(%q) = %v, want %v", tt.url, got, tt.want)
160+
t.Errorf("gitCloneArgs(%q, %q, %v) = %v, want %v", tt.url, tt.dest, tt.gitFlags, got, tt.want)
140161
}
141162

142163
// The url must appear after the -- separator so git cannot

0 commit comments

Comments
 (0)