Repository: google/go-github
Pull request #2986 (2023-11-04 02:34:29)

Implement Custom Properties
Resolves https://github.com/google/go-github/issues/2965

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/2986?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2986](https://app.codecov.io/gh/google/go-github/pull/2986?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (0b1aa7b) into [master](https://app.codecov.io/gh/google/go-github/commit/a5b1cea2fe8d05dc2a9e0190abd6f17d519b2696?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (a5b1cea) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master    #2986      +/-   ##
==========================================
+ Coverage   98.11%   98.13%   +0.01%     
==========================================
  Files         149      150       +1     
  Lines       12808    12906      +98     
==========================================
+ Hits        12567    12665      +98     
  Misses        166      166              
  Partials       75       75              
```


| [Files](https://app.codecov.io/gh/google/go-github/pull/2986?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/orgs\_properties.go](https://app.codecov.io/gh/google/go-github/pull/2986?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL29yZ3NfcHJvcGVydGllcy5nbw==) | `100.00% <100.00%> (ø)` | |



Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index a489d66c..536ce5a8 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -4990,6 +4990,46 @@ func (c *CredentialAuthorization) GetTokenLastEight() string {
 	return *c.TokenLastEight
 }
 
+// GetDefaultValue returns the DefaultValue field if it's non-nil, zero value otherwise.
+func (c *CustomProperty) GetDefaultValue() string {
+	if c == nil || c.DefaultValue == nil {
+		return ""
+	}
+	return *c.DefaultValue
+}
+
+// GetDescription returns the Description field if it's non-nil, zero value otherwise.
+func (c *CustomProperty) GetDescription() string {
+	if c == nil || c.Description == nil {
+		return ""
+	}
+	return *c.Description
+}
+
+// GetPropertyName returns the PropertyName field if it's non-nil, zero value otherwise.
+func (c *CustomProperty) GetPropertyName() string {
+	if c == nil || c.PropertyName == nil {
+		return ""
+	}
+	return *c.PropertyName
+}
+
+// GetRequired returns the Required field if it's non-nil, zero value otherwise.
+func (c *CustomProperty) GetRequired() bool {
+	if c == nil || c.Required == nil {
+		return false
+	}
+	return *c.Required
+}
+
+// GetValue returns the Value field if it's non-nil, zero value otherwise.
+func (c *CustomPropertyValue) GetValue() string {
+	if c == nil || c.Value == nil {
+		return ""
+	}
+	return *c.Value
+}
+
 // GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise.
 func (c *CustomRepoRoles) GetBaseRole() string {
 	if c == nil || c.BaseRole == nil {
@@ -20358,6 +20398,30 @@ func (r *RuleRequiredStatusChecks) GetIntegrationID() int64 {
 	return *r.IntegrationID
 }
 
+// GetRef returns the Ref field if it's non-nil, zero value otherwise.
+func (r *RuleRequiredWorkflow) GetRef() string {
+	if r == nil || r.Ref == nil {
+		return ""
+	}
+	return *r.Ref
+}
+
+// GetRepositoryID returns the RepositoryID field if it's non-nil, zero value otherwise.
+func (r *RuleRequiredWorkflow) GetRepositoryID() int64 {
+	if r == nil || r.RepositoryID == nil {
+		return 0
+	}
+	return *r.RepositoryID
+}
+
+// GetSha returns the Sha field if it's non-nil, zero value otherwise.
+func (r *RuleRequiredWorkflow) GetSha() string {
+	if r == nil || r.Sha == nil {
+		return ""
+	}
+	return *r.Sha
+}
+
 // GetConditions returns the Conditions field.
 func (r *Ruleset) GetConditions() *RulesetConditions {
 	if r == nil {
diff --git a/github/orgs_properties.go b/github/orgs_properties.go
new file mode 100644
index 00000000..1daac811
--- /dev/null
+++ b/github/orgs_properties.go
@@ -0,0 +1,198 @@
+// Copyright 2023 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"fmt"
+)
+
+// CustomProperty represents an organization custom property object.
+type CustomProperty struct {
+	// PropertyName is required for most endpoints except when calling CreateOrUpdateCustomProperty;
+	// where this is sent in the path and thus can be omitted.
+	PropertyName *string `json:"property_name,omitempty"`
+	// Possible values for ValueType are: string, single_select
+	ValueType     string   `json:"value_type"`
+	Required      *bool    `json:"required,omitempty"`
+	DefaultValue  *string  `json:"default_value,omitempty"`
+	Description   *string  `json:"description,omitempty"`
+	AllowedValues []string `json:"allowed_values,omitempty"`
+}
+
+// RepoCustomPropertyValue represents a repository custom property value.
+type RepoCustomPropertyValue struct {
+	RepositoryID       int64                  `json:"repository_id"`
+	RepositoryName     string                 `json:"repository_name"`
+	RepositoryFullName string                 `json:"repository_full_name"`
+	Properties         []*CustomPropertyValue `json:"properties"`
+}
+
+// CustomPropertyValue represents a custom property value.
+type CustomPropertyValue struct {
+	PropertyName string  `json:"property_name"`
+	Value        *string `json:"value,omitempty"`
+}
+
+// GetAllCustomProperties gets all custom properties that are defined for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#get-all-custom-properties-for-an-organization
+//
+//meta:operation GET /orgs/{org}/properties/schema
+func (s *OrganizationsService) GetAllCustomProperties(ctx context.Context, org string) ([]*CustomProperty, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/schema", org)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var customProperties []*CustomProperty
+	resp, err := s.client.Do(ctx, req, &customProperties)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return customProperties, resp, nil
+}
+
+// CreateOrUpdateCustomProperties creates new or updates existing custom properties that are defined for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#create-or-update-custom-properties-for-an-organization
+//
+//meta:operation PATCH /orgs/{org}/properties/schema
+func (s *OrganizationsService) CreateOrUpdateCustomProperties(ctx context.Context, org string, properties []*CustomProperty) ([]*CustomProperty, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/schema", org)
+
+	params := struct {
+		Properties []*CustomProperty `json:"properties"`
+	}{
+		Properties: properties,
+	}
+
+	req, err := s.client.NewRequest("PATCH", u, params)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var customProperties []*CustomProperty
+	resp, err := s.client.Do(ctx, req, &customProperties)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return customProperties, resp, nil
+}
+
+// GetCustomProperty gets a custom property that is defined for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#get-a-custom-property-for-an-organization
+//
+//meta:operation GET /orgs/{org}/properties/schema/{custom_property_name}
+func (s *OrganizationsService) GetCustomProperty(ctx context.Context, org, name string) (*CustomProperty, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/schema/%v", org, name)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var customProperty *CustomProperty
+	resp, err := s.client.Do(ctx, req, &customProperty)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return customProperty, resp, nil
+}
+
+// CreateOrUpdateCustomProperty creates a new or updates an existing custom property that is defined for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#create-or-update-a-custom-property-for-an-organization
+//
+//meta:operation PUT /orgs/{org}/properties/schema/{custom_property_name}
+func (s *OrganizationsService) CreateOrUpdateCustomProperty(ctx context.Context, org, customPropertyName string, property *CustomProperty) (*CustomProperty, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/schema/%v", org, customPropertyName)
+
+	req, err := s.client.NewRequest("PUT", u, property)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var customProperty *CustomProperty
+	resp, err := s.client.Do(ctx, req, &customProperty)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return customProperty, resp, nil
+}
+
+// RemoveCustomProperty removes a custom property that is defined for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#remove-a-custom-property-for-an-organization
+//
+//meta:operation DELETE /orgs/{org}/properties/schema/{custom_property_name}
+func (s *OrganizationsService) RemoveCustomProperty(ctx context.Context, org, customPropertyName string) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/schema/%v", org, customPropertyName)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
+
+// ListCustomPropertyValues lists all custom property values for repositories in the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#list-custom-property-values-for-organization-repositories
+//
+//meta:operation GET /orgs/{org}/properties/values
+func (s *OrganizationsService) ListCustomPropertyValues(ctx context.Context, org string, opts *ListOptions) ([]*RepoCustomPropertyValue, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/values", org)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var repoCustomPropertyValues []*RepoCustomPropertyValue
+	resp, err := s.client.Do(ctx, req, &repoCustomPropertyValues)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return repoCustomPropertyValues, resp, nil
+}
+
+// CreateOrUpdateRepoCustomPropertyValues creates new or updates existing custom property values across multiple repositories for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/properties#create-or-update-custom-property-values-for-organization-repositories
+//
+//meta:operation PATCH /orgs/{org}/properties/values
+func (s *OrganizationsService) CreateOrUpdateRepoCustomPropertyValues(ctx context.Context, org string, repoNames []string, properties []*CustomProperty) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/properties/values", org)
+
+	params := struct {
+		RepositoryNames []string          `json:"repository_names"`
+		Properties      []*CustomProperty `json:"properties"`
+	}{
+		RepositoryNames: repoNames,
+		Properties:      properties,
+	}
+
+	req, err := s.client.NewRequest("PATCH", u, params)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/github/repos.go b/github/repos.go
index b16cd3b8..5fcf219b 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -173,33 +173,19 @@ type BranchListOptions struct {
 // RepositoryListOptions specifies the optional parameters to the
 // RepositoriesService.List method.
 type RepositoryListOptions struct {
-	// Visibility of repositories to list. Can be one of all, public, or private.
-	// Default: all
+	// See RepositoryListByAuthenticatedUserOptions.Visibility
 	Visibility string `url:"visibility,omitempty"`
 
-	// List repos of given affiliation[s].
-	// Comma-separated list of values. Can include:
-	// * owner: Repositories that are owned by the authenticated user.
-	// * collaborator: Repositories that the user has been added to as a
-	//   collaborator.
-	// * organization_member: Repositories that the user has access to through
-	//   being a member of an organization. This includes every repository on
-	//   every team that the user is on.
-	// Default: owner,collaborator,organization_member
+	// See RepositoryListByAuthenticatedUserOptions.Affiliation
 	Affiliation string `url:"affiliation,omitempty"`
 
-	// Type of repositories to list.
-	// Can be one of all, owner, public, private, member. Default: all
-	// Will cause a 422 error if used in the same request as visibility or
-	// affiliation.
+	// See RepositoryListByUserOptions.Type or RepositoryListByAuthenticatedUserOptions.Type
 	Type string `url:"type,omitempty"`
 
-	// How to sort the repository list. Can be one of created, updated, pushed,
-	// full_name. Default: full_name
+	// See RepositoryListByUserOptions.Sort or RepositoryListByAuthenticatedUserOptions.Sort
 	Sort string `url:"sort,omitempty"`
 
-	// Direction in which to sort repositories. Can be one of asc or desc.
-	// Default: when using full_name: asc; otherwise desc
+	// See RepositoryListByUserOptions.Direction or RepositoryListByAuthenticatedUserOptions.Direction
 	Direction string `url:"direction,omitempty"`
 
 	ListOptions
@@ -262,8 +248,10 @@ func (d DependabotSecurityUpdates) String() string {
 	return Stringify(d)
 }
 
-// List the repositories for a user. Passing the empty string will list
-// repositories for the authenticated user.
+// List calls either RepositoriesService.ListByUser or RepositoriesService.ListByAuthenticatedUser
+// depending on whether user is empty.
+//
+// Deprecated: Use RepositoriesService.ListByUser or RepositoriesService.ListByAuthenticatedUser instead.
 //
 // GitHub API docs: https://docs.github.com/rest/repos/repos#list-repositories-for-a-user
 // GitHub API docs: https://docs.github.com/rest/repos/repos#list-repositories-for-the-authenticated-user
@@ -271,12 +259,55 @@ func (d DependabotSecurityUpdates) String() string {
 //meta:operation GET /user/repos
 //meta:operation GET /users/{username}/repos
 func (s *RepositoriesService) List(ctx context.Context, user string, opts *RepositoryListOptions) ([]*Repository, *Response, error) {
-	var u string
-	if user != "" {
-		u = fmt.Sprintf("users/%v/repos", user)
-	} else {
-		u = "user/repos"
+	if opts == nil {
+		opts = &RepositoryListOptions{}
 	}
+	if user != "" {
+		return s.ListByUser(ctx, user, &RepositoryListByUserOptions{
+			Type:        opts.Type,
+			Sort:        opts.Sort,
+			Direction:   opts.Direction,
+			ListOptions: opts.ListOptions,
+		})
+	}
+	return s.ListByAuthenticatedUser(ctx, &RepositoryListByAuthenticatedUserOptions{
+		Visibility:  opts.Visibility,
+		Affiliation: opts.Affiliation,
+		Type:        opts.Type,
+		Sort:        opts.Sort,
+		Direction:   opts.Direction,
+		ListOptions: opts.ListOptions,
+	})
+}
+
+// RepositoryListByUserOptions specifies the optional parameters to the
+// RepositoriesService.ListByUser method.
+type RepositoryListByUserOptions struct {
+	// Limit results to repositories of the specified type.
+	// Default: owner
+	// Can be one of: all, owner, member
+	Type string `url:"type,omitempty"`
+
+	// The property to sort the results by.
+	// Default: full_name
+	// Can be one of: created, updated, pushed, full_name
+	Sort string `url:"sort,omitempty"`
+
+	// The order to sort by.
+	// Default: asc when using full_name, otherwise desc.
+	// Can be one of: asc, desc
+	Direction string `url:"direction,omitempty"`
+
+	ListOptions
+}
+
+// ListByUser lists public repositories for the specified user.
+//
+// GitHub API docs: https://docs.github.com/rest/repos/repos#list-repositories-for-a-user
+//
+//meta:operation GET /users/{username}/repos
+func (s *RepositoriesService) ListByUser(ctx context.Context, user string, opts *RepositoryListByUserOptions) ([]*Repository, *Response, error) {
+	u := fmt.Sprintf("users/%v/repos", user)
 	u, err := addOptions(u, opts)
 	if err != nil {
 		return nil, nil, err
@@ -287,9 +318,68 @@ func (s *RepositoriesService) List(ctx context.Context, user string, opts *Repos
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept headers when APIs fully launch.
-	acceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
-	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
+	var repos []*Repository
+	resp, err := s.client.Do(ctx, req, &repos)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return repos, resp, nil
+}
+
+// RepositoryListByAuthenticatedUserOptions specifies the optional parameters to the
+// RepositoriesService.ListByAuthenticatedUser method.
+type RepositoryListByAuthenticatedUserOptions struct {
+	// Limit results to repositories with the specified visibility.
+	// Default: all
+	// Can be one of: all, public, private
+	Visibility string `url:"visibility,omitempty"`
+
+	// List repos of given affiliation[s].
+	// Comma-separated list of values. Can include:
+	// * owner: Repositories that are owned by the authenticated user.
+	// * collaborator: Repositories that the user has been added to as a
+	//   collaborator.
+	// * organization_member: Repositories that the user has access to through
+	//   being a member of an organization. This includes every repository on
+	//   every team that the user is on.
+	// Default: owner,collaborator,organization_member
+	Affiliation string `url:"affiliation,omitempty"`
+
+	// Limit results to repositories of the specified type. Will cause a 422 error if
+	// used in the same request as visibility or affiliation.
+	// Default: all
+	// Can be one of: all, owner, public, private, member
+	Type string `url:"type,omitempty"`
+
+	// The property to sort the results by.
+	// Default: full_name
+	// Can be one of: created, updated, pushed, full_name
+	Sort string `url:"sort,omitempty"`
+
+	// Direction in which to sort repositories. Can be one of asc or desc.
+	// Default: when using full_name: asc; otherwise desc
+	Direction string `url:"direction,omitempty"`
+
+	ListOptions
+}
+
+// ListByAuthenticatedUser lists repositories for the authenticated user.
+//
+// GitHub API docs: https://docs.github.com/rest/repos/repos#list-repositories-for-the-authenticated-user
+//
+//meta:operation GET /user/repos
+func (s *RepositoriesService) ListByAuthenticatedUser(ctx context.Context, opts *RepositoryListByAuthenticatedUserOptions) ([]*Repository, *Response, error) {
+	u := "user/repos"
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	var repos []*Repository
 	resp, err := s.client.Do(ctx, req, &repos)
diff --git a/github/repos_rules.go b/github/repos_rules.go
index b47b3703..479806c2 100644
--- a/github/repos_rules.go
+++ b/github/repos_rules.go
@@ -97,6 +97,19 @@ type RequiredStatusChecksRuleParameters struct {
 	StrictRequiredStatusChecksPolicy bool                       `json:"strict_required_status_checks_policy"`
 }
 
+// RuleRequiredWorkflow represents the Workflow for the RequireWorkflowsRuleParameters object.
+type RuleRequiredWorkflow struct {
+	Path         string  `json:"path"`
+	Ref          *string `json:"ref,omitempty"`
+	RepositoryID *int64  `json:"repository_id,omitempty"`
+	Sha          *string `json:"sha,omitempty"`
+}
+
+// RequiredWorkflowsRuleParameters represents the workflows rule parameters.
+type RequiredWorkflowsRuleParameters struct {
+	RequiredWorkflows []*RuleRequiredWorkflow `json:"workflows"`
+}
+
 // RepositoryRule represents a GitHub Rule.
 type RepositoryRule struct {
 	Type       string           `json:"type"`
@@ -171,6 +184,16 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
 		bytes, _ := json.Marshal(params)
 		rawParams := json.RawMessage(bytes)
 
+		r.Parameters = &rawParams
+	case "workflows":
+		params := RequiredWorkflowsRuleParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
+
 		r.Parameters = &rawParams
 	default:
 		r.Type = ""
@@ -329,6 +352,18 @@ func NewTagNamePatternRule(params *RulePatternParameters) (rule *RepositoryRule)
 	}
 }
 
+// NewRequiredWorkflowsRule creates a rule to require which status checks must pass before branches can be merged into a branch rule.
+func NewRequiredWorkflowsRule(params *RequiredWorkflowsRuleParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "workflows",
+		Parameters: &rawParams,
+	}
+}
+
 // Ruleset represents a GitHub ruleset object.
 type Ruleset struct {
 	ID   *int64 `json:"id,omitempty"`


Test patch:
diff --git a/github/examples_test.go b/github/examples_test.go
index ab319151..9338e0b6 100644
--- a/github/examples_test.go
+++ b/github/examples_test.go
@@ -49,14 +49,14 @@ func ExampleRepositoriesService_GetReadme() {
 	fmt.Printf("google/go-github README:\n%v\n", content)
 }
 
-func ExampleRepositoriesService_List() {
+func ExampleRepositoriesService_ListByUser() {
 	client := github.NewClient(nil)
 
 	user := "willnorris"
-	opt := &github.RepositoryListOptions{Type: "owner", Sort: "updated", Direction: "desc"}
+	opt := &github.RepositoryListByUserOptions{Type: "owner", Sort: "updated", Direction: "desc"}
 
 	ctx := context.Background()
-	repos, _, err := client.Repositories.List(ctx, user, opt)
+	repos, _, err := client.Repositories.ListByUser(ctx, user, opt)
 	if err != nil {
 		fmt.Println(err)
 	}
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 2c26fb6b..abe96ecf 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -5899,6 +5899,56 @@ func TestCredentialAuthorization_GetTokenLastEight(tt *testing.T) {
 	c.GetTokenLastEight()
 }
 
+func TestCustomProperty_GetDefaultValue(tt *testing.T) {
+	var zeroValue string
+	c := &CustomProperty{DefaultValue: &zeroValue}
+	c.GetDefaultValue()
+	c = &CustomProperty{}
+	c.GetDefaultValue()
+	c = nil
+	c.GetDefaultValue()
+}
+
+func TestCustomProperty_GetDescription(tt *testing.T) {
+	var zeroValue string
+	c := &CustomProperty{Description: &zeroValue}
+	c.GetDescription()
+	c = &CustomProperty{}
+	c.GetDescription()
+	c = nil
+	c.GetDescription()
+}
+
+func TestCustomProperty_GetPropertyName(tt *testing.T) {
+	var zeroValue string
+	c := &CustomProperty{PropertyName: &zeroValue}
+	c.GetPropertyName()
+	c = &CustomProperty{}
+	c.GetPropertyName()
+	c = nil
+	c.GetPropertyName()
+}
+
+func TestCustomProperty_GetRequired(tt *testing.T) {
+	var zeroValue bool
+	c := &CustomProperty{Required: &zeroValue}
+	c.GetRequired()
+	c = &CustomProperty{}
+	c.GetRequired()
+	c = nil
+	c.GetRequired()
+}
+
+func TestCustomPropertyValue_GetValue(tt *testing.T) {
+	var zeroValue string
+	c := &CustomPropertyValue{Value: &zeroValue}
+	c.GetValue()
+	c = &CustomPropertyValue{}
+	c.GetValue()
+	c = nil
+	c.GetValue()
+}
+
 func TestCustomRepoRoles_GetBaseRole(tt *testing.T) {
 	var zeroValue string
 	c := &CustomRepoRoles{BaseRole: &zeroValue}
@@ -23678,6 +23728,36 @@ func TestRuleRequiredStatusChecks_GetIntegrationID(tt *testing.T) {
 	r.GetIntegrationID()
 }
 
+func TestRuleRequiredWorkflow_GetRef(tt *testing.T) {
+	var zeroValue string
+	r := &RuleRequiredWorkflow{Ref: &zeroValue}
+	r.GetRef()
+	r = &RuleRequiredWorkflow{}
+	r.GetRef()
+	r = nil
+	r.GetRef()
+}
+
+func TestRuleRequiredWorkflow_GetRepositoryID(tt *testing.T) {
+	var zeroValue int64
+	r := &RuleRequiredWorkflow{RepositoryID: &zeroValue}
+	r.GetRepositoryID()
+	r = &RuleRequiredWorkflow{}
+	r.GetRepositoryID()
+	r = nil
+	r.GetRepositoryID()
+}
+
+func TestRuleRequiredWorkflow_GetSha(tt *testing.T) {
+	var zeroValue string
+	r := &RuleRequiredWorkflow{Sha: &zeroValue}
+	r.GetSha()
+	r = &RuleRequiredWorkflow{}
+	r.GetSha()
+	r = nil
+	r.GetSha()
+}
+
 func TestRuleset_GetConditions(tt *testing.T) {
 	r := &Ruleset{}
 	r.GetConditions()
diff --git a/github/orgs_properties_test.go b/github/orgs_properties_test.go
new file mode 100644
index 00000000..1a372306
--- /dev/null
+++ b/github/orgs_properties_test.go
@@ -0,0 +1,369 @@
+// Copyright 2023 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestOrganizationsService_GetAllCustomProperties(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/schema", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[
+		{
+          "property_name": "name",
+          "value_type": "single_select",
+          "required": true,
+          "default_value": "production",
+          "description": "Prod or dev environment",
+          "allowed_values":[
+            "production",
+            "development"
+          ]
+        },
+        {
+          "property_name": "service",
+          "value_type": "string"
+        },
+        {
+          "property_name": "team",
+          "value_type": "string",
+          "description": "Team owning the repository"
+        }
+        ]`)
+	})
+
+	ctx := context.Background()
+	properties, _, err := client.Organizations.GetAllCustomProperties(ctx, "o")
+	if err != nil {
+		t.Errorf("Organizations.GetAllCustomProperties returned error: %v", err)
+	}
+
+	want := []*CustomProperty{
+		{
+			PropertyName:  String("name"),
+			ValueType:     "single_select",
+			Required:      Bool(true),
+			DefaultValue:  String("production"),
+			Description:   String("Prod or dev environment"),
+			AllowedValues: []string{"production", "development"},
+		},
+		{
+			PropertyName: String("service"),
+			ValueType:    "string",
+		},
+		{
+			PropertyName: String("team"),
+			ValueType:    "string",
+			Description:  String("Team owning the repository"),
+		},
+	}
+	if !cmp.Equal(properties, want) {
+		t.Errorf("Organizations.GetAllCustomProperties returned %+v, want %+v", properties, want)
+	}
+
+	const methodName = "GetAllCustomProperties"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetAllCustomProperties(ctx, "o")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_CreateOrUpdateCustomProperties(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/schema", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PATCH")
+		testBody(t, r, `{"properties":[{"property_name":"name","value_type":"single_select","required":true},{"property_name":"service","value_type":"string"}]}`+"\n")
+		fmt.Fprint(w, `[
+		{
+          "property_name": "name",
+          "value_type": "single_select",
+          "required": true
+        },
+        {
+          "property_name": "service",
+          "value_type": "string"
+        }
+        ]`)
+	})
+
+	ctx := context.Background()
+	properties, _, err := client.Organizations.CreateOrUpdateCustomProperties(ctx, "o", []*CustomProperty{
+		{
+			PropertyName: String("name"),
+			ValueType:    "single_select",
+			Required:     Bool(true),
+		},
+		{
+			PropertyName: String("service"),
+			ValueType:    "string",
+		},
+	})
+	if err != nil {
+		t.Errorf("Organizations.CreateOrUpdateCustomProperties returned error: %v", err)
+	}
+
+	want := []*CustomProperty{
+		{
+			PropertyName: String("name"),
+			ValueType:    "single_select",
+			Required:     Bool(true),
+		},
+		{
+			PropertyName: String("service"),
+			ValueType:    "string",
+		},
+	}
+
+	if !cmp.Equal(properties, want) {
+		t.Errorf("Organizations.CreateOrUpdateCustomProperties returned %+v, want %+v", properties, want)
+	}
+
+	const methodName = "CreateOrUpdateCustomProperties"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.CreateOrUpdateCustomProperties(ctx, "o", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_GetCustomProperty(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/schema/name", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{
+		"property_name": "name",
+		"value_type": "single_select",
+		"required": true,
+		"default_value": "production",
+		"description": "Prod or dev environment",
+		"allowed_values":[
+		  "production",
+		  "development"
+		]
+	  }`)
+	})
+
+	ctx := context.Background()
+	property, _, err := client.Organizations.GetCustomProperty(ctx, "o", "name")
+	if err != nil {
+		t.Errorf("Organizations.GetCustomProperty returned error: %v", err)
+	}
+
+	want := &CustomProperty{
+		PropertyName:  String("name"),
+		ValueType:     "single_select",
+		Required:      Bool(true),
+		DefaultValue:  String("production"),
+		Description:   String("Prod or dev environment"),
+		AllowedValues: []string{"production", "development"},
+	}
+	if !cmp.Equal(property, want) {
+		t.Errorf("Organizations.GetCustomProperty returned %+v, want %+v", property, want)
+	}
+
+	const methodName = "GetCustomProperty"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetCustomProperty(ctx, "o", "name")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_CreateOrUpdateCustomProperty(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/schema/name", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		fmt.Fprint(w, `{
+		"property_name": "name",
+		"value_type": "single_select",
+		"required": true,
+		"default_value": "production",
+		"description": "Prod or dev environment",
+		"allowed_values":[
+		  "production",
+		  "development"
+		]
+	  }`)
+	})
+
+	ctx := context.Background()
+	property, _, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, "o", "name", &CustomProperty{
+		ValueType:     "single_select",
+		Required:      Bool(true),
+		DefaultValue:  String("production"),
+		Description:   String("Prod or dev environment"),
+		AllowedValues: []string{"production", "development"},
+	})
+	if err != nil {
+		t.Errorf("Organizations.CreateOrUpdateCustomProperty returned error: %v", err)
+	}
+
+	want := &CustomProperty{
+		PropertyName:  String("name"),
+		ValueType:     "single_select",
+		Required:      Bool(true),
+		DefaultValue:  String("production"),
+		Description:   String("Prod or dev environment"),
+		AllowedValues: []string{"production", "development"},
+	}
+	if !cmp.Equal(property, want) {
+		t.Errorf("Organizations.CreateOrUpdateCustomProperty returned %+v, want %+v", property, want)
+	}
+
+	const methodName = "CreateOrUpdateCustomProperty"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.CreateOrUpdateCustomProperty(ctx, "o", "name", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_RemoveCustomProperty(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/schema/name", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	ctx := context.Background()
+	_, err := client.Organizations.RemoveCustomProperty(ctx, "o", "name")
+	if err != nil {
+		t.Errorf("Organizations.RemoveCustomProperty returned error: %v", err)
+	}
+
+	const methodName = "RemoveCustomProperty"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Organizations.RemoveCustomProperty(ctx, "0", "name")
+	})
+}
+
+func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/values", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"page": "1", "per_page": "100"})
+		fmt.Fprint(w, `[{
+		"repository_id": 1296269,
+		"repository_name": "Hello-World",
+		"repository_full_name": "octocat/Hello-World",	
+		"properties": [
+		{
+          "property_name": "environment",
+          "value": "production"
+        },
+        {
+          "property_name": "service",
+          "value": "web"
+        }
+		]
+        }]`)
+	})
+
+	ctx := context.Background()
+	repoPropertyValues, _, err := client.Organizations.ListCustomPropertyValues(ctx, "o", &ListOptions{
+		Page:    1,
+		PerPage: 100,
+	})
+	if err != nil {
+		t.Errorf("Organizations.ListCustomPropertyValues returned error: %v", err)
+	}
+
+	want := []*RepoCustomPropertyValue{
+		{
+			RepositoryID:       1296269,
+			RepositoryName:     "Hello-World",
+			RepositoryFullName: "octocat/Hello-World",
+			Properties: []*CustomPropertyValue{
+				{
+					PropertyName: "environment",
+					Value:        String("production"),
+				},
+				{
+					PropertyName: "service",
+					Value:        String("web"),
+				},
+			},
+		},
+	}
+
+	if !cmp.Equal(repoPropertyValues, want) {
+		t.Errorf("Organizations.ListCustomPropertyValues returned %+v, want %+v", repoPropertyValues, want)
+	}
+
+	const methodName = "ListCustomPropertyValues"
+
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.ListCustomPropertyValues(ctx, "\n", &ListOptions{})
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.ListCustomPropertyValues(ctx, "o", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_CreateOrUpdateRepoCustomPropertyValues(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/properties/values", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PATCH")
+		testBody(t, r, `{"repository_names":["repo"],"properties":[{"property_name":"service","value_type":"string"}]}`+"\n")
+	})
+
+	ctx := context.Background()
+	_, err := client.Organizations.CreateOrUpdateRepoCustomPropertyValues(ctx, "o", []string{"repo"}, []*CustomProperty{
+		{
+			PropertyName: String("service"),
+			ValueType:    "string",
+		},
+	})
+	if err != nil {
+		t.Errorf("Organizations.CreateOrUpdateCustomPropertyValuesForRepos returned error: %v", err)
+	}
+
+	const methodName = "CreateOrUpdateCustomPropertyValuesForRepos"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Organizations.CreateOrUpdateRepoCustomPropertyValues(ctx, "o", nil, nil)
+	})
+}
diff --git a/github/repos_rules_test.go b/github/repos_rules_test.go
index a57137c6..cd8e49c2 100644
--- a/github/repos_rules_test.go
+++ b/github/repos_rules_test.go
@@ -215,6 +215,17 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) {
 			},
 			wantErr: true,
 		},
+		"Required workflows params": {
+			data: `{"type":"workflows","parameters":{"workflows":[{"path": ".github/workflows/test.yml", "repository_id": 1}]}}`,
+			want: NewRequiredWorkflowsRule(&RequiredWorkflowsRuleParameters{
+				RequiredWorkflows: []*RuleRequiredWorkflow{
+					{
+						Path:         ".github/workflows/test.yml",
+						RepositoryID: Int64(1),
+					},
+				},
+			}),
+		},
 		"Invalid type": {
 			data: `{"type":"unknown"}`,
 			want: &RepositoryRule{
diff --git a/github/repos_test.go b/github/repos_test.go
index 2bdc1b42..fa49ddd4 100644
--- a/github/repos_test.go
+++ b/github/repos_test.go
@@ -18,36 +18,30 @@ import (
 	"github.com/google/go-cmp/cmp"
 )
 
-func TestRepositoriesService_List_authenticatedUser(t *testing.T) {
+func TestRepositoriesService_ListByAuthenticatedUser(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
-	wantAcceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
 	mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
 		fmt.Fprint(w, `[{"id":1},{"id":2}]`)
 	})
 
 	ctx := context.Background()
-	got, _, err := client.Repositories.List(ctx, "", nil)
+	got, _, err := client.Repositories.ListByAuthenticatedUser(ctx, nil)
 	if err != nil {
 		t.Errorf("Repositories.List returned error: %v", err)
 	}
 
 	want := []*Repository{{ID: Int64(1)}, {ID: Int64(2)}}
 	if !cmp.Equal(got, want) {
-		t.Errorf("Repositories.List returned %+v, want %+v", got, want)
+		t.Errorf("Repositories.ListByAuthenticatedUser returned %+v, want %+v", got, want)
 	}
 
-	const methodName = "List"
-	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Repositories.List(ctx, "\n", &RepositoryListOptions{})
-		return err
-	})
+	const methodName = "ListByAuthenticatedUser"
 
 	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Repositories.List(ctx, "", nil)
+		got, resp, err := client.Repositories.ListByAuthenticatedUser(ctx, nil)
 		if got != nil {
 			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
 		}
@@ -55,78 +49,84 @@ func TestRepositoriesService_List_authenticatedUser(t *testing.T) {
 	})
 }
 
-func TestRepositoriesService_List_specifiedUser(t *testing.T) {
+func TestRepositoriesService_ListByUser(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
-	wantAcceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
 	mux.HandleFunc("/users/u/repos", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
 		testFormValues(t, r, values{
-			"visibility":  "public",
-			"affiliation": "owner,collaborator",
-			"sort":        "created",
-			"direction":   "asc",
-			"page":        "2",
+			"sort":      "created",
+			"direction": "asc",
+			"page":      "2",
 		})
 		fmt.Fprint(w, `[{"id":1}]`)
 	})
 
-	opt := &RepositoryListOptions{
-		Visibility:  "public",
-		Affiliation: "owner,collaborator",
+	opt := &RepositoryListByUserOptions{
 		Sort:        "created",
 		Direction:   "asc",
 		ListOptions: ListOptions{Page: 2},
 	}
 	ctx := context.Background()
-	repos, _, err := client.Repositories.List(ctx, "u", opt)
+	repos, _, err := client.Repositories.ListByUser(ctx, "u", opt)
 	if err != nil {
 		t.Errorf("Repositories.List returned error: %v", err)
 	}
 
 	want := []*Repository{{ID: Int64(1)}}
 	if !cmp.Equal(repos, want) {
-		t.Errorf("Repositories.List returned %+v, want %+v", repos, want)
+		t.Errorf("Repositories.ListByUser returned %+v, want %+v", repos, want)
 	}
+
+	const methodName = "ListByUser"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.ListByUser(ctx, "\n", &RepositoryListByUserOptions{})
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.ListByUser(ctx, "u", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
 }
 
-func TestRepositoriesService_List_specifiedUser_type(t *testing.T) {
+func TestRepositoriesService_ListByUser_type(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
-	wantAcceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeRepositoryVisibilityPreview}
 	mux.HandleFunc("/users/u/repos", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
 		testFormValues(t, r, values{
 			"type": "owner",
 		})
 		fmt.Fprint(w, `[{"id":1}]`)
 	})
 
-	opt := &RepositoryListOptions{
+	opt := &RepositoryListByUserOptions{
 		Type: "owner",
 	}
 	ctx := context.Background()
-	repos, _, err := client.Repositories.List(ctx, "u", opt)
+	repos, _, err := client.Repositories.ListByUser(ctx, "u", opt)
 	if err != nil {
-		t.Errorf("Repositories.List returned error: %v", err)
+		t.Errorf("Repositories.ListByUser returned error: %v", err)
 	}
 
 	want := []*Repository{{ID: Int64(1)}}
 	if !cmp.Equal(repos, want) {
-		t.Errorf("Repositories.List returned %+v, want %+v", repos, want)
+		t.Errorf("Repositories.ListByUser returned %+v, want %+v", repos, want)
 	}
 }
 
-func TestRepositoriesService_List_invalidUser(t *testing.T) {
+func TestRepositoriesService_ListByUser_invalidUser(t *testing.T) {
 	client, _, _, teardown := setup()
 	defer teardown()
 
 	ctx := context.Background()
-	_, _, err := client.Repositories.List(ctx, "%", nil)
+	_, _, err := client.Repositories.ListByUser(ctx, "%", nil)
 	testURLParseError(t, err)
 }
 
diff --git a/test/integration/repos_test.go b/test/integration/repos_test.go
index b0fa75cb..39a99fef 100644
--- a/test/integration/repos_test.go
+++ b/test/integration/repos_test.go
@@ -157,29 +157,31 @@ func TestRepositories_EditBranches(t *testing.T) {
 	}
 }
 
-func TestRepositories_List(t *testing.T) {
-	if !checkAuth("TestRepositories_List") {
+func TestRepositories_ListByAuthenticatedUser(t *testing.T) {
+	if !checkAuth("TestRepositories_ListByAuthenticatedUser") {
 		return
 	}
 
-	_, _, err := client.Repositories.List(context.Background(), "", nil)
+	_, _, err := client.Repositories.ListByAuthenticatedUser(context.Background(), nil)
 	if err != nil {
-		t.Fatalf("Repositories.List('') returned error: %v", err)
+		t.Fatalf("Repositories.ListByAuthenticatedUser() returned error: %v", err)
 	}
+}
 
-	_, _, err = client.Repositories.List(context.Background(), "google", nil)
+func TestRepositories_ListByUser(t *testing.T) {
+	_, _, err := client.Repositories.ListByUser(context.Background(), "google", nil)
 	if err != nil {
-		t.Fatalf("Repositories.List('google') returned error: %v", err)
+		t.Fatalf("Repositories.ListByUser('google') returned error: %v", err)
 	}
 
-	opt := github.RepositoryListOptions{Sort: "created"}
-	repos, _, err := client.Repositories.List(context.Background(), "google", &opt)
+	opt := github.RepositoryListByUserOptions{Sort: "created"}
+	repos, _, err := client.Repositories.ListByUser(context.Background(), "google", &opt)
 	if err != nil {
 		t.Fatalf("Repositories.List('google') with Sort opt returned error: %v", err)
 	}
 	for i, repo := range repos {
 		if i > 0 && (*repos[i-1].CreatedAt).Time.Before((*repo.CreatedAt).Time) {
-			t.Fatalf("Repositories.List('google') with default descending Sort returned incorrect order")
+			t.Fatalf("Repositories.ListByUser('google') with default descending Sort returned incorrect order")
 		}
 	}
 }