792 lines
22 KiB
Go
792 lines
22 KiB
Go
package fork
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cenkalti/backoff/v4"
|
|
"github.com/cli/cli/v2/context"
|
|
"github.com/cli/cli/v2/git"
|
|
"github.com/cli/cli/v2/internal/config"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/internal/prompter"
|
|
"github.com/cli/cli/v2/internal/run"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/httpmock"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/google/shlex"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestNewCmdFork(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cli string
|
|
tty bool
|
|
wants ForkOptions
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "repo with git args",
|
|
cli: "foo/bar -- --foo=bar",
|
|
wants: ForkOptions{
|
|
Repository: "foo/bar",
|
|
GitArgs: []string{"--foo=bar"},
|
|
RemoteName: "origin",
|
|
Rename: true,
|
|
},
|
|
},
|
|
{
|
|
name: "git args without repo",
|
|
cli: "-- --foo bar",
|
|
wantErr: true,
|
|
errMsg: "repository argument required when passing git clone flags",
|
|
},
|
|
{
|
|
name: "repo",
|
|
cli: "foo/bar",
|
|
wants: ForkOptions{
|
|
Repository: "foo/bar",
|
|
RemoteName: "origin",
|
|
Rename: true,
|
|
GitArgs: []string{},
|
|
},
|
|
},
|
|
{
|
|
name: "blank remote name",
|
|
cli: "--remote --remote-name=''",
|
|
wantErr: true,
|
|
errMsg: "--remote-name cannot be blank",
|
|
},
|
|
{
|
|
name: "remote name",
|
|
cli: "--remote --remote-name=foo",
|
|
wants: ForkOptions{
|
|
RemoteName: "foo",
|
|
Rename: false,
|
|
Remote: true,
|
|
},
|
|
},
|
|
{
|
|
name: "blank nontty",
|
|
cli: "",
|
|
wants: ForkOptions{
|
|
RemoteName: "origin",
|
|
Rename: true,
|
|
Organization: "",
|
|
},
|
|
},
|
|
{
|
|
name: "blank tty",
|
|
cli: "",
|
|
tty: true,
|
|
wants: ForkOptions{
|
|
RemoteName: "origin",
|
|
PromptClone: true,
|
|
PromptRemote: true,
|
|
Rename: true,
|
|
Organization: "",
|
|
},
|
|
},
|
|
{
|
|
name: "clone",
|
|
cli: "--clone",
|
|
wants: ForkOptions{
|
|
RemoteName: "origin",
|
|
Rename: true,
|
|
},
|
|
},
|
|
{
|
|
name: "remote",
|
|
cli: "--remote",
|
|
wants: ForkOptions{
|
|
RemoteName: "origin",
|
|
Remote: true,
|
|
Rename: true,
|
|
},
|
|
},
|
|
{
|
|
name: "to org",
|
|
cli: "--org batmanshome",
|
|
wants: ForkOptions{
|
|
RemoteName: "origin",
|
|
Remote: false,
|
|
Rename: false,
|
|
Organization: "batmanshome",
|
|
},
|
|
},
|
|
{
|
|
name: "empty org",
|
|
cli: " --org=''",
|
|
wantErr: true,
|
|
errMsg: "--org cannot be blank",
|
|
},
|
|
{
|
|
name: "git flags in wrong place",
|
|
cli: "--depth 1 OWNER/REPO",
|
|
wantErr: true,
|
|
errMsg: "unknown flag: --depth\nSeparate git clone flags with `--`.",
|
|
},
|
|
{
|
|
name: "with fork name",
|
|
cli: "--fork-name new-fork",
|
|
wants: ForkOptions{
|
|
Remote: false,
|
|
RemoteName: "origin",
|
|
ForkName: "new-fork",
|
|
Rename: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ios, _, _, _ := iostreams.Test()
|
|
|
|
f := &cmdutil.Factory{
|
|
IOStreams: ios,
|
|
}
|
|
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStdinTTY(tt.tty)
|
|
|
|
argv, err := shlex.Split(tt.cli)
|
|
assert.NoError(t, err)
|
|
|
|
var gotOpts *ForkOptions
|
|
cmd := NewCmdFork(f, func(opts *ForkOptions) error {
|
|
gotOpts = opts
|
|
return nil
|
|
})
|
|
cmd.SetArgs(argv)
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(&bytes.Buffer{})
|
|
cmd.SetErr(&bytes.Buffer{})
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
assert.Equal(t, tt.errMsg, err.Error())
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.wants.RemoteName, gotOpts.RemoteName)
|
|
assert.Equal(t, tt.wants.Remote, gotOpts.Remote)
|
|
assert.Equal(t, tt.wants.PromptRemote, gotOpts.PromptRemote)
|
|
assert.Equal(t, tt.wants.PromptClone, gotOpts.PromptClone)
|
|
assert.Equal(t, tt.wants.Organization, gotOpts.Organization)
|
|
assert.Equal(t, tt.wants.GitArgs, gotOpts.GitArgs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRepoFork(t *testing.T) {
|
|
forkResult := `{
|
|
"node_id": "123",
|
|
"name": "REPO",
|
|
"clone_url": "https://github.com/someone/repo.git",
|
|
"created_at": "2011-01-26T19:01:12Z",
|
|
"owner": {
|
|
"login": "someone"
|
|
}
|
|
}`
|
|
|
|
forkPost := func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("POST", "repos/OWNER/REPO/forks"),
|
|
httpmock.StringResponse(forkResult))
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
opts *ForkOptions
|
|
tty bool
|
|
httpStubs func(*httpmock.Registry)
|
|
execStubs func(*run.CommandStubber)
|
|
promptStubs func(*prompter.MockPrompter)
|
|
cfgStubs func(*testing.T, config.Config)
|
|
remotes []*context.Remote
|
|
wantOut string
|
|
wantErrOut string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "implicit match, configured protocol overrides provided",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: "fork",
|
|
},
|
|
remotes: []*context.Remote{
|
|
{
|
|
Remote: &git.Remote{Name: "origin", PushURL: &url.URL{
|
|
Scheme: "ssh",
|
|
}},
|
|
Repo: ghrepo.New("OWNER", "REPO"),
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git remote add fork https://github\.com/someone/REPO\.git`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Added remote fork\n",
|
|
},
|
|
{
|
|
name: "implicit match, no configured protocol",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: "fork",
|
|
},
|
|
remotes: []*context.Remote{
|
|
{
|
|
Remote: &git.Remote{Name: "origin", PushURL: &url.URL{
|
|
Scheme: "ssh",
|
|
}},
|
|
Repo: ghrepo.New("OWNER", "REPO"),
|
|
},
|
|
},
|
|
cfgStubs: func(_ *testing.T, c config.Config) {
|
|
c.Set("", "git_protocol", "")
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git remote add fork git@github\.com:someone/REPO\.git`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Added remote fork\n",
|
|
},
|
|
{
|
|
name: "implicit with negative interactive choices",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
PromptRemote: true,
|
|
Rename: true,
|
|
RemoteName: defaultRemoteName,
|
|
},
|
|
httpStubs: forkPost,
|
|
promptStubs: func(pm *prompter.MockPrompter) {
|
|
pm.RegisterConfirm("Would you like to add a remote for the fork?", func(_ string, _ bool) (bool, error) {
|
|
return false, nil
|
|
})
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\n",
|
|
},
|
|
{
|
|
name: "implicit with interactive choices",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
PromptRemote: true,
|
|
Rename: true,
|
|
RemoteName: defaultRemoteName,
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register("git remote rename origin upstream", 0, "")
|
|
cs.Register(`git remote add origin https://github.com/someone/REPO.git`, 0, "")
|
|
},
|
|
promptStubs: func(pm *prompter.MockPrompter) {
|
|
pm.RegisterConfirm("Would you like to add a remote for the fork?", func(_ string, _ bool) (bool, error) {
|
|
return true, nil
|
|
})
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Added remote origin\n",
|
|
},
|
|
{
|
|
name: "implicit tty reuse existing remote",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: defaultRemoteName,
|
|
},
|
|
remotes: []*context.Remote{
|
|
{
|
|
Remote: &git.Remote{Name: "origin", FetchURL: &url.URL{}},
|
|
Repo: ghrepo.New("someone", "REPO"),
|
|
},
|
|
{
|
|
Remote: &git.Remote{Name: "upstream", FetchURL: &url.URL{}},
|
|
Repo: ghrepo.New("OWNER", "REPO"),
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
wantErrOut: "ā Created fork someone/REPO\nā Using existing remote origin\n",
|
|
},
|
|
{
|
|
name: "implicit tty remote exists",
|
|
// gh repo fork --remote --remote-name origin | cat
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: defaultRemoteName,
|
|
},
|
|
httpStubs: forkPost,
|
|
wantErr: true,
|
|
errMsg: "a git remote named 'origin' already exists",
|
|
},
|
|
{
|
|
name: "implicit tty current owner forked",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "someone/REPO",
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("POST", "repos/someone/REPO/forks"),
|
|
httpmock.StringResponse(forkResult))
|
|
},
|
|
wantErr: true,
|
|
errMsg: "failed to fork: someone/REPO cannot be forked",
|
|
},
|
|
{
|
|
name: "implicit tty already forked",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Since: func(t time.Time) time.Duration {
|
|
return 120 * time.Second
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
wantErrOut: "! someone/REPO already exists\n",
|
|
},
|
|
{
|
|
name: "implicit tty --remote",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: defaultRemoteName,
|
|
Rename: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register("git remote rename origin upstream", 0, "")
|
|
cs.Register(`git remote add origin https://github.com/someone/REPO.git`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Added remote origin\n",
|
|
},
|
|
{
|
|
name: "implicit nontty reuse existing remote",
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: defaultRemoteName,
|
|
Rename: true,
|
|
},
|
|
remotes: []*context.Remote{
|
|
{
|
|
Remote: &git.Remote{Name: "origin", FetchURL: &url.URL{}},
|
|
Repo: ghrepo.New("someone", "REPO"),
|
|
},
|
|
{
|
|
Remote: &git.Remote{Name: "upstream", FetchURL: &url.URL{}},
|
|
Repo: ghrepo.New("OWNER", "REPO"),
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
},
|
|
{
|
|
name: "implicit nontty remote exists",
|
|
// gh repo fork --remote --remote-name origin | cat
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: defaultRemoteName,
|
|
},
|
|
httpStubs: forkPost,
|
|
wantErr: true,
|
|
errMsg: "a git remote named 'origin' already exists",
|
|
},
|
|
{
|
|
name: "implicit nontty already forked",
|
|
opts: &ForkOptions{
|
|
Since: func(t time.Time) time.Duration {
|
|
return 120 * time.Second
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
wantErrOut: "someone/REPO already exists\n",
|
|
},
|
|
{
|
|
name: "implicit nontty --remote",
|
|
opts: &ForkOptions{
|
|
Remote: true,
|
|
RemoteName: defaultRemoteName,
|
|
Rename: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register("git remote rename origin upstream", 0, "")
|
|
cs.Register(`git remote add origin https://github.com/someone/REPO.git`, 0, "")
|
|
},
|
|
},
|
|
{
|
|
name: "implicit nontty no args",
|
|
opts: &ForkOptions{},
|
|
httpStubs: forkPost,
|
|
},
|
|
{
|
|
name: "passes git flags",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
GitArgs: []string{"--depth", "1"},
|
|
Clone: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone --depth 1 https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Cloned fork\n! Repository OWNER/REPO set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n",
|
|
},
|
|
{
|
|
name: "repo arg fork to org",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
Organization: "gamehendge",
|
|
Clone: true,
|
|
},
|
|
httpStubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("POST", "repos/OWNER/REPO/forks"),
|
|
func(req *http.Request) (*http.Response, error) {
|
|
bb, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
assert.Equal(t, `{"organization":"gamehendge"}`, strings.TrimSpace(string(bb)))
|
|
return &http.Response{
|
|
Request: req,
|
|
StatusCode: 200,
|
|
Body: io.NopCloser(bytes.NewBufferString(`{"name":"REPO", "owner":{"login":"gamehendge"}}`)),
|
|
}, nil
|
|
})
|
|
},
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/gamehendge/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork gamehendge/REPO\nā Cloned fork\n! Repository OWNER/REPO set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n",
|
|
},
|
|
{
|
|
name: "repo arg url arg",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "https://github.com/OWNER/REPO.git",
|
|
Clone: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Cloned fork\n! Repository OWNER/REPO set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n",
|
|
},
|
|
{
|
|
name: "repo arg interactive no clone",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
PromptClone: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
promptStubs: func(pm *prompter.MockPrompter) {
|
|
pm.RegisterConfirm("Would you like to clone the fork?", func(_ string, _ bool) (bool, error) {
|
|
return false, nil
|
|
})
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\n",
|
|
},
|
|
{
|
|
name: "repo arg interactive",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
PromptClone: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
promptStubs: func(pm *prompter.MockPrompter) {
|
|
pm.RegisterConfirm("Would you like to clone the fork?", func(_ string, _ bool) (bool, error) {
|
|
return true, nil
|
|
})
|
|
},
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
wantErrOut: "ā Created fork someone/REPO\nā Cloned fork\n! Repository OWNER/REPO set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n",
|
|
},
|
|
{
|
|
name: "repo arg interactive already forked",
|
|
tty: true,
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
PromptClone: true,
|
|
Since: func(t time.Time) time.Duration {
|
|
return 120 * time.Second
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
promptStubs: func(pm *prompter.MockPrompter) {
|
|
pm.RegisterConfirm("Would you like to clone the fork?", func(_ string, _ bool) (bool, error) {
|
|
return true, nil
|
|
})
|
|
},
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
wantErrOut: "! someone/REPO already exists\nā Cloned fork\n! Repository OWNER/REPO set as the default repository. To learn more about the default repository, run: gh repo set-default --help\n",
|
|
},
|
|
{
|
|
name: "repo arg nontty no flags",
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
},
|
|
httpStubs: forkPost,
|
|
},
|
|
{
|
|
name: "repo arg nontty repo already exists",
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
Since: func(t time.Time) time.Duration {
|
|
return 120 * time.Second
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
wantErrOut: "someone/REPO already exists\n",
|
|
},
|
|
{
|
|
name: "repo arg nontty clone arg already exists",
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
Clone: true,
|
|
Since: func(t time.Time) time.Duration {
|
|
return 120 * time.Second
|
|
},
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
wantErrOut: "someone/REPO already exists\n",
|
|
},
|
|
{
|
|
name: "repo arg nontty clone arg",
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
Clone: true,
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
},
|
|
{
|
|
name: "non tty repo arg with fork-name",
|
|
opts: &ForkOptions{
|
|
Repository: "someone/REPO",
|
|
Clone: false,
|
|
ForkName: "NEW_REPO",
|
|
},
|
|
tty: false,
|
|
httpStubs: func(reg *httpmock.Registry) {
|
|
forkResult := `{
|
|
"node_id": "123",
|
|
"name": "REPO",
|
|
"clone_url": "https://github.com/OWNER/REPO.git",
|
|
"created_at": "2011-01-26T19:01:12Z",
|
|
"owner": {
|
|
"login": "OWNER"
|
|
}
|
|
}`
|
|
renameResult := `{
|
|
"node_id": "1234",
|
|
"name": "NEW_REPO",
|
|
"clone_url": "https://github.com/OWNER/NEW_REPO.git",
|
|
"created_at": "2012-01-26T19:01:12Z",
|
|
"owner": {
|
|
"login": "OWNER"
|
|
}
|
|
}`
|
|
reg.Register(
|
|
httpmock.REST("POST", "repos/someone/REPO/forks"),
|
|
httpmock.StringResponse(forkResult))
|
|
reg.Register(
|
|
httpmock.REST("PATCH", "repos/OWNER/REPO"),
|
|
httpmock.StringResponse(renameResult))
|
|
},
|
|
wantErrOut: "",
|
|
},
|
|
{
|
|
name: "tty repo arg with fork-name",
|
|
opts: &ForkOptions{
|
|
Repository: "someone/REPO",
|
|
Clone: false,
|
|
ForkName: "NEW_REPO",
|
|
},
|
|
tty: true,
|
|
httpStubs: func(reg *httpmock.Registry) {
|
|
forkResult := `{
|
|
"node_id": "123",
|
|
"name": "REPO",
|
|
"clone_url": "https://github.com/OWNER/REPO.git",
|
|
"created_at": "2011-01-26T19:01:12Z",
|
|
"owner": {
|
|
"login": "OWNER"
|
|
}
|
|
}`
|
|
renameResult := `{
|
|
"node_id": "1234",
|
|
"name": "NEW_REPO",
|
|
"clone_url": "https://github.com/OWNER/NEW_REPO.git",
|
|
"created_at": "2012-01-26T19:01:12Z",
|
|
"owner": {
|
|
"login": "OWNER"
|
|
}
|
|
}`
|
|
reg.Register(
|
|
httpmock.REST("POST", "repos/someone/REPO/forks"),
|
|
httpmock.StringResponse(forkResult))
|
|
reg.Register(
|
|
httpmock.REST("PATCH", "repos/OWNER/REPO"),
|
|
httpmock.StringResponse(renameResult))
|
|
},
|
|
wantErrOut: "ā Created fork OWNER/REPO\nā Renamed fork to OWNER/NEW_REPO\n",
|
|
},
|
|
{
|
|
name: "retries clone up to four times if necessary",
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
Clone: true,
|
|
BackOff: &backoff.ZeroBackOff{},
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 128, "")
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 128, "")
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 128, "")
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO remote add upstream https://github\.com/OWNER/REPO\.git`, 0, "")
|
|
cs.Register(`git -C REPO fetch upstream`, 0, "")
|
|
cs.Register(`git -C REPO config --add remote.upstream.gh-resolved base`, 0, "")
|
|
},
|
|
},
|
|
{
|
|
name: "does not retry clone if error occurs and exit code is not 128",
|
|
opts: &ForkOptions{
|
|
Repository: "OWNER/REPO",
|
|
Clone: true,
|
|
BackOff: &backoff.ZeroBackOff{},
|
|
},
|
|
httpStubs: forkPost,
|
|
execStubs: func(cs *run.CommandStubber) {
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 128, "")
|
|
cs.Register(`git clone https://github.com/someone/REPO\.git`, 65, "")
|
|
},
|
|
wantErr: true,
|
|
errMsg: `failed to clone fork: failed to run git: git -c credential.helper= -c credential.helper=!"[^"]+" auth git-credential clone https://github.com/someone/REPO\.git exited with status 65`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdinTTY(tt.tty)
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStderrTTY(tt.tty)
|
|
tt.opts.IO = ios
|
|
|
|
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
|
return ghrepo.New("OWNER", "REPO"), nil
|
|
}
|
|
|
|
reg := &httpmock.Registry{}
|
|
if tt.httpStubs != nil {
|
|
tt.httpStubs(reg)
|
|
}
|
|
tt.opts.HttpClient = func() (*http.Client, error) {
|
|
return &http.Client{Transport: reg}, nil
|
|
}
|
|
|
|
cfg, _ := config.NewIsolatedTestConfig(t)
|
|
if tt.cfgStubs != nil {
|
|
tt.cfgStubs(t, cfg)
|
|
}
|
|
tt.opts.Config = func() (config.Config, error) {
|
|
return cfg, nil
|
|
}
|
|
|
|
tt.opts.Remotes = func() (context.Remotes, error) {
|
|
if tt.remotes == nil {
|
|
return []*context.Remote{
|
|
{
|
|
Remote: &git.Remote{
|
|
Name: "origin",
|
|
FetchURL: &url.URL{},
|
|
},
|
|
Repo: ghrepo.New("OWNER", "REPO"),
|
|
},
|
|
}, nil
|
|
}
|
|
return tt.remotes, nil
|
|
}
|
|
|
|
tt.opts.GitClient = &git.Client{
|
|
GhPath: "some/path/gh",
|
|
GitPath: "some/path/git",
|
|
}
|
|
|
|
pm := prompter.NewMockPrompter(t)
|
|
tt.opts.Prompter = pm
|
|
if tt.promptStubs != nil {
|
|
tt.promptStubs(pm)
|
|
}
|
|
|
|
cs, restoreRun := run.Stub()
|
|
defer restoreRun(t)
|
|
if tt.execStubs != nil {
|
|
tt.execStubs(cs)
|
|
}
|
|
|
|
if tt.opts.Since == nil {
|
|
tt.opts.Since = func(t time.Time) time.Duration {
|
|
return 2 * time.Second
|
|
}
|
|
}
|
|
|
|
defer reg.Verify(t)
|
|
err := forkRun(tt.opts)
|
|
if tt.wantErr {
|
|
assert.Error(t, err, tt.errMsg)
|
|
return
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wantOut, stdout.String())
|
|
assert.Equal(t, tt.wantErrOut, stderr.String())
|
|
})
|
|
}
|
|
}
|