parent
3b23978114
commit
36436cb8b8
2 changed files with 129 additions and 22 deletions
|
|
@ -9,8 +9,10 @@ import (
|
|||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
|
|
@ -21,6 +23,10 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type errWithExitCode interface {
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
type iprompter interface {
|
||||
Input(string, string) (string, error)
|
||||
Select(string, string, []string) (int, error)
|
||||
|
|
@ -33,6 +39,7 @@ type CreateOptions struct {
|
|||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
Prompter iprompter
|
||||
BackOff backoff.BackOff
|
||||
|
||||
Name string
|
||||
Description string
|
||||
|
|
@ -328,7 +335,6 @@ func createFromScratch(opts *CreateOptions) error {
|
|||
InitReadme: opts.AddReadme,
|
||||
}
|
||||
|
||||
var templateRepoMainBranch string
|
||||
if opts.Template != "" {
|
||||
var templateRepo ghrepo.Interface
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
|
@ -352,7 +358,6 @@ func createFromScratch(opts *CreateOptions) error {
|
|||
}
|
||||
|
||||
input.TemplateRepositoryID = repo.ID
|
||||
templateRepoMainBranch = repo.DefaultBranchRef.Name
|
||||
}
|
||||
|
||||
repo, err := repoCreate(httpClient, repoToCreate.RepoHost(), input)
|
||||
|
|
@ -387,17 +392,12 @@ func createFromScratch(opts *CreateOptions) error {
|
|||
|
||||
remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
|
||||
|
||||
if opts.LicenseTemplate == "" && opts.GitIgnoreTemplate == "" {
|
||||
if opts.LicenseTemplate == "" && opts.GitIgnoreTemplate == "" && opts.Template == "" {
|
||||
// cloning empty repository or template
|
||||
checkoutBranch := ""
|
||||
if opts.Template != "" {
|
||||
// use the template's default branch
|
||||
checkoutBranch = templateRepoMainBranch
|
||||
}
|
||||
if err := localInit(opts.GitClient, remoteURL, repo.RepoName(), checkoutBranch); err != nil {
|
||||
if err := localInit(opts.GitClient, remoteURL, repo.RepoName()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if _, err := opts.GitClient.Clone(context.Background(), remoteURL, []string{}); err != nil {
|
||||
} else if err := cloneWithRetry(opts, remoteURL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -563,6 +563,25 @@ func createFromLocal(opts *CreateOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func cloneWithRetry(opts *CreateOptions, remoteURL string) error {
|
||||
// Allow injecting alternative BackOff in tests.
|
||||
if opts.BackOff == nil {
|
||||
opts.BackOff = backoff.NewConstantBackOff(3 * time.Second)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
return backoff.Retry(func() error {
|
||||
_, err := opts.GitClient.Clone(ctx, remoteURL, []string{})
|
||||
|
||||
var execError errWithExitCode
|
||||
if errors.As(err, &execError) && execError.ExitCode() == 128 {
|
||||
return err
|
||||
}
|
||||
|
||||
return backoff.Permanent(err)
|
||||
}, backoff.WithContext(backoff.WithMaxRetries(opts.BackOff, 3), ctx))
|
||||
}
|
||||
|
||||
func sourceInit(gitClient *git.Client, io *iostreams.IOStreams, remoteURL, baseRemote string) error {
|
||||
cs := io.ColorScheme()
|
||||
remoteAdd, err := gitClient.Command(context.Background(), "remote", "add", baseRemote, remoteURL)
|
||||
|
|
@ -620,7 +639,7 @@ func isLocalRepo(gitClient *git.Client) (bool, error) {
|
|||
}
|
||||
|
||||
// clone the checkout branch to specified path
|
||||
func localInit(gitClient *git.Client, remoteURL, path, checkoutBranch string) error {
|
||||
func localInit(gitClient *git.Client, remoteURL, path string) error {
|
||||
ctx := context.Background()
|
||||
gitInit, err := gitClient.Command(ctx, "init", path)
|
||||
if err != nil {
|
||||
|
|
@ -644,17 +663,7 @@ func localInit(gitClient *git.Client, remoteURL, path, checkoutBranch string) er
|
|||
return err
|
||||
}
|
||||
|
||||
if checkoutBranch == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
refspec := fmt.Sprintf("+refs/heads/%[1]s:refs/remotes/origin/%[1]s", checkoutBranch)
|
||||
err = gc.Fetch(ctx, "origin", refspec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gc.CheckoutBranch(ctx, checkoutBranch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func interactiveGitIgnore(client *http.Client, hostname string, prompter iprompter) (string, error) {
|
||||
|
|
|
|||
|
|
@ -119,6 +119,18 @@ func TestNewCmdCreate(t *testing.T) {
|
|||
IncludeAllBranches: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "template with .gitignore",
|
||||
cli: "template-repo --template mytemplate --gitignore ../.gitignore --public",
|
||||
wantsErr: true,
|
||||
errMsg: ".gitignore and license templates are not added when template is provided",
|
||||
},
|
||||
{
|
||||
name: "template with license",
|
||||
cli: "template-repo --template mytemplate --license ../.license --public",
|
||||
wantsErr: true,
|
||||
errMsg: ".gitignore and license templates are not added when template is provided",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -557,6 +569,92 @@ func Test_createRun(t *testing.T) {
|
|||
},
|
||||
wantStdout: "https://github.com/OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
name: "noninteractive clone from scratch",
|
||||
opts: &CreateOptions{
|
||||
Interactive: false,
|
||||
Name: "REPO",
|
||||
Visibility: "PRIVATE",
|
||||
Clone: true,
|
||||
},
|
||||
tty: false,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{
|
||||
"data": {
|
||||
"createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"name": "REPO",
|
||||
"owner": {"login":"OWNER"},
|
||||
"url": "https://github.com/OWNER/REPO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`))
|
||||
},
|
||||
execStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(`git init REPO`, 0, "")
|
||||
cs.Register(`git -C REPO remote add origin https://github.com/OWNER/REPO`, 0, "")
|
||||
},
|
||||
wantStdout: "https://github.com/OWNER/REPO\n",
|
||||
},
|
||||
{
|
||||
name: "noninteractive create from template",
|
||||
opts: &CreateOptions{
|
||||
Interactive: false,
|
||||
Name: "REPO",
|
||||
Visibility: "PRIVATE",
|
||||
Clone: true,
|
||||
Template: "mytemplate",
|
||||
},
|
||||
tty: false,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
// Test resolving repo owner from repo name only.
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"OWNER"}}}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query RepositoryInfo\b`),
|
||||
httpmock.GraphQLQuery(`{
|
||||
"data": {
|
||||
"repository": {
|
||||
"id": "REPOID"
|
||||
}
|
||||
}
|
||||
}`, func(s string, m map[string]interface{}) {
|
||||
assert.Equal(t, "OWNER", m["owner"])
|
||||
assert.Equal(t, "mytemplate", m["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"id":"OWNERID"}}}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation CloneTemplateRepository\b`),
|
||||
httpmock.GraphQLMutation(`
|
||||
{
|
||||
"data": {
|
||||
"cloneTemplateRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"name": "REPO",
|
||||
"owner": {"login":"OWNER"},
|
||||
"url": "https://github.com/OWNER/REPO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, func(m map[string]interface{}) {
|
||||
assert.Equal(t, "REPOID", m["repositoryId"])
|
||||
}))
|
||||
},
|
||||
execStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(`git clone https://github.com/OWNER/REPO`, 0, "")
|
||||
},
|
||||
wantStdout: "https://github.com/OWNER/REPO\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
prompterMock := &prompter.PrompterMock{}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue