diff --git a/git/git.go b/git/git.go index 31fea6233..50012f49a 100644 --- a/git/git.go +++ b/git/git.go @@ -367,12 +367,12 @@ func RunClone(cloneURL string, args []string) (target string, err error) { return } -func AddUpstreamRemote(upstreamURL, cloneDir string, branches []string) error { - args := []string{"-C", cloneDir, "remote", "add"} +func AddNamedRemote(url, name, dir string, branches []string) error { + args := []string{"-C", dir, "remote", "add"} for _, branch := range branches { args = append(args, "-t", branch) } - args = append(args, "-f", "upstream", upstreamURL) + args = append(args, "-f", name, url) cloneCmd, err := GitCommand(args...) if err != nil { return err diff --git a/git/git_test.go b/git/git_test.go index 979a5e243..4b7d5a74e 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -181,37 +181,40 @@ func TestParseExtraCloneArgs(t *testing.T) { } } -func TestAddUpstreamRemote(t *testing.T) { +func TestAddNamedRemote(t *testing.T) { tests := []struct { - name string - upstreamURL string - cloneDir string - branches []string - want string + title string + name string + url string + dir string + branches []string + want string }{ { - name: "fetch all", - upstreamURL: "URL", - cloneDir: "DIRECTORY", - branches: []string{}, - want: "git -C DIRECTORY remote add -f upstream URL", + title: "fetch all", + name: "test", + url: "URL", + dir: "DIRECTORY", + branches: []string{}, + want: "git -C DIRECTORY remote add -f test URL", }, { - name: "fetch specific branches only", - upstreamURL: "URL", - cloneDir: "DIRECTORY", - branches: []string{"master", "dev"}, - want: "git -C DIRECTORY remote add -t master -t dev -f upstream URL", + title: "fetch specific branches only", + name: "test", + url: "URL", + dir: "DIRECTORY", + branches: []string{"trunk", "dev"}, + want: "git -C DIRECTORY remote add -t trunk -t dev -f test URL", }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.title, func(t *testing.T) { cs, cmdTeardown := run.Stub() defer cmdTeardown(t) cs.Register(tt.want, 0, "") - err := AddUpstreamRemote(tt.upstreamURL, tt.cloneDir, tt.branches) + err := AddNamedRemote(tt.url, tt.name, tt.dir, tt.branches) if err != nil { t.Fatalf("error running command `git remote add -f`: %v", err) } diff --git a/pkg/cmd/repo/clone/clone.go b/pkg/cmd/repo/clone/clone.go index 1fd530c6f..57eb4b022 100644 --- a/pkg/cmd/repo/clone/clone.go +++ b/pkg/cmd/repo/clone/clone.go @@ -21,8 +21,9 @@ type CloneOptions struct { Config func() (config.Config, error) IO *iostreams.IOStreams - GitArgs []string - Repository string + GitArgs []string + Repository string + UpstreamName string } func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Command { @@ -46,7 +47,9 @@ func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Comm defaults to the name of the authenticating user. If the repository is a fork, its parent repository will be added as an additional - git remote called "upstream". + git remote called "upstream". The remote name can be configured using %[1]s--upstream-remote-name%[1]s. + The %[1]s--upstream-remote-name%[1]s option supports an "@owner" value which will name + the remote after the owner of the parent repository. `, "`"), RunE: func(cmd *cobra.Command, args []string) error { opts.Repository = args[0] @@ -60,6 +63,7 @@ func NewCmdClone(f *cmdutil.Factory, runF func(*CloneOptions) error) *cobra.Comm }, } + cmd.Flags().StringVarP(&opts.UpstreamName, "upstream-remote-name", "u", "upstream", "Upstream remote name when cloning a fork") cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { if err == pflag.ErrHelp { return err @@ -164,11 +168,15 @@ func cloneRun(opts *CloneOptions) error { } upstreamURL := ghrepo.FormatRemoteURL(canonicalRepo.Parent, protocol) - err = git.AddUpstreamRemote(upstreamURL, cloneDir, []string{canonicalRepo.Parent.DefaultBranchRef.Name}) + upstreamName := opts.UpstreamName + if opts.UpstreamName == "@owner" { + upstreamName = canonicalRepo.Parent.RepoOwner() + } + + err = git.AddNamedRemote(upstreamURL, upstreamName, cloneDir, []string{canonicalRepo.Parent.DefaultBranchRef.Name}) if err != nil { return err } } - return nil } diff --git a/pkg/cmd/repo/clone/clone_test.go b/pkg/cmd/repo/clone/clone_test.go index 8eefd32ca..34f2fe431 100644 --- a/pkg/cmd/repo/clone/clone_test.go +++ b/pkg/cmd/repo/clone/clone_test.go @@ -251,6 +251,42 @@ func Test_RepoClone_hasParent(t *testing.T) { } } +func Test_RepoClone_hasParent_upstreamRemoteName(t *testing.T) { + reg := &httpmock.Registry{} + reg.Register( + httpmock.GraphQL(`query RepositoryInfo\b`), + httpmock.StringResponse(` + { "data": { "repository": { + "name": "REPO", + "owner": { + "login": "OWNER" + }, + "parent": { + "name": "ORIG", + "owner": { + "login": "hubot" + }, + "defaultBranchRef": { + "name": "trunk" + } + } + } } } + `)) + + httpClient := &http.Client{Transport: reg} + + cs, cmdTeardown := run.Stub() + defer cmdTeardown(t) + + cs.Register(`git clone https://github.com/OWNER/REPO.git`, 0, "") + cs.Register(`git -C REPO remote add -t trunk -f test https://github.com/hubot/ORIG.git`, 0, "") + + _, err := runCloneCommand(httpClient, "OWNER/REPO --upstream-remote-name test") + if err != nil { + t.Fatalf("error running command `repo clone`: %v", err) + } +} + func Test_RepoClone_withoutUsername(t *testing.T) { reg := &httpmock.Registry{} defer reg.Verify(t) diff --git a/pkg/cmd/repo/fork/fork.go b/pkg/cmd/repo/fork/fork.go index c3b559c97..f7bf0140a 100644 --- a/pkg/cmd/repo/fork/fork.go +++ b/pkg/cmd/repo/fork/fork.go @@ -317,7 +317,7 @@ func forkRun(opts *ForkOptions) error { } upstreamURL := ghrepo.FormatRemoteURL(repoToFork, protocol) - err = git.AddUpstreamRemote(upstreamURL, cloneDir, []string{}) + err = git.AddNamedRemote(upstreamURL, "upstream", cloneDir, []string{}) if err != nil { return err }