1package create 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "strings" 9 10 "github.com/cli/cli/v2/api" 11) 12 13// repoCreateInput is input parameters for the repoCreate method 14type repoCreateInput struct { 15 Name string 16 HomepageURL string 17 Description string 18 Visibility string 19 OwnerLogin string 20 TeamSlug string 21 TemplateRepositoryID string 22 HasIssuesEnabled bool 23 HasWikiEnabled bool 24 GitIgnoreTemplate string 25 LicenseTemplate string 26} 27 28// createRepositoryInputV3 is the payload for the repo create REST API 29type createRepositoryInputV3 struct { 30 Name string `json:"name"` 31 HomepageURL string `json:"homepage,omitempty"` 32 Description string `json:"description,omitempty"` 33 IsPrivate bool `json:"private"` 34 Visibility string `json:"visibility,omitempty"` 35 TeamID uint64 `json:"team_id,omitempty"` 36 HasIssuesEnabled bool `json:"has_issues"` 37 HasWikiEnabled bool `json:"has_wiki"` 38 GitIgnoreTemplate string `json:"gitignore_template,omitempty"` 39 LicenseTemplate string `json:"license_template,omitempty"` 40} 41 42// createRepositoryInput is the payload for the repo create GraphQL mutation 43type createRepositoryInput struct { 44 Name string `json:"name"` 45 HomepageURL string `json:"homepageUrl,omitempty"` 46 Description string `json:"description,omitempty"` 47 Visibility string `json:"visibility"` 48 OwnerID string `json:"ownerId,omitempty"` 49 TeamID string `json:"teamId,omitempty"` 50 HasIssuesEnabled bool `json:"hasIssuesEnabled"` 51 HasWikiEnabled bool `json:"hasWikiEnabled"` 52} 53 54// cloneTemplateRepositoryInput is the payload for creating a repo from a template using GraphQL 55type cloneTemplateRepositoryInput struct { 56 Name string `json:"name"` 57 Visibility string `json:"visibility"` 58 Description string `json:"description,omitempty"` 59 OwnerID string `json:"ownerId"` 60 RepositoryID string `json:"repositoryId"` 61} 62 63// repoCreate creates a new GitHub repository 64func repoCreate(client *http.Client, hostname string, input repoCreateInput) (*api.Repository, error) { 65 isOrg := false 66 var ownerID string 67 var teamID string 68 var teamIDv3 uint64 69 70 apiClient := api.NewClientFromHTTP(client) 71 72 if input.TeamSlug != "" { 73 team, err := resolveOrganizationTeam(apiClient, hostname, input.OwnerLogin, input.TeamSlug) 74 if err != nil { 75 return nil, err 76 } 77 teamIDv3 = team.ID 78 teamID = team.NodeID 79 ownerID = team.Organization.NodeID 80 isOrg = true 81 } else if input.OwnerLogin != "" { 82 owner, err := resolveOwner(apiClient, hostname, input.OwnerLogin) 83 if err != nil { 84 return nil, err 85 } 86 ownerID = owner.NodeID 87 isOrg = owner.IsOrganization() 88 } 89 90 if input.TemplateRepositoryID != "" { 91 var response struct { 92 CloneTemplateRepository struct { 93 Repository api.Repository 94 } 95 } 96 97 if ownerID == "" { 98 var err error 99 ownerID, err = api.CurrentUserID(apiClient, hostname) 100 if err != nil { 101 return nil, err 102 } 103 } 104 105 variables := map[string]interface{}{ 106 "input": cloneTemplateRepositoryInput{ 107 Name: input.Name, 108 Description: input.Description, 109 Visibility: strings.ToUpper(input.Visibility), 110 OwnerID: ownerID, 111 RepositoryID: input.TemplateRepositoryID, 112 }, 113 } 114 115 err := apiClient.GraphQL(hostname, ` 116 mutation CloneTemplateRepository($input: CloneTemplateRepositoryInput!) { 117 cloneTemplateRepository(input: $input) { 118 repository { 119 id 120 name 121 owner { login } 122 url 123 } 124 } 125 } 126 `, variables, &response) 127 if err != nil { 128 return nil, err 129 } 130 131 return api.InitRepoHostname(&response.CloneTemplateRepository.Repository, hostname), nil 132 } 133 134 if input.GitIgnoreTemplate != "" || input.LicenseTemplate != "" { 135 inputv3 := createRepositoryInputV3{ 136 Name: input.Name, 137 HomepageURL: input.HomepageURL, 138 Description: input.Description, 139 IsPrivate: strings.EqualFold(input.Visibility, "PRIVATE"), 140 TeamID: teamIDv3, 141 HasIssuesEnabled: input.HasIssuesEnabled, 142 HasWikiEnabled: input.HasWikiEnabled, 143 GitIgnoreTemplate: input.GitIgnoreTemplate, 144 LicenseTemplate: input.LicenseTemplate, 145 } 146 147 path := "user/repos" 148 if isOrg { 149 path = fmt.Sprintf("orgs/%s/repos", input.OwnerLogin) 150 inputv3.Visibility = strings.ToLower(input.Visibility) 151 } 152 153 body := &bytes.Buffer{} 154 enc := json.NewEncoder(body) 155 if err := enc.Encode(inputv3); err != nil { 156 return nil, err 157 } 158 159 repo, err := api.CreateRepoTransformToV4(apiClient, hostname, "POST", path, body) 160 if err != nil { 161 return nil, err 162 } 163 return repo, nil 164 } 165 166 var response struct { 167 CreateRepository struct { 168 Repository api.Repository 169 } 170 } 171 172 variables := map[string]interface{}{ 173 "input": createRepositoryInput{ 174 Name: input.Name, 175 Description: input.Description, 176 HomepageURL: input.HomepageURL, 177 Visibility: strings.ToUpper(input.Visibility), 178 OwnerID: ownerID, 179 TeamID: teamID, 180 HasIssuesEnabled: input.HasIssuesEnabled, 181 HasWikiEnabled: input.HasWikiEnabled, 182 }, 183 } 184 185 err := apiClient.GraphQL(hostname, ` 186 mutation RepositoryCreate($input: CreateRepositoryInput!) { 187 createRepository(input: $input) { 188 repository { 189 id 190 name 191 owner { login } 192 url 193 } 194 } 195 } 196 `, variables, &response) 197 if err != nil { 198 return nil, err 199 } 200 201 return api.InitRepoHostname(&response.CreateRepository.Repository, hostname), nil 202} 203 204type ownerResponse struct { 205 NodeID string `json:"node_id"` 206 Type string `json:"type"` 207} 208 209func (r *ownerResponse) IsOrganization() bool { 210 return r.Type == "Organization" 211} 212 213func resolveOwner(client *api.Client, hostname, orgName string) (*ownerResponse, error) { 214 var response ownerResponse 215 err := client.REST(hostname, "GET", fmt.Sprintf("users/%s", orgName), nil, &response) 216 return &response, err 217} 218 219type teamResponse struct { 220 ID uint64 `json:"id"` 221 NodeID string `json:"node_id"` 222 Organization struct { 223 NodeID string `json:"node_id"` 224 } 225} 226 227func resolveOrganizationTeam(client *api.Client, hostname, orgName, teamSlug string) (*teamResponse, error) { 228 var response teamResponse 229 err := client.REST(hostname, "GET", fmt.Sprintf("orgs/%s/teams/%s", orgName, teamSlug), nil, &response) 230 return &response, err 231} 232 233// listGitIgnoreTemplates uses API v3 here because gitignore template isn't supported by GraphQL yet. 234func listGitIgnoreTemplates(httpClient *http.Client, hostname string) ([]string, error) { 235 var gitIgnoreTemplates []string 236 client := api.NewClientFromHTTP(httpClient) 237 err := client.REST(hostname, "GET", "gitignore/templates", nil, &gitIgnoreTemplates) 238 if err != nil { 239 return []string{}, err 240 } 241 return gitIgnoreTemplates, nil 242} 243 244// listLicenseTemplates uses API v3 here because license template isn't supported by GraphQL yet. 245func listLicenseTemplates(httpClient *http.Client, hostname string) ([]api.License, error) { 246 var licenseTemplates []api.License 247 client := api.NewClientFromHTTP(httpClient) 248 err := client.REST(hostname, "GET", "licenses", nil, &licenseTemplates) 249 if err != nil { 250 return nil, err 251 } 252 return licenseTemplates, nil 253} 254