repo clone: automatically set up "upstream" remote for forks
This commit is contained in:
parent
7555a4cf2f
commit
8460609181
5 changed files with 120 additions and 12 deletions
|
|
@ -2,6 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
|
@ -12,6 +13,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
// Repository contains information about a GitHub repo
|
||||
|
|
@ -93,6 +95,37 @@ func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
|
|||
return &result.Repository, nil
|
||||
}
|
||||
|
||||
// RepoParent finds out the parent repository of a fork
|
||||
func RepoParent(client *Client, repo ghrepo.Interface) (ghrepo.Interface, error) {
|
||||
var query struct {
|
||||
Repository struct {
|
||||
Parent *struct {
|
||||
Name string
|
||||
Owner struct {
|
||||
Login string
|
||||
}
|
||||
}
|
||||
} `graphql:"repository(owner: $owner, name: $name)"`
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"owner": githubv4.String(repo.RepoOwner()),
|
||||
"name": githubv4.String(repo.RepoName()),
|
||||
}
|
||||
|
||||
v4 := githubv4.NewClient(client.http)
|
||||
err := v4.Query(context.Background(), &query, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if query.Repository.Parent == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parent := ghrepo.New(query.Repository.Parent.Owner.Login, query.Repository.Parent.Name)
|
||||
return parent, nil
|
||||
}
|
||||
|
||||
// RepoNetworkResult describes the relationship between related repositories
|
||||
type RepoNetworkResult struct {
|
||||
ViewerLogin string
|
||||
|
|
|
|||
|
|
@ -93,6 +93,28 @@ func repoClone(cmd *cobra.Command, args []string) error {
|
|||
cloneURL = fmt.Sprintf("https://github.com/%s.git", cloneURL)
|
||||
}
|
||||
|
||||
var repo ghrepo.Interface
|
||||
var parentRepo ghrepo.Interface
|
||||
|
||||
// TODO: consider caching and reusing `git.ParseSSHConfig().Translator()`
|
||||
// here to handle hostname aliases in SSH remotes
|
||||
if u, err := git.ParseURL(cloneURL); err == nil {
|
||||
repo, _ = ghrepo.FromURL(u)
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
ctx := contextForCommand(cmd)
|
||||
apiClient, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentRepo, err = api.RepoParent(apiClient, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cloneArgs := []string{"clone"}
|
||||
cloneArgs = append(cloneArgs, args[1:]...)
|
||||
cloneArgs = append(cloneArgs, cloneURL)
|
||||
|
|
@ -101,7 +123,26 @@ func repoClone(cmd *cobra.Command, args []string) error {
|
|||
cloneCmd.Stdin = os.Stdin
|
||||
cloneCmd.Stdout = os.Stdout
|
||||
cloneCmd.Stderr = os.Stderr
|
||||
return run.PrepareCmd(cloneCmd).Run()
|
||||
err := run.PrepareCmd(cloneCmd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parentRepo != nil {
|
||||
// TODO: support SSH remote URLs
|
||||
upstreamURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(parentRepo))
|
||||
cloneDir := path.Base(strings.TrimSuffix(cloneURL, ".git"))
|
||||
|
||||
cloneCmd := git.GitCommand("-C", cloneDir, "remote", "add", "upstream", upstreamURL)
|
||||
cloneCmd.Stdout = os.Stdout
|
||||
cloneCmd.Stderr = os.Stderr
|
||||
err := run.PrepareCmd(cloneCmd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func repoCreate(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -363,12 +363,17 @@ func TestRepoClone(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
http := initFakeHTTP()
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"parent": null
|
||||
} } }
|
||||
`))
|
||||
|
||||
cs, restore := test.InitCmdStubber()
|
||||
defer restore()
|
||||
|
||||
cs.Stub("") // git clone
|
||||
|
||||
output, err := RunCommand(repoCloneCmd, tt.args)
|
||||
if err != nil {
|
||||
|
|
@ -377,15 +382,38 @@ func TestRepoClone(t *testing.T) {
|
|||
|
||||
eq(t, output.String(), "")
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), tt.want)
|
||||
eq(t, cs.Count, 1)
|
||||
eq(t, strings.Join(cs.Calls[0].Args, " "), tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoClone_hasParent(t *testing.T) {
|
||||
http := initFakeHTTP()
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"parent": {
|
||||
"owner": {"login": "hubot"},
|
||||
"name": "ORIG"
|
||||
}
|
||||
} } }
|
||||
`))
|
||||
|
||||
cs, restore := test.InitCmdStubber()
|
||||
defer restore()
|
||||
|
||||
cs.Stub("") // git clone
|
||||
cs.Stub("") // git remote add
|
||||
|
||||
_, err := RunCommand(repoCloneCmd, "repo clone OWNER/REPO")
|
||||
if err != nil {
|
||||
t.Fatalf("error running command `repo clone`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, cs.Count, 2)
|
||||
eq(t, strings.Join(cs.Calls[1].Args, " "), "git -C REPO remote add upstream https://github.com/hubot/ORIG.git")
|
||||
}
|
||||
|
||||
func TestRepoCreate(t *testing.T) {
|
||||
ctx := context.NewBlank()
|
||||
ctx.SetBranch("master")
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -16,6 +16,8 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.8 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -145,6 +145,10 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
|
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0 h1:T9uus1QvcPgeLShS30YOnnzk3r9Vvygp45muhlrufgY=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue