From b82a0e6269aae43afad5f97e3dcd40b3b221fea2 Mon Sep 17 00:00:00 2001 From: asketeddy Date: Tue, 13 Oct 2026 18:50:28 +0900 Subject: [PATCH 1/3] refactor!: Pass repository creation by value --- example/newrepo/main.go | 2 +- github/doc.go | 9 +- github/github-accessors.go | 216 +++++++++++++++++++++++ github/github-accessors_test.go | 294 ++++++++++++++++++++++++++++++++ github/github-stringify_test.go | 36 ++++ github/repos.go | 56 +----- github/repos_test.go | 84 ++++++--- test/integration/github_test.go | 4 +- 8 files changed, 619 insertions(+), 82 deletions(-) diff --git a/example/newrepo/main.go b/example/newrepo/main.go index d4945b85c51..29d7e499527 100644 --- a/example/newrepo/main.go +++ b/example/newrepo/main.go @@ -41,7 +41,7 @@ func main() { log.Fatal(err) } - r := &github.Repository{Name: name, Private: private, Description: description, AutoInit: autoInit} + r := github.CreateRepositoryRequest{Name: *name, Private: private, Description: description, AutoInit: autoInit} repo, _, err := client.Repositories.Create(ctx, "", r) if err != nil { log.Fatal(err) diff --git a/github/doc.go b/github/doc.go index c9c1ab050db..ae20995dd1a 100644 --- a/github/doc.go +++ b/github/doc.go @@ -169,14 +169,15 @@ https://docs.github.com/rest/using-the-rest-api/best-practices-for-using-the-res # Creating and Updating Resources -All structs for GitHub resources use pointer values for all non-repeated fields. -This allows distinguishing between unset fields and those set to a zero-value. +When creating a repository, the required name is a value and optional fields +use pointers. This allows distinguishing between unset optional fields and +those set to a zero-value. Use the new builtin to easily create these pointers for string, bool, and int values. For example: // create a new private repository named "foo" - repo := &github.Repository{ - Name: new("foo"), + repo := github.CreateRepositoryRequest{ + Name: "foo", Private: new(true), } client.Repositories.Create(ctx, "", repo) diff --git a/github/github-accessors.go b/github/github-accessors.go index e192e57d774..fa599d86459 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -13670,6 +13670,222 @@ func (c *CreateReleaseRequest) GetTargetCommitish() string { return *c.TargetCommitish } +// GetAllowAutoMerge returns the AllowAutoMerge field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAllowAutoMerge() bool { + if c == nil || c.AllowAutoMerge == nil { + return false + } + return *c.AllowAutoMerge +} + +// GetAllowForking returns the AllowForking field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAllowForking() bool { + if c == nil || c.AllowForking == nil { + return false + } + return *c.AllowForking +} + +// GetAllowMergeCommit returns the AllowMergeCommit field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAllowMergeCommit() bool { + if c == nil || c.AllowMergeCommit == nil { + return false + } + return *c.AllowMergeCommit +} + +// GetAllowRebaseMerge returns the AllowRebaseMerge field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAllowRebaseMerge() bool { + if c == nil || c.AllowRebaseMerge == nil { + return false + } + return *c.AllowRebaseMerge +} + +// GetAllowSquashMerge returns the AllowSquashMerge field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAllowSquashMerge() bool { + if c == nil || c.AllowSquashMerge == nil { + return false + } + return *c.AllowSquashMerge +} + +// GetAllowUpdateBranch returns the AllowUpdateBranch field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAllowUpdateBranch() bool { + if c == nil || c.AllowUpdateBranch == nil { + return false + } + return *c.AllowUpdateBranch +} + +// GetAutoInit returns the AutoInit field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetAutoInit() bool { + if c == nil || c.AutoInit == nil { + return false + } + return *c.AutoInit +} + +// GetCustomProperties returns the CustomProperties map if it's non-nil, an empty map otherwise. +func (c *CreateRepositoryRequest) GetCustomProperties() map[string]any { + if c == nil || c.CustomProperties == nil { + return map[string]any{} + } + return c.CustomProperties +} + +// GetDeleteBranchOnMerge returns the DeleteBranchOnMerge field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetDeleteBranchOnMerge() bool { + if c == nil || c.DeleteBranchOnMerge == nil { + return false + } + return *c.DeleteBranchOnMerge +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetGitignoreTemplate returns the GitignoreTemplate field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetGitignoreTemplate() string { + if c == nil || c.GitignoreTemplate == nil { + return "" + } + return *c.GitignoreTemplate +} + +// GetHasDiscussions returns the HasDiscussions field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetHasDiscussions() bool { + if c == nil || c.HasDiscussions == nil { + return false + } + return *c.HasDiscussions +} + +// GetHasIssues returns the HasIssues field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetHasIssues() bool { + if c == nil || c.HasIssues == nil { + return false + } + return *c.HasIssues +} + +// GetHasProjects returns the HasProjects field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetHasProjects() bool { + if c == nil || c.HasProjects == nil { + return false + } + return *c.HasProjects +} + +// GetHasWiki returns the HasWiki field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetHasWiki() bool { + if c == nil || c.HasWiki == nil { + return false + } + return *c.HasWiki +} + +// GetHomepage returns the Homepage field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetHomepage() string { + if c == nil || c.Homepage == nil { + return "" + } + return *c.Homepage +} + +// GetIsTemplate returns the IsTemplate field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetIsTemplate() bool { + if c == nil || c.IsTemplate == nil { + return false + } + return *c.IsTemplate +} + +// GetLicenseTemplate returns the LicenseTemplate field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetLicenseTemplate() string { + if c == nil || c.LicenseTemplate == nil { + return "" + } + return *c.LicenseTemplate +} + +// GetMergeCommitMessage returns the MergeCommitMessage field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetMergeCommitMessage() string { + if c == nil || c.MergeCommitMessage == nil { + return "" + } + return *c.MergeCommitMessage +} + +// GetMergeCommitTitle returns the MergeCommitTitle field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetMergeCommitTitle() string { + if c == nil || c.MergeCommitTitle == nil { + return "" + } + return *c.MergeCommitTitle +} + +// GetName returns the Name field. +func (c *CreateRepositoryRequest) GetName() string { + if c == nil { + return "" + } + return c.Name +} + +// GetPrivate returns the Private field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetPrivate() bool { + if c == nil || c.Private == nil { + return false + } + return *c.Private +} + +// GetSquashMergeCommitMessage returns the SquashMergeCommitMessage field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetSquashMergeCommitMessage() string { + if c == nil || c.SquashMergeCommitMessage == nil { + return "" + } + return *c.SquashMergeCommitMessage +} + +// GetSquashMergeCommitTitle returns the SquashMergeCommitTitle field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetSquashMergeCommitTitle() string { + if c == nil || c.SquashMergeCommitTitle == nil { + return "" + } + return *c.SquashMergeCommitTitle +} + +// GetTeamID returns the TeamID field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetTeamID() int64 { + if c == nil || c.TeamID == nil { + return 0 + } + return *c.TeamID +} + +// GetUseSquashPRTitleAsDefault returns the UseSquashPRTitleAsDefault field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetUseSquashPRTitleAsDefault() bool { + if c == nil || c.UseSquashPRTitleAsDefault == nil { + return false + } + return *c.UseSquashPRTitleAsDefault +} + +// GetVisibility returns the Visibility field if it's non-nil, zero value otherwise. +func (c *CreateRepositoryRequest) GetVisibility() string { + if c == nil || c.Visibility == nil { + return "" + } + return *c.Visibility +} + // GetAllowsPublicRepositories returns the AllowsPublicRepositories field if it's non-nil, zero value otherwise. func (c *CreateRunnerGroupRequest) GetAllowsPublicRepositories() bool { if c == nil || c.AllowsPublicRepositories == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index e86fbcce1fc..be9fdd10073 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -17199,6 +17199,300 @@ func TestCreateReleaseRequest_GetTargetCommitish(tt *testing.T) { c.GetTargetCommitish() } +func TestCreateRepositoryRequest_GetAllowAutoMerge(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AllowAutoMerge: &zeroValue} + c.GetAllowAutoMerge() + c = &CreateRepositoryRequest{} + c.GetAllowAutoMerge() + c = nil + c.GetAllowAutoMerge() +} + +func TestCreateRepositoryRequest_GetAllowForking(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AllowForking: &zeroValue} + c.GetAllowForking() + c = &CreateRepositoryRequest{} + c.GetAllowForking() + c = nil + c.GetAllowForking() +} + +func TestCreateRepositoryRequest_GetAllowMergeCommit(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AllowMergeCommit: &zeroValue} + c.GetAllowMergeCommit() + c = &CreateRepositoryRequest{} + c.GetAllowMergeCommit() + c = nil + c.GetAllowMergeCommit() +} + +func TestCreateRepositoryRequest_GetAllowRebaseMerge(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AllowRebaseMerge: &zeroValue} + c.GetAllowRebaseMerge() + c = &CreateRepositoryRequest{} + c.GetAllowRebaseMerge() + c = nil + c.GetAllowRebaseMerge() +} + +func TestCreateRepositoryRequest_GetAllowSquashMerge(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AllowSquashMerge: &zeroValue} + c.GetAllowSquashMerge() + c = &CreateRepositoryRequest{} + c.GetAllowSquashMerge() + c = nil + c.GetAllowSquashMerge() +} + +func TestCreateRepositoryRequest_GetAllowUpdateBranch(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AllowUpdateBranch: &zeroValue} + c.GetAllowUpdateBranch() + c = &CreateRepositoryRequest{} + c.GetAllowUpdateBranch() + c = nil + c.GetAllowUpdateBranch() +} + +func TestCreateRepositoryRequest_GetAutoInit(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{AutoInit: &zeroValue} + c.GetAutoInit() + c = &CreateRepositoryRequest{} + c.GetAutoInit() + c = nil + c.GetAutoInit() +} + +func TestCreateRepositoryRequest_GetCustomProperties(tt *testing.T) { + tt.Parallel() + zeroValue := map[string]any{} + c := &CreateRepositoryRequest{CustomProperties: zeroValue} + c.GetCustomProperties() + c = &CreateRepositoryRequest{} + c.GetCustomProperties() + c = nil + c.GetCustomProperties() +} + +func TestCreateRepositoryRequest_GetDeleteBranchOnMerge(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{DeleteBranchOnMerge: &zeroValue} + c.GetDeleteBranchOnMerge() + c = &CreateRepositoryRequest{} + c.GetDeleteBranchOnMerge() + c = nil + c.GetDeleteBranchOnMerge() +} + +func TestCreateRepositoryRequest_GetDescription(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{Description: &zeroValue} + c.GetDescription() + c = &CreateRepositoryRequest{} + c.GetDescription() + c = nil + c.GetDescription() +} + +func TestCreateRepositoryRequest_GetGitignoreTemplate(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{GitignoreTemplate: &zeroValue} + c.GetGitignoreTemplate() + c = &CreateRepositoryRequest{} + c.GetGitignoreTemplate() + c = nil + c.GetGitignoreTemplate() +} + +func TestCreateRepositoryRequest_GetHasDiscussions(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{HasDiscussions: &zeroValue} + c.GetHasDiscussions() + c = &CreateRepositoryRequest{} + c.GetHasDiscussions() + c = nil + c.GetHasDiscussions() +} + +func TestCreateRepositoryRequest_GetHasIssues(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{HasIssues: &zeroValue} + c.GetHasIssues() + c = &CreateRepositoryRequest{} + c.GetHasIssues() + c = nil + c.GetHasIssues() +} + +func TestCreateRepositoryRequest_GetHasProjects(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{HasProjects: &zeroValue} + c.GetHasProjects() + c = &CreateRepositoryRequest{} + c.GetHasProjects() + c = nil + c.GetHasProjects() +} + +func TestCreateRepositoryRequest_GetHasWiki(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{HasWiki: &zeroValue} + c.GetHasWiki() + c = &CreateRepositoryRequest{} + c.GetHasWiki() + c = nil + c.GetHasWiki() +} + +func TestCreateRepositoryRequest_GetHomepage(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{Homepage: &zeroValue} + c.GetHomepage() + c = &CreateRepositoryRequest{} + c.GetHomepage() + c = nil + c.GetHomepage() +} + +func TestCreateRepositoryRequest_GetIsTemplate(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{IsTemplate: &zeroValue} + c.GetIsTemplate() + c = &CreateRepositoryRequest{} + c.GetIsTemplate() + c = nil + c.GetIsTemplate() +} + +func TestCreateRepositoryRequest_GetLicenseTemplate(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{LicenseTemplate: &zeroValue} + c.GetLicenseTemplate() + c = &CreateRepositoryRequest{} + c.GetLicenseTemplate() + c = nil + c.GetLicenseTemplate() +} + +func TestCreateRepositoryRequest_GetMergeCommitMessage(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{MergeCommitMessage: &zeroValue} + c.GetMergeCommitMessage() + c = &CreateRepositoryRequest{} + c.GetMergeCommitMessage() + c = nil + c.GetMergeCommitMessage() +} + +func TestCreateRepositoryRequest_GetMergeCommitTitle(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{MergeCommitTitle: &zeroValue} + c.GetMergeCommitTitle() + c = &CreateRepositoryRequest{} + c.GetMergeCommitTitle() + c = nil + c.GetMergeCommitTitle() +} + +func TestCreateRepositoryRequest_GetName(tt *testing.T) { + tt.Parallel() + c := &CreateRepositoryRequest{} + c.GetName() + c = nil + c.GetName() +} + +func TestCreateRepositoryRequest_GetPrivate(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{Private: &zeroValue} + c.GetPrivate() + c = &CreateRepositoryRequest{} + c.GetPrivate() + c = nil + c.GetPrivate() +} + +func TestCreateRepositoryRequest_GetSquashMergeCommitMessage(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{SquashMergeCommitMessage: &zeroValue} + c.GetSquashMergeCommitMessage() + c = &CreateRepositoryRequest{} + c.GetSquashMergeCommitMessage() + c = nil + c.GetSquashMergeCommitMessage() +} + +func TestCreateRepositoryRequest_GetSquashMergeCommitTitle(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{SquashMergeCommitTitle: &zeroValue} + c.GetSquashMergeCommitTitle() + c = &CreateRepositoryRequest{} + c.GetSquashMergeCommitTitle() + c = nil + c.GetSquashMergeCommitTitle() +} + +func TestCreateRepositoryRequest_GetTeamID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + c := &CreateRepositoryRequest{TeamID: &zeroValue} + c.GetTeamID() + c = &CreateRepositoryRequest{} + c.GetTeamID() + c = nil + c.GetTeamID() +} + +func TestCreateRepositoryRequest_GetUseSquashPRTitleAsDefault(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateRepositoryRequest{UseSquashPRTitleAsDefault: &zeroValue} + c.GetUseSquashPRTitleAsDefault() + c = &CreateRepositoryRequest{} + c.GetUseSquashPRTitleAsDefault() + c = nil + c.GetUseSquashPRTitleAsDefault() +} + +func TestCreateRepositoryRequest_GetVisibility(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateRepositoryRequest{Visibility: &zeroValue} + c.GetVisibility() + c = &CreateRepositoryRequest{} + c.GetVisibility() + c = nil + c.GetVisibility() +} + func TestCreateRunnerGroupRequest_GetAllowsPublicRepositories(tt *testing.T) { tt.Parallel() var zeroValue bool diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index ec3811216d1..fdcbc0e33ba 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -508,6 +508,42 @@ func TestContributorStats_String(t *testing.T) { } } +func TestCreateRepositoryRequest_String(t *testing.T) { + t.Parallel() + v := CreateRepositoryRequest{ + Name: "", + Description: new(""), + Homepage: new(""), + Private: new(false), + Visibility: new(""), + HasIssues: new(false), + HasProjects: new(false), + HasWiki: new(false), + HasDiscussions: new(false), + IsTemplate: new(false), + TeamID: new(int64(0)), + AutoInit: new(false), + GitignoreTemplate: new(""), + LicenseTemplate: new(""), + AllowSquashMerge: new(false), + AllowMergeCommit: new(false), + AllowRebaseMerge: new(false), + AllowUpdateBranch: new(false), + AllowAutoMerge: new(false), + AllowForking: new(false), + DeleteBranchOnMerge: new(false), + UseSquashPRTitleAsDefault: new(false), + SquashMergeCommitTitle: new(""), + SquashMergeCommitMessage: new(""), + MergeCommitTitle: new(""), + MergeCommitMessage: new(""), + } + want := `github.CreateRepositoryRequest{Name:"", Description:"", Homepage:"", Private:false, Visibility:"", HasIssues:false, HasProjects:false, HasWiki:false, HasDiscussions:false, IsTemplate:false, TeamID:0, AutoInit:false, GitignoreTemplate:"", LicenseTemplate:"", AllowSquashMerge:false, AllowMergeCommit:false, AllowRebaseMerge:false, AllowUpdateBranch:false, AllowAutoMerge:false, AllowForking:false, DeleteBranchOnMerge:false, UseSquashPRTitleAsDefault:false, SquashMergeCommitTitle:"", SquashMergeCommitMessage:"", MergeCommitTitle:"", MergeCommitMessage:""}` + if got := v.String(); got != want { + t.Errorf("CreateRepositoryRequest.String = %v, want %v", got, want) + } +} + func TestCreateTeamRequest_String(t *testing.T) { t.Parallel() v := CreateTeamRequest{ diff --git a/github/repos.go b/github/repos.go index 588b0c66fbe..e7671b2d20a 100644 --- a/github/repos.go +++ b/github/repos.go @@ -502,14 +502,9 @@ func (s *RepositoriesService) ListAll(ctx context.Context, opts *RepositoryListA return repos, resp, nil } -// createRepoRequest is a subset of Repository and is used internally -// by Create to pass only the known fields for the endpoint. -// -// See https://github.com/google/go-github/issues/1014 for more -// information. -type createRepoRequest struct { - // Name is required when creating a repo. - Name *string `json:"name,omitempty"` +// CreateRepositoryRequest specifies the repository to create. +type CreateRepositoryRequest struct { + Name string `json:"name"` Description *string `json:"description,omitempty"` Homepage *string `json:"homepage,omitempty"` @@ -521,7 +516,7 @@ type createRepoRequest struct { HasDiscussions *bool `json:"has_discussions,omitempty"` IsTemplate *bool `json:"is_template,omitempty"` - // Creating an organization repository. Required for non-owners. + // TeamID is used when creating an organization repository. TeamID *int64 `json:"team_id,omitempty"` AutoInit *bool `json:"auto_init,omitempty"` @@ -542,13 +537,12 @@ type createRepoRequest struct { CustomProperties map[string]any `json:"custom_properties,omitempty"` } +func (r CreateRepositoryRequest) String() string { return Stringify(r) } + // Create a new repository. If an organization is specified, the new // repository will be created under that org. If the empty string is // specified, it will be created for the authenticated user. // -// Note that only a subset of the repo fields are used and repo must -// not be nil. -// // Also note that this method will return the response without actually // waiting for GitHub to finish creating the repository and letting the // changes propagate throughout its servers. You may set up a loop with @@ -560,11 +554,7 @@ type createRepoRequest struct { // //meta:operation POST /orgs/{org}/repos //meta:operation POST /user/repos -func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repository) (*Repository, *Response, error) { - if repo == nil { - return nil, nil, errors.New("repository must be provided") - } - +func (s *RepositoriesService) Create(ctx context.Context, org string, body CreateRepositoryRequest) (*Repository, *Response, error) { var u string if org != "" { u = fmt.Sprintf("orgs/%v/repos", org) @@ -572,37 +562,7 @@ func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repo u = "user/repos" } - repoReq := &createRepoRequest{ - Name: repo.Name, - Description: repo.Description, - Homepage: repo.Homepage, - Private: repo.Private, - Visibility: repo.Visibility, - HasIssues: repo.HasIssues, - HasProjects: repo.HasProjects, - HasWiki: repo.HasWiki, - HasDiscussions: repo.HasDiscussions, - IsTemplate: repo.IsTemplate, - TeamID: repo.TeamID, - AutoInit: repo.AutoInit, - GitignoreTemplate: repo.GitignoreTemplate, - LicenseTemplate: repo.LicenseTemplate, - AllowSquashMerge: repo.AllowSquashMerge, - AllowMergeCommit: repo.AllowMergeCommit, - AllowRebaseMerge: repo.AllowRebaseMerge, - AllowUpdateBranch: repo.AllowUpdateBranch, - AllowAutoMerge: repo.AllowAutoMerge, - AllowForking: repo.AllowForking, - DeleteBranchOnMerge: repo.DeleteBranchOnMerge, - UseSquashPRTitleAsDefault: repo.UseSquashPRTitleAsDefault, - SquashMergeCommitTitle: repo.SquashMergeCommitTitle, - SquashMergeCommitMessage: repo.SquashMergeCommitMessage, - MergeCommitTitle: repo.MergeCommitTitle, - MergeCommitMessage: repo.MergeCommitMessage, - CustomProperties: repo.CustomProperties, - } - - req, err := s.client.NewRequest(ctx, "POST", u, repoReq) + req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } diff --git a/github/repos_test.go b/github/repos_test.go index 7d66533bfa1..b5a4e2676a6 100644 --- a/github/repos_test.go +++ b/github/repos_test.go @@ -221,17 +221,13 @@ func TestRepositoriesService_Create_user(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - input := &Repository{ - Name: new("n"), - Archived: new(true), // not passed along. - } + input := CreateRepositoryRequest{Name: "n"} wantAcceptHeaders := []string{mediaTypeRepositoryTemplatePreview, mediaTypeRepositoryVisibilityPreview} mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", ")) - want := &createRepoRequest{Name: new("n")} - testJSONBody(t, r, want) + testJSONBody(t, r, input) fmt.Fprint(w, `{"id":1}`) }) @@ -248,10 +244,6 @@ func TestRepositoriesService_Create_user(t *testing.T) { } const methodName = "Create" - testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Repositories.Create(ctx, "", nil) - return err - }) testBadOptions(t, methodName, func() (err error) { _, _, err = client.Repositories.Create(ctx, "\n", input) return err @@ -270,17 +262,13 @@ func TestRepositoriesService_Create_org(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - input := &Repository{ - Name: new("n"), - Archived: new(true), // not passed along. - } + input := CreateRepositoryRequest{Name: "n"} wantAcceptHeaders := []string{mediaTypeRepositoryTemplatePreview, mediaTypeRepositoryVisibilityPreview} mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", ")) - want := &createRepoRequest{Name: new("n")} - testJSONBody(t, r, want) + testJSONBody(t, r, input) fmt.Fprint(w, `{"id":1}`) }) @@ -300,8 +288,8 @@ func TestRepositoriesService_Create_withCustomProperties(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - input := &Repository{ - Name: new("n"), + input := CreateRepositoryRequest{ + Name: "n", CustomProperties: map[string]any{ "environment": "production", "team": "backend", @@ -313,15 +301,7 @@ func TestRepositoriesService_Create_withCustomProperties(t *testing.T) { mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", ")) - want := &createRepoRequest{ - Name: new("n"), - CustomProperties: map[string]any{ - "environment": "production", - "team": "backend", - "priority": float64(1), // JSON unmarshals numbers as float64 - }, - } - testJSONBody(t, r, want) + testJSONBody(t, r, input) fmt.Fprint(w, `{"id":1}`) }) @@ -337,6 +317,56 @@ func TestRepositoriesService_Create_withCustomProperties(t *testing.T) { } } +func TestRepositoriesService_Create_jsonFields(t *testing.T) { + t.Parallel() + + for _, endpoint := range []struct { + name string + org string + path string + }{ + {name: "user", path: "/user/repos"}, + {name: "org", org: "o", path: "/orgs/o/repos"}, + } { + t.Run(endpoint.name, func(t *testing.T) { + t.Parallel() + for _, tt := range []struct { + name string + input CreateRepositoryRequest + want string + }{ + { + name: "unset optional fields", + want: `{"name":""}`, + }, + { + name: "explicit optional zero values", + input: CreateRepositoryRequest{ + Description: new(""), + Private: new(false), + HasIssues: new(false), + }, + want: `{"name":"","description":"","private":false,"has_issues":false}`, + }, + } { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc(endpoint.path, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testJSONBodyRaw(t, r, tt.want) + fmt.Fprint(w, `{"id":1}`) + }) + + if _, _, err := client.Repositories.Create(t.Context(), endpoint.org, tt.input); err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + }) + } + }) + } +} + func TestRepositoriesService_CreateFromTemplate(t *testing.T) { t.Parallel() client, mux, _ := setup(t) diff --git a/test/integration/github_test.go b/test/integration/github_test.go index 2d45f93d7ac..3763708259c 100644 --- a/test/integration/github_test.go +++ b/test/integration/github_test.go @@ -79,8 +79,8 @@ func createRandomTestRepository(t *testing.T, owner string, autoinit bool) *gith repo, _, err := client.Repositories.Create( t.Context(), owner, - &github.Repository{ - Name: &repoName, + github.CreateRepositoryRequest{ + Name: repoName, AutoInit: &autoinit, }, ) From 496d4a0d63263e1ecc56214aeacb63d0472763e4 Mon Sep 17 00:00:00 2001 From: asketeddy Date: Wed, 14 Oct 2026 07:39:24 +0900 Subject: [PATCH 2/3] Fix repository creation review findings --- README.md | 9 +++++---- tools/schemafields/exceptions.txt | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 15bf3c64ae7..765a04164ea 100644 --- a/README.md +++ b/README.md @@ -341,15 +341,16 @@ recommended when making requests using short-lived credentials such as a ### Creating and Updating Resources ### -All structs for GitHub resources use pointer values for all non-repeated fields. -This allows distinguishing between unset fields and those set to a zero-value. +Many resource and request structs use pointers for optional scalar fields. This +allows distinguishing between unset fields and those set to a zero-value. +Required fields such as `CreateRepositoryRequest.Name` use value types. Use the `new` builtin to easily create these pointers for string, bool, and int values. For example: ```go // create a new private repository named "foo" -repo := &github.Repository{ - Name: new("foo"), +repo := github.CreateRepositoryRequest{ + Name: "foo", Private: new(true), } client.Repositories.Create(ctx, "", repo) diff --git a/tools/schemafields/exceptions.txt b/tools/schemafields/exceptions.txt index 89d67ee518a..14f74cafa8b 100644 --- a/tools/schemafields/exceptions.txt +++ b/tools/schemafields/exceptions.txt @@ -11,8 +11,14 @@ # Run "script/check-schema-fields.sh -fix" to repair the error-severity findings # that no line here grandfathers. It drops the lines that no finding needs any # more, so the file can only shrink. +# +# CreateRepositoryRequest.AllowForking and CreateRepositoryRequest.AllowUpdateBranch +# preserve fields previously available through the Repository request body, +# although they are absent from the pinned repository creation request schemas. CodeSecurityConfiguration.Description CreateCheckSuiteOptions.HeadBranch +CreateRepositoryRequest.AllowForking +CreateRepositoryRequest.AllowUpdateBranch CreateTeamRequest.LDAPDN IDPGroupList.Groups OIDCSubjectClaimCustomTemplate.UseDefault From 93b281403ec413f3df9ecadda7e419f2bcda03ea Mon Sep 17 00:00:00 2001 From: asketeddy Date: Wed, 14 Oct 2026 07:58:50 +0900 Subject: [PATCH 3/3] Sync resource creation documentation --- github/doc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/github/doc.go b/github/doc.go index ae20995dd1a..a497ec8eb50 100644 --- a/github/doc.go +++ b/github/doc.go @@ -169,9 +169,9 @@ https://docs.github.com/rest/using-the-rest-api/best-practices-for-using-the-res # Creating and Updating Resources -When creating a repository, the required name is a value and optional fields -use pointers. This allows distinguishing between unset optional fields and -those set to a zero-value. +Many resource and request structs use pointers for optional scalar fields. This +allows distinguishing between unset fields and those set to a zero-value. +Required fields such as [CreateRepositoryRequest.Name] use value types. Use the new builtin to easily create these pointers for string, bool, and int values. For example: