Skip to content

Commit d25ec72

Browse files
committed
Add repo clone <repo> command
1 parent 7950023 commit d25ec72

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

command/repo.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package command
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67

8+
"github.com/cli/cli/git"
79
"github.com/cli/cli/internal/ghrepo"
810
"github.com/cli/cli/utils"
911
"github.com/spf13/cobra"
1012
)
1113

1214
func init() {
1315
RootCmd.AddCommand(repoCmd)
16+
repoCmd.AddCommand(repoCloneCmd)
1417
repoCmd.AddCommand(repoViewCmd)
1518
}
1619

@@ -24,6 +27,16 @@ A repository can be supplied as an argument in any of the following formats:
2427
- by URL, e.g. "https://github.com/OWNER/REPO"`,
2528
}
2629

30+
var repoCloneCmd = &cobra.Command{
31+
Use: "clone <repo>",
32+
Args: cobra.MinimumNArgs(1),
33+
Short: "Clone a repository locally",
34+
Long: `Clone a GitHub repository locally.
35+
36+
To pass 'git clone' options, separate them with '--'.`,
37+
RunE: repoClone,
38+
}
39+
2740
var repoViewCmd = &cobra.Command{
2841
Use: "view [<repo>]",
2942
Short: "View a repository in the browser",
@@ -33,6 +46,23 @@ With no argument, the repository for the current directory is opened.`,
3346
RunE: repoView,
3447
}
3548

49+
func repoClone(cmd *cobra.Command, args []string) error {
50+
cloneURL := args[0]
51+
if !strings.Contains(cloneURL, ":") {
52+
cloneURL = fmt.Sprintf("https://github.com/%s.git", cloneURL)
53+
}
54+
55+
cloneArgs := []string{"clone"}
56+
cloneArgs = append(cloneArgs, args[1:]...)
57+
cloneArgs = append(cloneArgs, cloneURL)
58+
59+
cloneCmd := git.GitCommand(cloneArgs...)
60+
cloneCmd.Stdin = os.Stdin
61+
cloneCmd.Stdout = os.Stdout
62+
cloneCmd.Stderr = os.Stderr
63+
return utils.PrepareCmd(cloneCmd).Run()
64+
}
65+
3666
func repoView(cmd *cobra.Command, args []string) error {
3767
ctx := contextForCommand(cmd)
3868

command/repo_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,65 @@ package command
22

33
import (
44
"os/exec"
5+
"strings"
56
"testing"
67

78
"github.com/cli/cli/context"
89
"github.com/cli/cli/utils"
910
)
1011

12+
func TestRepoClone(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
args string
16+
want string
17+
}{
18+
{
19+
name: "shorthand",
20+
args: "repo clone OWNER/REPO",
21+
want: "git clone https://github.com/OWNER/REPO.git",
22+
},
23+
{
24+
name: "clone arguments",
25+
args: "repo clone OWNER/REPO -- -o upstream --depth 1",
26+
want: "git clone -o upstream --depth 1 https://github.com/OWNER/REPO.git",
27+
},
28+
{
29+
name: "HTTPS URL",
30+
args: "repo clone https://github.com/OWNER/REPO",
31+
want: "git clone https://github.com/OWNER/REPO",
32+
},
33+
{
34+
name: "SSH URL",
35+
args: "repo clone git@github.com:OWNER/REPO.git",
36+
want: "git clone git@github.com:OWNER/REPO.git",
37+
},
38+
}
39+
for _, tt := range tests {
40+
t.Run(tt.name, func(t *testing.T) {
41+
var seenCmd *exec.Cmd
42+
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
43+
seenCmd = cmd
44+
return &outputStub{}
45+
})
46+
defer restoreCmd()
47+
48+
output, err := RunCommand(repoViewCmd, tt.args)
49+
if err != nil {
50+
t.Fatalf("error running command `repo clone`: %v", err)
51+
}
52+
53+
eq(t, output.String(), "")
54+
eq(t, output.Stderr(), "")
55+
56+
if seenCmd == nil {
57+
t.Fatal("expected a command to run")
58+
}
59+
eq(t, strings.Join(seenCmd.Args, " "), tt.want)
60+
})
61+
}
62+
}
63+
1164
func TestRepoView(t *testing.T) {
1265
initBlankContext("OWNER/REPO", "master")
1366
http := initFakeHTTP()

0 commit comments

Comments
 (0)