Merge pull request #1421 from cli/migrate-repo-create
isolate repo create command
This commit is contained in:
commit
d84c202a6d
13 changed files with 640 additions and 552 deletions
|
|
@ -2,32 +2,10 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shurcooL/githubv4"
|
||||
)
|
||||
|
||||
// using API v3 here because the equivalent in GraphQL needs `read:org` scope
|
||||
func resolveOrganization(client *Client, orgName string) (string, error) {
|
||||
var response struct {
|
||||
NodeID string `json:"node_id"`
|
||||
}
|
||||
err := client.REST("GET", fmt.Sprintf("users/%s", orgName), nil, &response)
|
||||
return response.NodeID, err
|
||||
}
|
||||
|
||||
// using API v3 here because the equivalent in GraphQL needs `read:org` scope
|
||||
func resolveOrganizationTeam(client *Client, orgName, teamSlug string) (string, string, error) {
|
||||
var response struct {
|
||||
NodeID string `json:"node_id"`
|
||||
Organization struct {
|
||||
NodeID string `json:"node_id"`
|
||||
}
|
||||
}
|
||||
err := client.REST("GET", fmt.Sprintf("orgs/%s/teams/%s", orgName, teamSlug), nil, &response)
|
||||
return response.Organization.NodeID, response.NodeID, err
|
||||
}
|
||||
|
||||
// OrganizationProjects fetches all open projects for an organization
|
||||
func OrganizationProjects(client *Client, owner string) ([]RepoProject, error) {
|
||||
var query struct {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ func GitHubRepo(client *Client, repo ghrepo.Interface) (*Repository, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return initRepoHostname(&result.Repository, repo.RepoHost()), nil
|
||||
return InitRepoHostname(&result.Repository, repo.RepoHost()), nil
|
||||
}
|
||||
|
||||
func RepoDefaultBranch(client *Client, repo ghrepo.Interface) (string, error) {
|
||||
|
|
@ -250,7 +250,7 @@ func RepoNetwork(client *Client, repos []ghrepo.Interface) (RepoNetworkResult, e
|
|||
if err := decoder.Decode(&repo); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Repositories = append(result.Repositories, initRepoHostname(&repo, hostname))
|
||||
result.Repositories = append(result.Repositories, InitRepoHostname(&repo, hostname))
|
||||
} else {
|
||||
return result, fmt.Errorf("unknown GraphQL result key %q", name)
|
||||
}
|
||||
|
|
@ -258,7 +258,7 @@ func RepoNetwork(client *Client, repos []ghrepo.Interface) (RepoNetworkResult, e
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func initRepoHostname(repo *Repository, hostname string) *Repository {
|
||||
func InitRepoHostname(repo *Repository, hostname string) *Repository {
|
||||
repo.hostname = hostname
|
||||
if repo.Parent != nil {
|
||||
repo.Parent.hostname = hostname
|
||||
|
|
@ -338,72 +338,11 @@ func RepoFindFork(client *Client, repo ghrepo.Interface) (*Repository, error) {
|
|||
// `affiliations` condition, to guard against versions of GitHub with a
|
||||
// faulty `affiliations` implementation
|
||||
if len(forks) > 0 && forks[0].ViewerCanPush() {
|
||||
return initRepoHostname(&forks[0], repo.RepoHost()), nil
|
||||
return InitRepoHostname(&forks[0], repo.RepoHost()), nil
|
||||
}
|
||||
return nil, &NotFoundError{errors.New("no fork found")}
|
||||
}
|
||||
|
||||
// RepoCreateInput represents input parameters for RepoCreate
|
||||
type RepoCreateInput struct {
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
HomepageURL string `json:"homepageUrl,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
OwnerID string `json:"ownerId,omitempty"`
|
||||
TeamID string `json:"teamId,omitempty"`
|
||||
|
||||
HasIssuesEnabled bool `json:"hasIssuesEnabled"`
|
||||
HasWikiEnabled bool `json:"hasWikiEnabled"`
|
||||
}
|
||||
|
||||
// RepoCreate creates a new GitHub repository
|
||||
func RepoCreate(client *Client, input RepoCreateInput) (*Repository, error) {
|
||||
var response struct {
|
||||
CreateRepository struct {
|
||||
Repository Repository
|
||||
}
|
||||
}
|
||||
|
||||
if input.TeamID != "" {
|
||||
orgID, teamID, err := resolveOrganizationTeam(client, input.OwnerID, input.TeamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.TeamID = teamID
|
||||
input.OwnerID = orgID
|
||||
} else if input.OwnerID != "" {
|
||||
orgID, err := resolveOrganization(client, input.OwnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.OwnerID = orgID
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"input": input,
|
||||
}
|
||||
|
||||
err := client.GraphQL(`
|
||||
mutation RepositoryCreate($input: CreateRepositoryInput!) {
|
||||
createRepository(input: $input) {
|
||||
repository {
|
||||
id
|
||||
name
|
||||
owner { login }
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`, variables, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: support Enterprise hosts
|
||||
return initRepoHostname(&response.CreateRepository.Repository, "github.com"), nil
|
||||
}
|
||||
|
||||
type RepoMetadataResult struct {
|
||||
AssignableUsers []RepoAssignee
|
||||
Labels []RepoLabel
|
||||
|
|
|
|||
|
|
@ -1,51 +1,12 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
)
|
||||
|
||||
func Test_RepoCreate(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
client := NewClient(ReplaceTripper(http))
|
||||
|
||||
http.StubResponse(200, bytes.NewBufferString(`{}`))
|
||||
|
||||
input := RepoCreateInput{
|
||||
Description: "roasted chesnuts",
|
||||
HomepageURL: "http://example.com",
|
||||
}
|
||||
|
||||
_, err := RepoCreate(client, input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(http.Requests) != 1 {
|
||||
t.Fatalf("expected 1 HTTP request, seen %d", len(http.Requests))
|
||||
}
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[0].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if description := reqBody.Variables.Input["description"].(string); description != "roasted chesnuts" {
|
||||
t.Errorf("expected description to be %q, got %q", "roasted chesnuts", description)
|
||||
}
|
||||
if homepage := reqBody.Variables.Input["homepageUrl"].(string); homepage != "http://example.com" {
|
||||
t.Errorf("expected homepageUrl to be %q, got %q", "http://example.com", homepage)
|
||||
}
|
||||
}
|
||||
func Test_RepoMetadata(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
client := NewClient(ReplaceTripper(http))
|
||||
|
|
|
|||
179
command/repo.go
179
command/repo.go
|
|
@ -4,30 +4,20 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
repoCmd.AddCommand(repoCreateCmd)
|
||||
repoCreateCmd.Flags().StringP("description", "d", "", "Description of repository")
|
||||
repoCreateCmd.Flags().StringP("homepage", "h", "", "Repository home page URL")
|
||||
repoCreateCmd.Flags().StringP("team", "t", "", "The name of the organization team to be granted access")
|
||||
repoCreateCmd.Flags().Bool("enable-issues", true, "Enable issues in the new repository")
|
||||
repoCreateCmd.Flags().Bool("enable-wiki", true, "Enable wiki in the new repository")
|
||||
repoCreateCmd.Flags().Bool("public", false, "Make the new repository public (default: private)")
|
||||
|
||||
repoCmd.AddCommand(repoForkCmd)
|
||||
repoForkCmd.Flags().String("clone", "prompt", "Clone fork: {true|false|prompt}")
|
||||
repoForkCmd.Flags().String("remote", "prompt", "Add remote for fork: {true|false|prompt}")
|
||||
|
|
@ -55,26 +45,6 @@ A repository can be supplied as an argument in any of the following formats:
|
|||
- by URL, e.g. "https://github.com/OWNER/REPO"`},
|
||||
}
|
||||
|
||||
var repoCreateCmd = &cobra.Command{
|
||||
Use: "create [<name>]",
|
||||
Short: "Create a new repository",
|
||||
Long: `Create a new GitHub repository.`,
|
||||
Example: heredoc.Doc(`
|
||||
# create a repository under your account using the current directory name
|
||||
$ gh repo create
|
||||
|
||||
# create a repository with a specific name
|
||||
$ gh repo create my-project
|
||||
|
||||
# create a repository in an organization
|
||||
$ gh repo create cli/my-project
|
||||
`),
|
||||
Annotations: map[string]string{"help:arguments": `A repository can be supplied as an argument in any of the following formats:
|
||||
- <OWNER/REPO>
|
||||
- by URL, e.g. "https://github.com/OWNER/REPO"`},
|
||||
RunE: repoCreate,
|
||||
}
|
||||
|
||||
var repoForkCmd = &cobra.Command{
|
||||
Use: "fork [<repository>]",
|
||||
Short: "Create a fork of a repository",
|
||||
|
|
@ -105,141 +75,6 @@ var repoCreditsCmd = &cobra.Command{
|
|||
Hidden: true,
|
||||
}
|
||||
|
||||
func repoCreate(cmd *cobra.Command, args []string) error {
|
||||
projectDir, projectDirErr := git.ToplevelDir()
|
||||
|
||||
orgName := ""
|
||||
teamSlug, err := cmd.Flags().GetString("team")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var name string
|
||||
if len(args) > 0 {
|
||||
name = args[0]
|
||||
if strings.Contains(name, "/") {
|
||||
newRepo, err := ghrepo.FromFullName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("argument error: %w", err)
|
||||
}
|
||||
orgName = newRepo.RepoOwner()
|
||||
name = newRepo.RepoName()
|
||||
}
|
||||
} else {
|
||||
if projectDirErr != nil {
|
||||
return projectDirErr
|
||||
}
|
||||
name = path.Base(projectDir)
|
||||
}
|
||||
|
||||
isPublic, err := cmd.Flags().GetBool("public")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasIssuesEnabled, err := cmd.Flags().GetBool("enable-issues")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasWikiEnabled, err := cmd.Flags().GetBool("enable-wiki")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
description, err := cmd.Flags().GetString("description")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
homepage, err := cmd.Flags().GetString("homepage")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: move this into constant within `api`
|
||||
visibility := "PRIVATE"
|
||||
if isPublic {
|
||||
visibility = "PUBLIC"
|
||||
}
|
||||
|
||||
input := api.RepoCreateInput{
|
||||
Name: name,
|
||||
Visibility: visibility,
|
||||
OwnerID: orgName,
|
||||
TeamID: teamSlug,
|
||||
Description: description,
|
||||
HomepageURL: homepage,
|
||||
HasIssuesEnabled: hasIssuesEnabled,
|
||||
HasWikiEnabled: hasWikiEnabled,
|
||||
}
|
||||
|
||||
ctx := contextForCommand(cmd)
|
||||
client, err := apiClientForContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := api.RepoCreate(client, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
greenCheck := utils.Green("✓")
|
||||
isTTY := false
|
||||
if outFile, isFile := out.(*os.File); isFile {
|
||||
isTTY = utils.IsTerminal(outFile)
|
||||
if isTTY {
|
||||
// FIXME: duplicates colorableOut
|
||||
out = utils.NewColorable(outFile)
|
||||
}
|
||||
}
|
||||
|
||||
if isTTY {
|
||||
fmt.Fprintf(out, "%s Created repository %s on GitHub\n", greenCheck, ghrepo.FullName(repo))
|
||||
} else {
|
||||
fmt.Fprintln(out, repo.URL)
|
||||
}
|
||||
|
||||
remoteURL := formatRemoteURL(cmd, repo)
|
||||
|
||||
if projectDirErr == nil {
|
||||
_, err = git.AddRemote("origin", remoteURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isTTY {
|
||||
fmt.Fprintf(out, "%s Added remote %s\n", greenCheck, remoteURL)
|
||||
}
|
||||
} else if isTTY {
|
||||
doSetup := false
|
||||
err := Confirm(fmt.Sprintf("Create a local project directory for %s?", ghrepo.FullName(repo)), &doSetup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if doSetup {
|
||||
path := repo.Name
|
||||
|
||||
gitInit := git.GitCommand("init", path)
|
||||
gitInit.Stdout = os.Stdout
|
||||
gitInit.Stderr = os.Stderr
|
||||
err = run.PrepareCmd(gitInit).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gitRemoteAdd := git.GitCommand("-C", path, "remote", "add", "origin", remoteURL)
|
||||
gitRemoteAdd.Stdout = os.Stdout
|
||||
gitRemoteAdd.Stderr = os.Stderr
|
||||
err = run.PrepareCmd(gitRemoteAdd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "%s Initialized repository in './%s/'\n", greenCheck, path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var Since = func(t time.Time) time.Duration {
|
||||
return time.Since(t)
|
||||
}
|
||||
|
|
@ -371,7 +206,7 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
|
||||
remoteDesired := remotePref == "true"
|
||||
if remotePref == "prompt" {
|
||||
err = Confirm("Would you like to add a remote for the fork?", &remoteDesired)
|
||||
err = prompt.Confirm("Would you like to add a remote for the fork?", &remoteDesired)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prompt: %w", err)
|
||||
}
|
||||
|
|
@ -409,7 +244,7 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
} else {
|
||||
cloneDesired := clonePref == "true"
|
||||
if clonePref == "prompt" {
|
||||
err = Confirm("Would you like to clone the fork?", &cloneDesired)
|
||||
err = prompt.Confirm("Would you like to clone the fork?", &cloneDesired)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prompt: %w", err)
|
||||
}
|
||||
|
|
@ -447,14 +282,6 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var Confirm = func(prompt string, result *bool) error {
|
||||
p := &survey.Confirm{
|
||||
Message: prompt,
|
||||
Default: true,
|
||||
}
|
||||
return survey.AskOne(p, result)
|
||||
}
|
||||
|
||||
func repoCredits(cmd *cobra.Command, args []string) error {
|
||||
return credits(cmd, args)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
|
@ -13,7 +11,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/context"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/test"
|
||||
"github.com/cli/cli/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -296,12 +294,7 @@ func TestRepoFork_outside_survey_yes(t *testing.T) {
|
|||
cs.Stub("") // git clone
|
||||
cs.Stub("") // git remote add
|
||||
|
||||
oldConfirm := Confirm
|
||||
Confirm = func(_ string, result *bool) error {
|
||||
*result = true
|
||||
return nil
|
||||
}
|
||||
defer func() { Confirm = oldConfirm }()
|
||||
defer prompt.StubConfirm(true)()
|
||||
|
||||
output, err := RunCommand("repo fork OWNER/REPO")
|
||||
if err != nil {
|
||||
|
|
@ -331,12 +324,7 @@ func TestRepoFork_outside_survey_no(t *testing.T) {
|
|||
return &test.OutputStub{}
|
||||
})()
|
||||
|
||||
oldConfirm := Confirm
|
||||
Confirm = func(_ string, result *bool) error {
|
||||
*result = false
|
||||
return nil
|
||||
}
|
||||
defer func() { Confirm = oldConfirm }()
|
||||
defer prompt.StubConfirm(false)()
|
||||
|
||||
output, err := RunCommand("repo fork OWNER/REPO")
|
||||
if err != nil {
|
||||
|
|
@ -369,12 +357,7 @@ func TestRepoFork_in_parent_survey_yes(t *testing.T) {
|
|||
return &test.OutputStub{}
|
||||
})()
|
||||
|
||||
oldConfirm := Confirm
|
||||
Confirm = func(_ string, result *bool) error {
|
||||
*result = true
|
||||
return nil
|
||||
}
|
||||
defer func() { Confirm = oldConfirm }()
|
||||
defer prompt.StubConfirm(true)()
|
||||
|
||||
output, err := RunCommand("repo fork")
|
||||
if err != nil {
|
||||
|
|
@ -413,12 +396,7 @@ func TestRepoFork_in_parent_survey_no(t *testing.T) {
|
|||
return &test.OutputStub{}
|
||||
})()
|
||||
|
||||
oldConfirm := Confirm
|
||||
Confirm = func(_ string, result *bool) error {
|
||||
*result = false
|
||||
return nil
|
||||
}
|
||||
defer func() { Confirm = oldConfirm }()
|
||||
defer prompt.StubConfirm(false)()
|
||||
|
||||
output, err := RunCommand("repo fork")
|
||||
if err != nil {
|
||||
|
|
@ -435,210 +413,3 @@ func TestRepoFork_in_parent_survey_no(t *testing.T) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCreate(t *testing.T) {
|
||||
ctx := context.NewBlank()
|
||||
ctx.SetBranch("master")
|
||||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
http := initFakeHTTP()
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/OWNER/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "OWNER"
|
||||
}
|
||||
}
|
||||
} } }`))
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand("repo create REPO")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo create`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "https://github.com/OWNER/REPO\n")
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add -f origin https://github.com/OWNER/REPO.git")
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(http.Requests) != 1 {
|
||||
t.Fatalf("expected 1 HTTP request, got %d", len(http.Requests))
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[0].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" {
|
||||
t.Errorf("expected %q, got %q", "REPO", repoName)
|
||||
}
|
||||
if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" {
|
||||
t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility)
|
||||
}
|
||||
if _, ownerSet := reqBody.Variables.Input["ownerId"]; ownerSet {
|
||||
t.Error("expected ownerId not to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCreate_org(t *testing.T) {
|
||||
ctx := context.NewBlank()
|
||||
ctx.SetBranch("master")
|
||||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
http := initFakeHTTP()
|
||||
http.Register(
|
||||
httpmock.REST("GET", "users/ORG"),
|
||||
httpmock.StringResponse(`
|
||||
{ "node_id": "ORGID"
|
||||
}`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/ORG/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "ORG"
|
||||
}
|
||||
}
|
||||
} } }`))
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand("repo create ORG/REPO")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo create`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "https://github.com/ORG/REPO\n")
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add -f origin https://github.com/ORG/REPO.git")
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(http.Requests) != 2 {
|
||||
t.Fatalf("expected 2 HTTP requests, got %d", len(http.Requests))
|
||||
}
|
||||
|
||||
eq(t, http.Requests[0].URL.Path, "/users/ORG")
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[1].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if orgID := reqBody.Variables.Input["ownerId"].(string); orgID != "ORGID" {
|
||||
t.Errorf("expected %q, got %q", "ORGID", orgID)
|
||||
}
|
||||
if _, teamSet := reqBody.Variables.Input["teamId"]; teamSet {
|
||||
t.Error("expected teamId not to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCreate_orgWithTeam(t *testing.T) {
|
||||
ctx := context.NewBlank()
|
||||
ctx.SetBranch("master")
|
||||
initContext = func() context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
http := initFakeHTTP()
|
||||
http.Register(
|
||||
httpmock.REST("GET", "orgs/ORG/teams/monkeys"),
|
||||
httpmock.StringResponse(`
|
||||
{ "node_id": "TEAMID",
|
||||
"organization": { "node_id": "ORGID" }
|
||||
}`))
|
||||
http.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/ORG/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "ORG"
|
||||
}
|
||||
}
|
||||
} } }`))
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := RunCommand("repo create ORG/REPO --team monkeys")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo create`: %v", err)
|
||||
}
|
||||
|
||||
eq(t, output.String(), "https://github.com/ORG/REPO\n")
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add -f origin https://github.com/ORG/REPO.git")
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(http.Requests) != 2 {
|
||||
t.Fatalf("expected 2 HTTP requests, got %d", len(http.Requests))
|
||||
}
|
||||
|
||||
eq(t, http.Requests[0].URL.Path, "/orgs/ORG/teams/monkeys")
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[1].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if orgID := reqBody.Variables.Input["ownerId"].(string); orgID != "ORGID" {
|
||||
t.Errorf("expected %q, got %q", "ORGID", orgID)
|
||||
}
|
||||
if teamID := reqBody.Variables.Input["teamId"].(string); teamID != "TEAMID" {
|
||||
t.Errorf("expected %q, got %q", "TEAMID", teamID)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
apiCmd "github.com/cli/cli/pkg/cmd/api"
|
||||
gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create"
|
||||
repoCloneCmd "github.com/cli/cli/pkg/cmd/repo/clone"
|
||||
repoCreateCmd "github.com/cli/cli/pkg/cmd/repo/create"
|
||||
repoViewCmd "github.com/cli/cli/pkg/cmd/repo/view"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
|
|
@ -148,6 +149,7 @@ func init() {
|
|||
RootCmd.AddCommand(repoCmd)
|
||||
repoCmd.AddCommand(repoViewCmd.NewCmdView(&repoResolvingCmdFactory, nil))
|
||||
repoCmd.AddCommand(repoCloneCmd.NewCmdClone(cmdFactory, nil))
|
||||
repoCmd.AddCommand(repoCreateCmd.NewCmdCreate(cmdFactory, nil))
|
||||
}
|
||||
|
||||
// RootCmd is the entry point of command-line execution
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package clone
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -15,20 +14,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO copypasta from command package
|
||||
type cmdOut struct {
|
||||
outBuf, errBuf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (c cmdOut) String() string {
|
||||
return c.outBuf.String()
|
||||
}
|
||||
|
||||
func (c cmdOut) Stderr() string {
|
||||
return c.errBuf.String()
|
||||
}
|
||||
|
||||
func runCloneCommand(httpClient *http.Client, cli string) (*cmdOut, error) {
|
||||
func runCloneCommand(httpClient *http.Client, cli string) (*test.CmdOut, error) {
|
||||
io, stdin, stdout, stderr := iostreams.Test()
|
||||
fac := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
|
|
@ -59,7 +45,7 @@ func runCloneCommand(httpClient *http.Client, cli string) (*cmdOut, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &cmdOut{stdout, stderr}, nil
|
||||
return &test.CmdOut{OutBuf: stdout, ErrBuf: stderr}, nil
|
||||
}
|
||||
|
||||
func Test_RepoClone(t *testing.T) {
|
||||
|
|
|
|||
193
pkg/cmd/repo/create/create.go
Normal file
193
pkg/cmd/repo/create/create.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/git"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/utils"
|
||||
"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
|
||||
EnableIssues bool
|
||||
EnableWiki bool
|
||||
Public bool
|
||||
}
|
||||
|
||||
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
|
||||
opts := &CreateOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [<name>]",
|
||||
Short: "Create a new repository",
|
||||
Long: `Create a new GitHub repository.`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Example: heredoc.Doc(`
|
||||
# create a repository under your account using the current directory name
|
||||
$ gh repo create
|
||||
|
||||
# create a repository with a specific name
|
||||
$ gh repo create my-project
|
||||
|
||||
# create a repository in an organization
|
||||
$ gh repo create cli/my-project
|
||||
`),
|
||||
Annotations: map[string]string{
|
||||
"help:arguments": heredoc.Doc(
|
||||
`A repository can be supplied as an argument in any of the following formats:
|
||||
- <OWNER/REPO>
|
||||
- by URL, e.g. "https://github.com/OWNER/REPO"`),
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
opts.Name = args[0]
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return createRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of 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().BoolVar(&opts.EnableIssues, "enable-issues", true, "Enable issues in the new repository")
|
||||
cmd.Flags().BoolVar(&opts.EnableWiki, "enable-wiki", true, "Enable wiki in the new repository")
|
||||
cmd.Flags().BoolVar(&opts.Public, "public", false, "Make the new repository public (default: private)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createRun(opts *CreateOptions) error {
|
||||
projectDir, projectDirErr := git.ToplevelDir()
|
||||
|
||||
orgName := ""
|
||||
name := opts.Name
|
||||
|
||||
if name != "" {
|
||||
if strings.Contains(name, "/") {
|
||||
newRepo, err := ghrepo.FromFullName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("argument error: %w", err)
|
||||
}
|
||||
orgName = newRepo.RepoOwner()
|
||||
name = newRepo.RepoName()
|
||||
}
|
||||
} else {
|
||||
if projectDirErr != nil {
|
||||
return projectDirErr
|
||||
}
|
||||
name = path.Base(projectDir)
|
||||
}
|
||||
|
||||
visibility := "PRIVATE"
|
||||
if opts.Public {
|
||||
visibility = "PUBLIC"
|
||||
}
|
||||
|
||||
input := repoCreateInput{
|
||||
Name: name,
|
||||
Visibility: visibility,
|
||||
OwnerID: orgName,
|
||||
TeamID: opts.Team,
|
||||
Description: opts.Description,
|
||||
HomepageURL: opts.Homepage,
|
||||
HasIssuesEnabled: opts.EnableIssues,
|
||||
HasWikiEnabled: opts.EnableWiki,
|
||||
}
|
||||
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := repoCreate(httpClient, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr := opts.IO.ErrOut
|
||||
stdout := opts.IO.Out
|
||||
greenCheck := utils.Green("✓")
|
||||
isTTY := opts.IO.IsStdoutTTY()
|
||||
|
||||
if isTTY {
|
||||
fmt.Fprintf(stderr, "%s Created repository %s on GitHub\n", greenCheck, ghrepo.FullName(repo))
|
||||
} else {
|
||||
fmt.Fprintln(stdout, repo.URL)
|
||||
}
|
||||
|
||||
// TODO This is overly wordy and I'd like to streamline this.
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
protocol, err := cfg.Get("", "git_protocol")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remoteURL := ghrepo.FormatRemoteURL(repo, protocol)
|
||||
|
||||
if projectDirErr == nil {
|
||||
_, err = git.AddRemote("origin", remoteURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isTTY {
|
||||
fmt.Fprintf(stderr, "%s Added remote %s\n", greenCheck, remoteURL)
|
||||
}
|
||||
} else if isTTY {
|
||||
doSetup := false
|
||||
err := prompt.Confirm(fmt.Sprintf("Create a local project directory for %s?", ghrepo.FullName(repo)), &doSetup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if doSetup {
|
||||
path := repo.Name
|
||||
|
||||
gitInit := git.GitCommand("init", path)
|
||||
gitInit.Stdout = stdout
|
||||
gitInit.Stderr = stderr
|
||||
err = run.PrepareCmd(gitInit).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gitRemoteAdd := git.GitCommand("-C", path, "remote", "add", "origin", remoteURL)
|
||||
gitRemoteAdd.Stdout = stdout
|
||||
gitRemoteAdd.Stderr = stderr
|
||||
err = run.PrepareCmd(gitRemoteAdd).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "%s Initialized repository in './%s/'\n", greenCheck, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
256
pkg/cmd/repo/create/create_test.go
Normal file
256
pkg/cmd/repo/create/create_test.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/run"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/test"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func runCommand(httpClient *http.Client, cli string) (*test.CmdOut, error) {
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
fac := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
return httpClient, nil
|
||||
},
|
||||
Config: func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdCreate(fac, nil)
|
||||
|
||||
// TODO STUPID HACK
|
||||
// cobra aggressively adds help to all commands. since we're not running through the root command
|
||||
// (which manages help when running for real) and since create has a '-h' flag (for homepage),
|
||||
// cobra blows up when it tried to add a help flag and -h is already in use. This hack adds a
|
||||
// dummy help flag with a random shorthand to get around this.
|
||||
cmd.Flags().BoolP("help", "x", false, "")
|
||||
|
||||
argv, err := shlex.Split(cli)
|
||||
cmd.SetArgs(argv)
|
||||
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &test.CmdOut{
|
||||
OutBuf: stdout,
|
||||
ErrBuf: stderr}, nil
|
||||
}
|
||||
|
||||
func TestRepoCreate(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/OWNER/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "OWNER"
|
||||
}
|
||||
}
|
||||
} } }`))
|
||||
|
||||
httpClient := &http.Client{Transport: reg}
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := runCommand(httpClient, "REPO")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo create`: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "https://github.com/OWNER/REPO\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
assert.Equal(t, "git remote add -f origin https://github.com/OWNER/REPO.git", strings.Join(seenCmd.Args, " "))
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(reg.Requests) != 1 {
|
||||
t.Fatalf("expected 1 HTTP request, got %d", len(reg.Requests))
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(reg.Requests[0].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if repoName := reqBody.Variables.Input["name"].(string); repoName != "REPO" {
|
||||
t.Errorf("expected %q, got %q", "REPO", repoName)
|
||||
}
|
||||
if repoVisibility := reqBody.Variables.Input["visibility"].(string); repoVisibility != "PRIVATE" {
|
||||
t.Errorf("expected %q, got %q", "PRIVATE", repoVisibility)
|
||||
}
|
||||
if _, ownerSet := reqBody.Variables.Input["ownerId"]; ownerSet {
|
||||
t.Error("expected ownerId not to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCreate_org(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "users/ORG"),
|
||||
httpmock.StringResponse(`
|
||||
{ "node_id": "ORGID"
|
||||
}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/ORG/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "ORG"
|
||||
}
|
||||
}
|
||||
} } }`))
|
||||
httpClient := &http.Client{Transport: reg}
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := runCommand(httpClient, "ORG/REPO")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo create`: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "https://github.com/ORG/REPO\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
assert.Equal(t, "git remote add -f origin https://github.com/ORG/REPO.git", strings.Join(seenCmd.Args, " "))
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(reg.Requests) != 2 {
|
||||
t.Fatalf("expected 2 HTTP requests, got %d", len(reg.Requests))
|
||||
}
|
||||
|
||||
assert.Equal(t, "/users/ORG", reg.Requests[0].URL.Path)
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if orgID := reqBody.Variables.Input["ownerId"].(string); orgID != "ORGID" {
|
||||
t.Errorf("expected %q, got %q", "ORGID", orgID)
|
||||
}
|
||||
if _, teamSet := reqBody.Variables.Input["teamId"]; teamSet {
|
||||
t.Error("expected teamId not to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCreate_orgWithTeam(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "orgs/ORG/teams/monkeys"),
|
||||
httpmock.StringResponse(`
|
||||
{ "node_id": "TEAMID",
|
||||
"organization": { "node_id": "ORGID" }
|
||||
}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation RepositoryCreate\b`),
|
||||
httpmock.StringResponse(`
|
||||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/ORG/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "ORG"
|
||||
}
|
||||
}
|
||||
} } }`))
|
||||
httpClient := &http.Client{Transport: reg}
|
||||
|
||||
var seenCmd *exec.Cmd
|
||||
restoreCmd := run.SetPrepareCmd(func(cmd *exec.Cmd) run.Runnable {
|
||||
seenCmd = cmd
|
||||
return &test.OutputStub{}
|
||||
})
|
||||
defer restoreCmd()
|
||||
|
||||
output, err := runCommand(httpClient, "ORG/REPO --team monkeys")
|
||||
if err != nil {
|
||||
t.Errorf("error running command `repo create`: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "https://github.com/ORG/REPO\n", output.String())
|
||||
assert.Equal(t, "", output.Stderr())
|
||||
|
||||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
assert.Equal(t, "git remote add -f origin https://github.com/ORG/REPO.git", strings.Join(seenCmd.Args, " "))
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(reg.Requests) != 2 {
|
||||
t.Fatalf("expected 2 HTTP requests, got %d", len(reg.Requests))
|
||||
}
|
||||
|
||||
assert.Equal(t, "/orgs/ORG/teams/monkeys", reg.Requests[0].URL.Path)
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if orgID := reqBody.Variables.Input["ownerId"].(string); orgID != "ORGID" {
|
||||
t.Errorf("expected %q, got %q", "ORGID", orgID)
|
||||
}
|
||||
if teamID := reqBody.Variables.Input["teamId"].(string); teamID != "TEAMID" {
|
||||
t.Errorf("expected %q, got %q", "TEAMID", teamID)
|
||||
}
|
||||
}
|
||||
92
pkg/cmd/repo/create/http.go
Normal file
92
pkg/cmd/repo/create/http.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
)
|
||||
|
||||
// repoCreateInput represents input parameters for repoCreate
|
||||
type repoCreateInput struct {
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
HomepageURL string `json:"homepageUrl,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
OwnerID string `json:"ownerId,omitempty"`
|
||||
TeamID string `json:"teamId,omitempty"`
|
||||
|
||||
HasIssuesEnabled bool `json:"hasIssuesEnabled"`
|
||||
HasWikiEnabled bool `json:"hasWikiEnabled"`
|
||||
}
|
||||
|
||||
// repoCreate creates a new GitHub repository
|
||||
func repoCreate(client *http.Client, input repoCreateInput) (*api.Repository, error) {
|
||||
apiClient := api.NewClientFromHTTP(client)
|
||||
|
||||
var response struct {
|
||||
CreateRepository struct {
|
||||
Repository api.Repository
|
||||
}
|
||||
}
|
||||
|
||||
if input.TeamID != "" {
|
||||
orgID, teamID, err := resolveOrganizationTeam(apiClient, input.OwnerID, input.TeamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.TeamID = teamID
|
||||
input.OwnerID = orgID
|
||||
} else if input.OwnerID != "" {
|
||||
orgID, err := resolveOrganization(apiClient, input.OwnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.OwnerID = orgID
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"input": input,
|
||||
}
|
||||
|
||||
err := apiClient.GraphQL(`
|
||||
mutation RepositoryCreate($input: CreateRepositoryInput!) {
|
||||
createRepository(input: $input) {
|
||||
repository {
|
||||
id
|
||||
name
|
||||
owner { login }
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`, variables, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: support Enterprise hosts
|
||||
return api.InitRepoHostname(&response.CreateRepository.Repository, "github.com"), nil
|
||||
}
|
||||
|
||||
// using API v3 here because the equivalent in GraphQL needs `read:org` scope
|
||||
func resolveOrganization(client *api.Client, orgName string) (string, error) {
|
||||
var response struct {
|
||||
NodeID string `json:"node_id"`
|
||||
}
|
||||
err := client.REST("GET", fmt.Sprintf("users/%s", orgName), nil, &response)
|
||||
return response.NodeID, err
|
||||
}
|
||||
|
||||
// using API v3 here because the equivalent in GraphQL needs `read:org` scope
|
||||
func resolveOrganizationTeam(client *api.Client, orgName, teamSlug string) (string, string, error) {
|
||||
var response struct {
|
||||
NodeID string `json:"node_id"`
|
||||
Organization struct {
|
||||
NodeID string `json:"node_id"`
|
||||
}
|
||||
}
|
||||
err := client.REST("GET", fmt.Sprintf("orgs/%s/teams/%s", orgName, teamSlug), nil, &response)
|
||||
return response.Organization.NodeID, response.NodeID, err
|
||||
}
|
||||
48
pkg/cmd/repo/create/http_test.go
Normal file
48
pkg/cmd/repo/create/http_test.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package create
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
)
|
||||
|
||||
func Test_RepoCreate(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
httpClient := api.NewHTTPClient(api.ReplaceTripper(reg))
|
||||
|
||||
reg.StubResponse(200, bytes.NewBufferString(`{}`))
|
||||
|
||||
input := repoCreateInput{
|
||||
Description: "roasted chesnuts",
|
||||
HomepageURL: "http://example.com",
|
||||
}
|
||||
|
||||
_, err := repoCreate(httpClient, input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(reg.Requests) != 1 {
|
||||
t.Fatalf("expected 1 HTTP request, seen %d", len(reg.Requests))
|
||||
}
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
Variables struct {
|
||||
Input map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(reg.Requests[0].Body)
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if description := reqBody.Variables.Input["description"].(string); description != "roasted chesnuts" {
|
||||
t.Errorf("expected description to be %q, got %q", "roasted chesnuts", description)
|
||||
}
|
||||
if homepage := reqBody.Variables.Input["homepageUrl"].(string); homepage != "http://example.com" {
|
||||
t.Errorf("expected homepageUrl to be %q, got %q", "http://example.com", homepage)
|
||||
}
|
||||
}
|
||||
22
pkg/prompt/prompt.go
Normal file
22
pkg/prompt/prompt.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package prompt
|
||||
|
||||
import "github.com/AlecAivazis/survey/v2"
|
||||
|
||||
func StubConfirm(result bool) func() {
|
||||
orig := Confirm
|
||||
Confirm = func(_ string, r *bool) error {
|
||||
*r = result
|
||||
return nil
|
||||
}
|
||||
return func() {
|
||||
Confirm = orig
|
||||
}
|
||||
}
|
||||
|
||||
var Confirm = func(prompt string, result *bool) error {
|
||||
p := &survey.Confirm{
|
||||
Message: prompt,
|
||||
Default: true,
|
||||
}
|
||||
return survey.AskOne(p, result)
|
||||
}
|
||||
|
|
@ -11,6 +11,19 @@ import (
|
|||
"github.com/cli/cli/internal/run"
|
||||
)
|
||||
|
||||
// TODO copypasta from command package
|
||||
type CmdOut struct {
|
||||
OutBuf, ErrBuf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (c CmdOut) String() string {
|
||||
return c.OutBuf.String()
|
||||
}
|
||||
|
||||
func (c CmdOut) Stderr() string {
|
||||
return c.ErrBuf.String()
|
||||
}
|
||||
|
||||
// OutputStub implements a simple utils.Runnable
|
||||
type OutputStub struct {
|
||||
Out []byte
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue