* Use plural linking verb While it looks like you could use "there's" informally, grammatically, it should be "are" since "commits" is plural. <https://dictionary.cambridge.org/us/grammar/british-grammar/there-is-there-s-and-there-are> * Omit "the" I created a remote called "github" and got this prompt: > Would you like to push commits from the current branch to the "github"? Normally, the default name "origin" doesn't sound bad in that prompt, but using the name "github" made the prompt sound like something wasn't right to my American English-speaking ears. Here are a few options. Yes, I know English grammar sucks, to put it mildly. But, hopefully, the following options and explanations make sense. Get rid of "the". This is the option I went with. "github" acts as a proper noun, so no determiner is needed. If you substitute your own name for "github" in the original prompt above, you get the same effect: > Would you like to push commits from the current branch to "github"? Add the implicit word "remote". "github" now acts as an adjective and "the" refers to "remote": > Would you like to push commits from the current branch to the "github" remote? Or, combine the two. This last option relies on the fact that instructions and manuals often omit definite articles because most articles are definite. See the [zero-marking][2] article on Wikipedia. The original prompt already does this by omitting "the" before the word "commits": > Would you like to push commits from the current branch to "github" remote? Reference: [1]: http://writing.umn.edu/sws/quickhelp/grammar/articlesproper.html [2]: https://en.wikipedia.org/wiki/Zero-marking_in_English
850 lines
23 KiB
Go
850 lines
23 KiB
Go
package create
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/cli/cli/v2/api"
|
|
"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/run"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/cli/cli/v2/pkg/prompt"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type CreateOptions struct {
|
|
HttpClient func() (*http.Client, error)
|
|
Config func() (config.Config, error)
|
|
IO *iostreams.IOStreams
|
|
|
|
Name string
|
|
Description string
|
|
Homepage string
|
|
Team string
|
|
Template string
|
|
Public bool
|
|
Private bool
|
|
Internal bool
|
|
Visibility string
|
|
Push bool
|
|
Clone bool
|
|
Source string
|
|
Remote string
|
|
GitIgnoreTemplate string
|
|
LicenseTemplate string
|
|
DisableIssues bool
|
|
DisableWiki bool
|
|
Interactive bool
|
|
}
|
|
|
|
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
|
|
opts := &CreateOptions{
|
|
IO: f.IOStreams,
|
|
HttpClient: f.HttpClient,
|
|
Config: f.Config,
|
|
}
|
|
|
|
var enableIssues bool
|
|
var enableWiki bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "create [<name>]",
|
|
Short: "Create a new repository",
|
|
Long: heredoc.Docf(`
|
|
Create a new GitHub repository.
|
|
|
|
To create a repository interactively, use %[1]sgh repo create%[1]s with no arguments.
|
|
|
|
To create a remote repository non-interactively, supply the repository name and one of %[1]s--public%[1]s, %[1]s--private%[1]s, or %[1]s--internal%[1]s.
|
|
Pass %[1]s--clone%[1]s to clone the new repository locally.
|
|
|
|
To create a remote repository from an existing local repository, specify the source directory with %[1]s--source%[1]s.
|
|
By default, the remote repository name will be the name of the source directory.
|
|
Pass %[1]s--push%[1]s to push any local commits to the new repository.
|
|
`, "`"),
|
|
Example: heredoc.Doc(`
|
|
# create a repository interactively
|
|
gh repo create
|
|
|
|
# create a new remote repository and clone it locally
|
|
gh repo create my-project --public --clone
|
|
|
|
# create a remote repository from the current directory
|
|
gh repo create my-project --private --source=. --remote=upstream
|
|
`),
|
|
Args: cobra.MaximumNArgs(1),
|
|
Aliases: []string{"new"},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) > 0 {
|
|
opts.Name = args[0]
|
|
}
|
|
|
|
if len(args) == 0 && cmd.Flags().NFlag() == 0 {
|
|
if !opts.IO.CanPrompt() {
|
|
return cmdutil.FlagErrorf("at least one argument required in non-interactive mode")
|
|
}
|
|
opts.Interactive = true
|
|
} else {
|
|
// exactly one visibility flag required
|
|
if !opts.Public && !opts.Private && !opts.Internal {
|
|
return cmdutil.FlagErrorf("`--public`, `--private`, or `--internal` required when not running interactively")
|
|
}
|
|
err := cmdutil.MutuallyExclusive(
|
|
"expected exactly one of `--public`, `--private`, or `--internal`",
|
|
opts.Public, opts.Private, opts.Internal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.Public {
|
|
opts.Visibility = "PUBLIC"
|
|
} else if opts.Private {
|
|
opts.Visibility = "PRIVATE"
|
|
} else {
|
|
opts.Visibility = "INTERNAL"
|
|
}
|
|
}
|
|
|
|
if opts.Source == "" {
|
|
if opts.Remote != "" {
|
|
return cmdutil.FlagErrorf("the `--remote` option can only be used with `--source`")
|
|
}
|
|
if opts.Push {
|
|
return cmdutil.FlagErrorf("the `--push` option can only be used with `--source`")
|
|
}
|
|
if opts.Name == "" && !opts.Interactive {
|
|
return cmdutil.FlagErrorf("name argument required to create new remote repository")
|
|
}
|
|
|
|
} else if opts.Clone || opts.GitIgnoreTemplate != "" || opts.LicenseTemplate != "" || opts.Template != "" {
|
|
return cmdutil.FlagErrorf("the `--source` option is not supported with `--clone`, `--template`, `--license`, or `--gitignore`")
|
|
}
|
|
|
|
if opts.Template != "" && (opts.GitIgnoreTemplate != "" || opts.LicenseTemplate != "") {
|
|
return cmdutil.FlagErrorf(".gitignore and license templates are not added when template is provided")
|
|
}
|
|
|
|
if cmd.Flags().Changed("enable-issues") {
|
|
opts.DisableIssues = !enableIssues
|
|
}
|
|
if cmd.Flags().Changed("enable-wiki") {
|
|
opts.DisableWiki = !enableWiki
|
|
}
|
|
if opts.Template != "" && (opts.Homepage != "" || opts.Team != "" || opts.DisableIssues || opts.DisableWiki) {
|
|
return cmdutil.FlagErrorf("the `--template` option is not supported with `--homepage`, `--team`, `--disable-issues`, or `--disable-wiki`")
|
|
}
|
|
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
return createRun(opts)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of the repository")
|
|
cmd.Flags().StringVarP(&opts.Homepage, "homepage", "h", "", "Repository home page `URL`")
|
|
cmd.Flags().StringVarP(&opts.Team, "team", "t", "", "The `name` of the organization team to be granted access")
|
|
cmd.Flags().StringVarP(&opts.Template, "template", "p", "", "Make the new repository based on a template `repository`")
|
|
cmd.Flags().BoolVar(&opts.Public, "public", false, "Make the new repository public")
|
|
cmd.Flags().BoolVar(&opts.Private, "private", false, "Make the new repository private")
|
|
cmd.Flags().BoolVar(&opts.Internal, "internal", false, "Make the new repository internal")
|
|
cmd.Flags().StringVarP(&opts.GitIgnoreTemplate, "gitignore", "g", "", "Specify a gitignore template for the repository")
|
|
cmd.Flags().StringVarP(&opts.LicenseTemplate, "license", "l", "", "Specify an Open Source License for the repository")
|
|
cmd.Flags().StringVarP(&opts.Source, "source", "s", "", "Specify path to local repository to use as source")
|
|
cmd.Flags().StringVarP(&opts.Remote, "remote", "r", "", "Specify remote name for the new repository")
|
|
cmd.Flags().BoolVar(&opts.Push, "push", false, "Push local commits to the new repository")
|
|
cmd.Flags().BoolVarP(&opts.Clone, "clone", "c", false, "Clone the new repository to the current directory")
|
|
cmd.Flags().BoolVar(&opts.DisableIssues, "disable-issues", false, "Disable issues in the new repository")
|
|
cmd.Flags().BoolVar(&opts.DisableWiki, "disable-wiki", false, "Disable wiki in the new repository")
|
|
|
|
// deprecated flags
|
|
cmd.Flags().BoolP("confirm", "y", false, "Skip the confirmation prompt")
|
|
cmd.Flags().BoolVar(&enableIssues, "enable-issues", true, "Enable issues in the new repository")
|
|
cmd.Flags().BoolVar(&enableWiki, "enable-wiki", true, "Enable wiki in the new repository")
|
|
|
|
_ = cmd.Flags().MarkDeprecated("confirm", "Pass any argument to skip confirmation prompt")
|
|
_ = cmd.Flags().MarkDeprecated("enable-issues", "Disable issues with `--disable-issues`")
|
|
_ = cmd.Flags().MarkDeprecated("enable-wiki", "Disable wiki with `--disable-wiki`")
|
|
|
|
_ = cmd.RegisterFlagCompletionFunc("gitignore", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
httpClient, err := opts.HttpClient()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
cfg, err := opts.Config()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
hostname, err := cfg.DefaultHost()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
results, err := listGitIgnoreTemplates(httpClient, hostname)
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
return results, cobra.ShellCompDirectiveNoFileComp
|
|
})
|
|
|
|
_ = cmd.RegisterFlagCompletionFunc("license", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
httpClient, err := opts.HttpClient()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
cfg, err := opts.Config()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
hostname, err := cfg.DefaultHost()
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
licenses, err := listLicenseTemplates(httpClient, hostname)
|
|
if err != nil {
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
var results []string
|
|
for _, license := range licenses {
|
|
results = append(results, fmt.Sprintf("%s\t%s", license.Key, license.Name))
|
|
}
|
|
return results, cobra.ShellCompDirectiveNoFileComp
|
|
})
|
|
|
|
return cmd
|
|
}
|
|
|
|
func createRun(opts *CreateOptions) error {
|
|
fromScratch := opts.Source == ""
|
|
|
|
if opts.Interactive {
|
|
var selectedMode string
|
|
modeOptions := []string{
|
|
"Create a new repository on GitHub from scratch",
|
|
"Push an existing local repository to GitHub",
|
|
}
|
|
if err := prompt.SurveyAskOne(&survey.Select{
|
|
Message: "What would you like to do?",
|
|
Options: modeOptions,
|
|
}, &selectedMode); err != nil {
|
|
return err
|
|
}
|
|
fromScratch = selectedMode == modeOptions[0]
|
|
}
|
|
|
|
if fromScratch {
|
|
return createFromScratch(opts)
|
|
}
|
|
return createFromLocal(opts)
|
|
}
|
|
|
|
// create new repo on remote host
|
|
func createFromScratch(opts *CreateOptions) error {
|
|
httpClient, err := opts.HttpClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var repoToCreate ghrepo.Interface
|
|
cfg, err := opts.Config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
host, err := cfg.DefaultHost()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.Interactive {
|
|
opts.Name, opts.Description, opts.Visibility, err = interactiveRepoInfo("")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.GitIgnoreTemplate, err = interactiveGitIgnore(httpClient, host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.LicenseTemplate, err = interactiveLicense(httpClient, host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := confirmSubmission(opts.Name, opts.Visibility); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if strings.Contains(opts.Name, "/") {
|
|
var err error
|
|
repoToCreate, err = ghrepo.FromFullName(opts.Name)
|
|
if err != nil {
|
|
return fmt.Errorf("argument error: %w", err)
|
|
}
|
|
} else {
|
|
repoToCreate = ghrepo.NewWithHost("", opts.Name, host)
|
|
}
|
|
|
|
input := repoCreateInput{
|
|
Name: repoToCreate.RepoName(),
|
|
Visibility: opts.Visibility,
|
|
OwnerLogin: repoToCreate.RepoOwner(),
|
|
TeamSlug: opts.Team,
|
|
Description: opts.Description,
|
|
HomepageURL: opts.Homepage,
|
|
HasIssuesEnabled: !opts.DisableIssues,
|
|
HasWikiEnabled: !opts.DisableWiki,
|
|
GitIgnoreTemplate: opts.GitIgnoreTemplate,
|
|
LicenseTemplate: opts.LicenseTemplate,
|
|
}
|
|
|
|
var templateRepoMainBranch string
|
|
if opts.Template != "" {
|
|
var templateRepo ghrepo.Interface
|
|
apiClient := api.NewClientFromHTTP(httpClient)
|
|
|
|
templateRepoName := opts.Template
|
|
if !strings.Contains(templateRepoName, "/") {
|
|
currentUser, err := api.CurrentLoginName(apiClient, host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
templateRepoName = currentUser + "/" + templateRepoName
|
|
}
|
|
templateRepo, err = ghrepo.FromFullName(templateRepoName)
|
|
if err != nil {
|
|
return fmt.Errorf("argument error: %w", err)
|
|
}
|
|
|
|
repo, err := api.GitHubRepo(apiClient, templateRepo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
input.TemplateRepositoryID = repo.ID
|
|
templateRepoMainBranch = repo.DefaultBranchRef.Name
|
|
}
|
|
|
|
repo, err := repoCreate(httpClient, repoToCreate.RepoHost(), input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs := opts.IO.ColorScheme()
|
|
isTTY := opts.IO.IsStdoutTTY()
|
|
if isTTY {
|
|
fmt.Fprintf(opts.IO.Out,
|
|
"%s Created repository %s on GitHub\n",
|
|
cs.SuccessIconWithColor(cs.Green),
|
|
ghrepo.FullName(repo))
|
|
} else {
|
|
fmt.Fprintln(opts.IO.Out, repo.URL)
|
|
}
|
|
|
|
if opts.Interactive {
|
|
cloneQuestion := &survey.Confirm{
|
|
Message: "Clone the new repository locally?",
|
|
Default: true,
|
|
}
|
|
err = prompt.SurveyAskOne(cloneQuestion, &opts.Clone)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if opts.Clone {
|
|
protocol, err := cfg.GetOrDefault(repo.RepoHost(), "git_protocol")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
|
|
|
|
if opts.LicenseTemplate == "" && opts.GitIgnoreTemplate == "" {
|
|
// cloning empty repository or template
|
|
checkoutBranch := ""
|
|
if opts.Template != "" {
|
|
// use the template's default branch
|
|
checkoutBranch = templateRepoMainBranch
|
|
}
|
|
if err := localInit(opts.IO, remoteURL, repo.RepoName(), checkoutBranch); err != nil {
|
|
return err
|
|
}
|
|
} else if _, err := git.RunClone(remoteURL, []string{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// create repo on remote host from existing local repo
|
|
func createFromLocal(opts *CreateOptions) error {
|
|
httpClient, err := opts.HttpClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs := opts.IO.ColorScheme()
|
|
isTTY := opts.IO.IsStdoutTTY()
|
|
stdout := opts.IO.Out
|
|
|
|
cfg, err := opts.Config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
host, err := cfg.DefaultHost()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if opts.Interactive {
|
|
opts.Source, err = interactiveSource()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
repoPath := opts.Source
|
|
|
|
var baseRemote string
|
|
if opts.Remote == "" {
|
|
baseRemote = "origin"
|
|
} else {
|
|
baseRemote = opts.Remote
|
|
}
|
|
|
|
absPath, err := filepath.Abs(repoPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
isRepo, err := isLocalRepo(repoPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isRepo {
|
|
if repoPath == "." {
|
|
return fmt.Errorf("current directory is not a git repository. Run `git init` to initalize it")
|
|
}
|
|
return fmt.Errorf("%s is not a git repository. Run `git -C %s init` to initialize it", absPath, repoPath)
|
|
}
|
|
|
|
committed, err := hasCommits(repoPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if opts.Push {
|
|
// fail immediately if trying to push with no commits
|
|
if !committed {
|
|
return fmt.Errorf("`--push` enabled but no commits found in %s", absPath)
|
|
}
|
|
}
|
|
|
|
if opts.Interactive {
|
|
opts.Name, opts.Description, opts.Visibility, err = interactiveRepoInfo(filepath.Base(absPath))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var repoToCreate ghrepo.Interface
|
|
|
|
// repo name will be currdir name or specified
|
|
if opts.Name == "" {
|
|
repoToCreate = ghrepo.NewWithHost("", filepath.Base(absPath), host)
|
|
} else if strings.Contains(opts.Name, "/") {
|
|
var err error
|
|
repoToCreate, err = ghrepo.FromFullName(opts.Name)
|
|
if err != nil {
|
|
return fmt.Errorf("argument error: %w", err)
|
|
}
|
|
} else {
|
|
repoToCreate = ghrepo.NewWithHost("", opts.Name, host)
|
|
}
|
|
|
|
input := repoCreateInput{
|
|
Name: repoToCreate.RepoName(),
|
|
Visibility: opts.Visibility,
|
|
OwnerLogin: repoToCreate.RepoOwner(),
|
|
TeamSlug: opts.Team,
|
|
Description: opts.Description,
|
|
HomepageURL: opts.Homepage,
|
|
HasIssuesEnabled: !opts.DisableIssues,
|
|
HasWikiEnabled: !opts.DisableWiki,
|
|
GitIgnoreTemplate: opts.GitIgnoreTemplate,
|
|
LicenseTemplate: opts.LicenseTemplate,
|
|
}
|
|
|
|
repo, err := repoCreate(httpClient, repoToCreate.RepoHost(), input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if isTTY {
|
|
fmt.Fprintf(stdout,
|
|
"%s Created repository %s on GitHub\n",
|
|
cs.SuccessIconWithColor(cs.Green),
|
|
ghrepo.FullName(repo))
|
|
} else {
|
|
fmt.Fprintln(stdout, repo.URL)
|
|
}
|
|
|
|
protocol, err := cfg.GetOrDefault(repo.RepoHost(), "git_protocol")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
|
|
|
|
if opts.Interactive {
|
|
var addRemote bool
|
|
remoteQuesiton := &survey.Confirm{
|
|
Message: `Add a remote?`,
|
|
Default: true,
|
|
}
|
|
err = prompt.SurveyAskOne(remoteQuesiton, &addRemote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !addRemote {
|
|
return nil
|
|
}
|
|
|
|
pushQuestion := &survey.Input{
|
|
Message: "What should the new remote be called?",
|
|
Default: "origin",
|
|
}
|
|
err = prompt.SurveyAskOne(pushQuestion, &baseRemote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := sourceInit(opts.IO, remoteURL, baseRemote, repoPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// don't prompt for push if there are no commits
|
|
if opts.Interactive && committed {
|
|
pushQuestion := &survey.Confirm{
|
|
Message: fmt.Sprintf(`Would you like to push commits from the current branch to %q?`, baseRemote),
|
|
Default: true,
|
|
}
|
|
err = prompt.SurveyAskOne(pushQuestion, &opts.Push)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if opts.Push {
|
|
repoPush, err := git.GitCommand("-C", repoPath, "push", "-u", baseRemote, "HEAD")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = run.PrepareCmd(repoPush).Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if isTTY {
|
|
fmt.Fprintf(stdout, "%s Pushed commits to %s\n", cs.SuccessIcon(), remoteURL)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sourceInit(io *iostreams.IOStreams, remoteURL, baseRemote, repoPath string) error {
|
|
cs := io.ColorScheme()
|
|
isTTY := io.IsStdoutTTY()
|
|
stdout := io.Out
|
|
|
|
remoteAdd, err := git.GitCommand("-C", repoPath, "remote", "add", baseRemote, remoteURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = run.PrepareCmd(remoteAdd).Run()
|
|
if err != nil {
|
|
return fmt.Errorf("%s Unable to add remote %q", cs.FailureIcon(), baseRemote)
|
|
}
|
|
if isTTY {
|
|
fmt.Fprintf(stdout, "%s Added remote %s\n", cs.SuccessIcon(), remoteURL)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// check if local repository has commited changes
|
|
func hasCommits(repoPath string) (bool, error) {
|
|
hasCommitsCmd, err := git.GitCommand("-C", repoPath, "rev-parse", "HEAD")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
prepareCmd := run.PrepareCmd(hasCommitsCmd)
|
|
err = prepareCmd.Run()
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
var execError *exec.ExitError
|
|
if errors.As(err, &execError) {
|
|
exitCode := int(execError.ExitCode())
|
|
if exitCode == 128 {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// check if path is the top level directory of a git repo
|
|
func isLocalRepo(repoPath string) (bool, error) {
|
|
projectDir, projectDirErr := git.GetDirFromPath(repoPath)
|
|
if projectDirErr != nil {
|
|
var execError *exec.ExitError
|
|
if errors.As(projectDirErr, &execError) {
|
|
if exitCode := int(execError.ExitCode()); exitCode == 128 {
|
|
return false, nil
|
|
}
|
|
return false, projectDirErr
|
|
}
|
|
}
|
|
if projectDir != ".git" {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// clone the checkout branch to specified path
|
|
func localInit(io *iostreams.IOStreams, remoteURL, path, checkoutBranch string) error {
|
|
gitInit, err := git.GitCommand("init", path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
isTTY := io.IsStdoutTTY()
|
|
if isTTY {
|
|
gitInit.Stdout = io.Out
|
|
}
|
|
gitInit.Stderr = io.ErrOut
|
|
err = run.PrepareCmd(gitInit).Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gitRemoteAdd, err := git.GitCommand("-C", path, "remote", "add", "origin", remoteURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gitRemoteAdd.Stdout = io.Out
|
|
gitRemoteAdd.Stderr = io.ErrOut
|
|
err = run.PrepareCmd(gitRemoteAdd).Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if checkoutBranch == "" {
|
|
return nil
|
|
}
|
|
|
|
gitFetch, err := git.GitCommand("-C", path, "fetch", "origin", fmt.Sprintf("+refs/heads/%[1]s:refs/remotes/origin/%[1]s", checkoutBranch))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gitFetch.Stdout = io.Out
|
|
gitFetch.Stderr = io.ErrOut
|
|
err = run.PrepareCmd(gitFetch).Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gitCheckout, err := git.GitCommand("-C", path, "checkout", checkoutBranch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gitCheckout.Stdout = io.Out
|
|
gitCheckout.Stderr = io.ErrOut
|
|
return run.PrepareCmd(gitCheckout).Run()
|
|
}
|
|
|
|
func interactiveGitIgnore(client *http.Client, hostname string) (string, error) {
|
|
var addGitIgnore bool
|
|
var addGitIgnoreSurvey []*survey.Question
|
|
|
|
addGitIgnoreQuestion := &survey.Question{
|
|
Name: "addGitIgnore",
|
|
Prompt: &survey.Confirm{
|
|
Message: "Would you like to add a .gitignore?",
|
|
Default: false,
|
|
},
|
|
}
|
|
|
|
addGitIgnoreSurvey = append(addGitIgnoreSurvey, addGitIgnoreQuestion)
|
|
err := prompt.SurveyAsk(addGitIgnoreSurvey, &addGitIgnore)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var wantedIgnoreTemplate string
|
|
|
|
if addGitIgnore {
|
|
var gitIg []*survey.Question
|
|
|
|
gitIgnoretemplates, err := listGitIgnoreTemplates(client, hostname)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
gitIgnoreQuestion := &survey.Question{
|
|
Name: "chooseGitIgnore",
|
|
Prompt: &survey.Select{
|
|
Message: "Choose a .gitignore template",
|
|
Options: gitIgnoretemplates,
|
|
},
|
|
}
|
|
gitIg = append(gitIg, gitIgnoreQuestion)
|
|
err = prompt.SurveyAsk(gitIg, &wantedIgnoreTemplate)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
}
|
|
|
|
return wantedIgnoreTemplate, nil
|
|
}
|
|
|
|
func interactiveLicense(client *http.Client, hostname string) (string, error) {
|
|
var addLicense bool
|
|
var addLicenseSurvey []*survey.Question
|
|
var wantedLicense string
|
|
|
|
addLicenseQuestion := &survey.Question{
|
|
Name: "addLicense",
|
|
Prompt: &survey.Confirm{
|
|
Message: "Would you like to add a license?",
|
|
Default: false,
|
|
},
|
|
}
|
|
|
|
addLicenseSurvey = append(addLicenseSurvey, addLicenseQuestion)
|
|
err := prompt.SurveyAsk(addLicenseSurvey, &addLicense)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
licenseKey := map[string]string{}
|
|
|
|
if addLicense {
|
|
licenseTemplates, err := listLicenseTemplates(client, hostname)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var licenseNames []string
|
|
for _, l := range licenseTemplates {
|
|
licenseNames = append(licenseNames, l.Name)
|
|
licenseKey[l.Name] = l.Key
|
|
}
|
|
var licenseQs []*survey.Question
|
|
|
|
licenseQuestion := &survey.Question{
|
|
Name: "chooseLicense",
|
|
Prompt: &survey.Select{
|
|
Message: "Choose a license",
|
|
Options: licenseNames,
|
|
},
|
|
}
|
|
licenseQs = append(licenseQs, licenseQuestion)
|
|
err = prompt.SurveyAsk(licenseQs, &wantedLicense)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return licenseKey[wantedLicense], nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// name, description, and visibility
|
|
func interactiveRepoInfo(defaultName string) (string, string, string, error) {
|
|
qs := []*survey.Question{
|
|
{
|
|
Name: "repoName",
|
|
Prompt: &survey.Input{
|
|
Message: "Repository name",
|
|
Default: defaultName,
|
|
},
|
|
},
|
|
{
|
|
Name: "repoDescription",
|
|
Prompt: &survey.Input{Message: "Description"},
|
|
},
|
|
{
|
|
Name: "repoVisibility",
|
|
Prompt: &survey.Select{
|
|
Message: "Visibility",
|
|
Options: []string{"Public", "Private", "Internal"},
|
|
},
|
|
}}
|
|
|
|
answer := struct {
|
|
RepoName string
|
|
RepoDescription string
|
|
RepoVisibility string
|
|
}{}
|
|
|
|
err := prompt.SurveyAsk(qs, &answer)
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
|
|
return answer.RepoName, answer.RepoDescription, strings.ToUpper(answer.RepoVisibility), nil
|
|
}
|
|
|
|
func interactiveSource() (string, error) {
|
|
var sourcePath string
|
|
sourcePrompt := &survey.Input{
|
|
Message: "Path to local repository",
|
|
Default: "."}
|
|
|
|
err := prompt.SurveyAskOne(sourcePrompt, &sourcePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return sourcePath, nil
|
|
}
|
|
|
|
func confirmSubmission(repoWithOwner, visibility string) error {
|
|
targetRepo := normalizeRepoName(repoWithOwner)
|
|
if idx := strings.IndexRune(repoWithOwner, '/'); idx > 0 {
|
|
targetRepo = repoWithOwner[0:idx+1] + normalizeRepoName(repoWithOwner[idx+1:])
|
|
}
|
|
var answer struct {
|
|
ConfirmSubmit bool
|
|
}
|
|
err := prompt.SurveyAsk([]*survey.Question{{
|
|
Name: "confirmSubmit",
|
|
Prompt: &survey.Confirm{
|
|
Message: fmt.Sprintf(`This will create "%s" as a %s repository on GitHub. Continue?`, targetRepo, strings.ToLower(visibility)),
|
|
Default: true,
|
|
},
|
|
}}, &answer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !answer.ConfirmSubmit {
|
|
return cmdutil.CancelError
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// normalizeRepoName takes in the repo name the user inputted and normalizes it using the same logic as GitHub (GitHub.com/new)
|
|
func normalizeRepoName(repoName string) string {
|
|
return strings.TrimSuffix(regexp.MustCompile(`[^\w._-]+`).ReplaceAllString(repoName, "-"), ".git")
|
|
}
|