Repository: google/go-github
Pull request #2795 (2023-06-02 03:51:10)

Implement rulesets
This PR includes both the organization and repository rulesets API.

Resolves https://github.com/google/go-github/issues/2789

Signed-off-by: Daniel Liao [10663736+liaodaniel@users.noreply.github.com](mailto:10663736+liaodaniel@users.noreply.github.com)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/2795?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2795](https://app.codecov.io/gh/google/go-github/pull/2795?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (c390b09) into [master](https://app.codecov.io/gh/google/go-github/commit/d6880db28be3f950bff2298cd4dbff47a48d1196?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (d6880db) will **decrease** coverage by `0.15%`.
> The diff coverage is `90.98%`.

```diff
@@            Coverage Diff             @@
##           master    #2795      +/-   ##
==========================================
- Coverage   98.06%   97.91%   -0.15%     
==========================================
  Files         132      134       +2     
  Lines       11650    11894     +244     
==========================================
+ Hits        11424    11646     +222     
- Misses        154      170      +16     
- Partials       72       78       +6     
```


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


liaodaniel: Linter appears to be failing due to https://github.com/golangci/golangci-lint/issues/3862. Guess I'll wait until they make a fix for it

Patch:
diff --git a/README.md b/README.md
index 6858fd1a..9a5386c2 100644
--- a/README.md
+++ b/README.md
@@ -396,6 +396,7 @@ Versions prior to 48.2.0 are not listed.
 
 | go-github Version | GitHub v3 API Version |
 | ----------------- | --------------------- |
+| 53.1.0            | 2022-11-28            |
 | 53.0.0            | 2022-11-28            |
 | 52.0.0            | 2022-11-28            |
 | 51.0.0            | 2022-11-28            |
diff --git a/github/github-accessors.go b/github/github-accessors.go
index a9aaee81..2d2e6423 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -2222,6 +2222,22 @@ func (b *BranchProtectionRuleEvent) GetSender() *User {
 	return b.Sender
 }
 
+// GetActorID returns the ActorID field if it's non-nil, zero value otherwise.
+func (b *BypassActor) GetActorID() int64 {
+	if b == nil || b.ActorID == nil {
+		return 0
+	}
+	return *b.ActorID
+}
+
+// GetActorType returns the ActorType field if it's non-nil, zero value otherwise.
+func (b *BypassActor) GetActorType() string {
+	if b == nil || b.ActorType == nil {
+		return ""
+	}
+	return *b.ActorType
+}
+
 // GetApp returns the App field.
 func (c *CheckRun) GetApp() *App {
 	if c == nil {
@@ -17918,6 +17934,14 @@ func (r *RepositoryRelease) GetZipballURL() string {
 	return *r.ZipballURL
 }
 
+// GetParameters returns the Parameters field if it's non-nil, zero value otherwise.
+func (r *RepositoryRule) GetParameters() json.RawMessage {
+	if r == nil || r.Parameters == nil {
+		return json.RawMessage{}
+	}
+	return *r.Parameters
+}
+
 // GetCommit returns the Commit field.
 func (r *RepositoryTag) GetCommit() *Commit {
 	if r == nil {
@@ -18366,6 +18390,118 @@ func (r *Rule) GetSeverity() string {
 	return *r.Severity
 }
 
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (r *RulePatternParameters) GetName() string {
+	if r == nil || r.Name == nil {
+		return ""
+	}
+	return *r.Name
+}
+
+// GetNegate returns the Negate field if it's non-nil, zero value otherwise.
+func (r *RulePatternParameters) GetNegate() bool {
+	if r == nil || r.Negate == nil {
+		return false
+	}
+	return *r.Negate
+}
+
+// GetIntegrationID returns the IntegrationID field if it's non-nil, zero value otherwise.
+func (r *RuleRequiredStatusChecks) GetIntegrationID() int64 {
+	if r == nil || r.IntegrationID == nil {
+		return 0
+	}
+	return *r.IntegrationID
+}
+
+// GetBypassMode returns the BypassMode field if it's non-nil, zero value otherwise.
+func (r *Ruleset) GetBypassMode() string {
+	if r == nil || r.BypassMode == nil {
+		return ""
+	}
+	return *r.BypassMode
+}
+
+// GetConditions returns the Conditions field.
+func (r *Ruleset) GetConditions() *RulesetConditions {
+	if r == nil {
+		return nil
+	}
+	return r.Conditions
+}
+
+// GetLinks returns the Links field.
+func (r *Ruleset) GetLinks() *RulesetLinks {
+	if r == nil {
+		return nil
+	}
+	return r.Links
+}
+
+// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
+func (r *Ruleset) GetNodeID() string {
+	if r == nil || r.NodeID == nil {
+		return ""
+	}
+	return *r.NodeID
+}
+
+// GetSourceType returns the SourceType field if it's non-nil, zero value otherwise.
+func (r *Ruleset) GetSourceType() string {
+	if r == nil || r.SourceType == nil {
+		return ""
+	}
+	return *r.SourceType
+}
+
+// GetTarget returns the Target field if it's non-nil, zero value otherwise.
+func (r *Ruleset) GetTarget() string {
+	if r == nil || r.Target == nil {
+		return ""
+	}
+	return *r.Target
+}
+
+// GetRefName returns the RefName field.
+func (r *RulesetConditions) GetRefName() *RulesetRefConditionParameters {
+	if r == nil {
+		return nil
+	}
+	return r.RefName
+}
+
+// GetRepositoryName returns the RepositoryName field.
+func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryConditionParameters {
+	if r == nil {
+		return nil
+	}
+	return r.RepositoryName
+}
+
+// GetHRef returns the HRef field if it's non-nil, zero value otherwise.
+func (r *RulesetLink) GetHRef() string {
+	if r == nil || r.HRef == nil {
+		return ""
+	}
+	return *r.HRef
+}
+
+// GetSelf returns the Self field.
+func (r *RulesetLinks) GetSelf() *RulesetLink {
+	if r == nil {
+		return nil
+	}
+	return r.Self
+}
+
+// GetProtected returns the Protected field if it's non-nil, zero value otherwise.
+func (r *RulesetRepositoryConditionParameters) GetProtected() bool {
+	if r == nil || r.Protected == nil {
+		return false
+	}
+	return *r.Protected
+}
+
 // GetBusy returns the Busy field if it's non-nil, zero value otherwise.
 func (r *Runner) GetBusy() bool {
 	if r == nil || r.Busy == nil {
diff --git a/github/github.go b/github/github.go
index 7d8aef53..db623ffd 100644
--- a/github/github.go
+++ b/github/github.go
@@ -28,7 +28,7 @@ import (
 )
 
 const (
-	Version = "v53.0.0"
+	Version = "v53.1.0"
 
 	defaultAPIVersion = "2022-11-28"
 	defaultBaseURL    = "https://api.github.com/"
diff --git a/github/orgs_rules.go b/github/orgs_rules.go
new file mode 100644
index 00000000..a3905af8
--- /dev/null
+++ b/github/orgs_rules.go
@@ -0,0 +1,105 @@
+// 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"
+)
+
+// GetAllOrganizationRulesets gets all the rulesets for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#get-all-organization-repository-rulesets
+func (s *OrganizationsService) GetAllOrganizationRulesets(ctx context.Context, org string) ([]*Ruleset, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/rulesets", org)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rulesets []*Ruleset
+	resp, err := s.client.Do(ctx, req, &rulesets)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rulesets, resp, nil
+}
+
+// CreateOrganizationRuleset creates a ruleset for the specified organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#create-an-organization-repository-ruleset
+func (s *OrganizationsService) CreateOrganizationRuleset(ctx context.Context, org string, rs *Ruleset) (*Ruleset, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/rulesets", org)
+
+	req, err := s.client.NewRequest("POST", u, rs)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset *Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// GetOrganizationRuleset gets a ruleset from the specified organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#get-an-organization-repository-ruleset
+func (s *OrganizationsService) GetOrganizationRuleset(ctx context.Context, org string, rulesetID int64) (*Ruleset, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset *Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// UpdateOrganizationRuleset updates a ruleset from the specified organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#update-an-organization-repository-ruleset
+func (s *OrganizationsService) UpdateOrganizationRuleset(ctx context.Context, org string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID)
+
+	req, err := s.client.NewRequest("PUT", u, rs)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset *Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// DeleteOrganizationRuleset deletes a ruleset from the specified organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#delete-an-organization-repository-ruleset
+func (s *OrganizationsService) DeleteOrganizationRuleset(ctx context.Context, org string, rulesetID int64) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/github/repos_contents.go b/github/repos_contents.go
index be58fd52..874a3277 100644
--- a/github/repos_contents.go
+++ b/github/repos_contents.go
@@ -192,8 +192,15 @@ func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owne
 // as possible, both result types will be returned but only one will contain a
 // value and the other will be nil.
 //
+// Due to an auth vulnerability issue in the GitHub v3 API, ".." is not allowed
+// to appear anywhere in the "path" or this method will return an error.
+//
 // GitHub API docs: https://docs.github.com/en/rest/repos/contents#get-repository-content
 func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opts *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) {
+	if strings.Contains(path, "..") {
+		return nil, nil, nil, errors.New("path must not contain '..' due to auth vulnerability issue")
+	}
+
 	escapedPath := (&url.URL{Path: strings.TrimSuffix(path, "/")}).String()
 	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath)
 	u, err = addOptions(u, opts)
diff --git a/github/repos_rules.go b/github/repos_rules.go
new file mode 100644
index 00000000..64e7c9ca
--- /dev/null
+++ b/github/repos_rules.go
@@ -0,0 +1,447 @@
+// 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"
+	"encoding/json"
+	"fmt"
+)
+
+// BypassActor represents the bypass actors from a ruleset.
+type BypassActor struct {
+	ActorID *int64 `json:"actor_id,omitempty"`
+	// Possible values for ActorType are: Team, Integration
+	ActorType *string `json:"actor_type,omitempty"`
+}
+
+// RulesetLink represents a single link object from GitHub ruleset request _links.
+type RulesetLink struct {
+	HRef *string `json:"href,omitempty"`
+}
+
+// RulesetLinks represents the "_links" object in a Ruleset.
+type RulesetLinks struct {
+	Self *RulesetLink `json:"self,omitempty"`
+}
+
+// RulesetRefConditionParameters represents the conditions object for ref_names.
+type RulesetRefConditionParameters struct {
+	Include []string `json:"include,omitempty"`
+	Exclude []string `json:"exclude,omitempty"`
+}
+
+// RulesetRepositoryConditionParameters represents the conditions object for repository_names.
+type RulesetRepositoryConditionParameters struct {
+	Include   []string `json:"include,omitempty"`
+	Exclude   []string `json:"exclude,omitempty"`
+	Protected *bool    `json:"protected,omitempty"`
+}
+
+// RulesetCondition represents the conditions object in a ruleset.
+type RulesetConditions struct {
+	RefName        *RulesetRefConditionParameters        `json:"ref_name,omitempty"`
+	RepositoryName *RulesetRepositoryConditionParameters `json:"repository_name,omitempty"`
+}
+
+// RulePatternParameters represents the rule pattern parameters.
+type RulePatternParameters struct {
+	Name *string `json:"name,omitempty"`
+	// If Negate is true, the rule will fail if the pattern matches.
+	Negate *bool `json:"negate,omitempty"`
+	// Possible values for Operator are: starts_with, ends_with, contains, regex
+	Operator string `json:"operator"`
+	Pattern  string `json:"pattern"`
+}
+
+// UpdateAllowsFetchAndMergeRuleParameters represents the update rule parameters.
+type UpdateAllowsFetchAndMergeRuleParameters struct {
+	UpdateAllowsFetchAndMerge bool `json:"update_allows_fetch_and_merge"`
+}
+
+// RequiredDeploymentEnvironmentsRuleParameters represents the required_deployments rule parameters.
+type RequiredDeploymentEnvironmentsRuleParameters struct {
+	RequiredDeploymentEnvironments []string `json:"required_deployment_environments"`
+}
+
+// PullRequestRuleParameters represents the pull_request rule parameters.
+type PullRequestRuleParameters struct {
+	DismissStaleReviewsOnPush      bool `json:"dismiss_stale_reviews_on_push"`
+	RequireCodeOwnerReview         bool `json:"require_code_owner_review"`
+	RequireLastPushApproval        bool `json:"require_last_push_approval"`
+	RequiredApprovingReviewCount   int  `json:"required_approving_review_count"`
+	RequiredReviewThreadResolution bool `json:"required_review_thread_resolution"`
+}
+
+// RuleRequiredStatusChecks represents the RequiredStatusChecks for the RequiredStatusChecksRuleParameters object.
+type RuleRequiredStatusChecks struct {
+	Context       string `json:"context"`
+	IntegrationID *int64 `json:"integration_id,omitempty"`
+}
+
+// RequiredStatusChecksRuleParameters represents the required_status_checks rule parameters.
+type RequiredStatusChecksRuleParameters struct {
+	RequiredStatusChecks             []RuleRequiredStatusChecks `json:"required_status_checks"`
+	StrictRequiredStatusChecksPolicy bool                       `json:"strict_required_status_checks_policy"`
+}
+
+// RepositoryRule represents a GitHub Rule.
+type RepositoryRule struct {
+	Type       string           `json:"type"`
+	Parameters *json.RawMessage `json:"parameters,omitempty"`
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+// This helps us handle the fact that RepositoryRule parameter field can be of numerous types.
+func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
+	type rule RepositoryRule
+	var RepositoryRule rule
+	if err := json.Unmarshal(data, &RepositoryRule); err != nil {
+		return err
+	}
+
+	r.Type = RepositoryRule.Type
+
+	switch RepositoryRule.Type {
+	case "creation", "deletion", "required_linear_history", "required_signatures", "non_fast_forward":
+		r.Parameters = nil
+	case "update":
+		params := UpdateAllowsFetchAndMergeRuleParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
+
+		r.Parameters = &rawParams
+	case "required_deployments":
+		params := RequiredDeploymentEnvironmentsRuleParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
+
+		r.Parameters = &rawParams
+	case "commit_message_pattern", "commit_author_email_pattern", "committer_email_pattern", "branch_name_pattern", "tag_name_pattern":
+		params := RulePatternParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
+
+		r.Parameters = &rawParams
+	case "pull_request":
+		params := PullRequestRuleParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
+
+		r.Parameters = &rawParams
+	case "required_status_checks":
+		params := RequiredStatusChecksRuleParameters{}
+		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 = ""
+		r.Parameters = nil
+		return fmt.Errorf("RepositoryRule.Type %T is not yet implemented, unable to unmarshal", RepositoryRule.Type)
+	}
+
+	return nil
+}
+
+// NewCreationRule creates a rule to only allow users with bypass permission to create matching refs.
+func NewCreationRule() (rule *RepositoryRule) {
+	return &RepositoryRule{
+		Type: "creation",
+	}
+}
+
+// NewUpdateRule creates a rule to only allow users with bypass permission to update matching refs.
+func NewUpdateRule(params *UpdateAllowsFetchAndMergeRuleParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "update",
+		Parameters: &rawParams,
+	}
+}
+
+// NewDeletionRule creates a rule to only allow users with bypass permissions to delete matching refs.
+func NewDeletionRule() (rule *RepositoryRule) {
+	return &RepositoryRule{
+		Type: "deletion",
+	}
+}
+
+// NewRequiredLinearHistoryRule creates a rule to prevent merge commits from being pushed to matching branches.
+func NewRequiredLinearHistoryRule() (rule *RepositoryRule) {
+	return &RepositoryRule{
+		Type: "required_linear_history",
+	}
+}
+
+// NewRequiredDeploymentsRule creates a rule to require environments to be successfully deployed before they can be merged into the matching branches.
+func NewRequiredDeploymentsRule(params *RequiredDeploymentEnvironmentsRuleParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "required_deployments",
+		Parameters: &rawParams,
+	}
+}
+
+// NewRequiredSignaturesRule creates a rule a to require commits pushed to matching branches to have verified signatures.
+func NewRequiredSignaturesRule() (rule *RepositoryRule) {
+	return &RepositoryRule{
+		Type: "required_signatures",
+	}
+}
+
+// NewPullRequestRule creates a rule to require all commits be made to a non-target branch and submitted via a pull request before they can be merged.
+func NewPullRequestRule(params *PullRequestRuleParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "pull_request",
+		Parameters: &rawParams,
+	}
+}
+
+// NewRequiredStatusChecksRule creates a rule to require which status checks must pass before branches can be merged into a branch rule.
+func NewRequiredStatusChecksRule(params *RequiredStatusChecksRuleParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "required_status_checks",
+		Parameters: &rawParams,
+	}
+}
+
+// NewNonFastForwardRule creates a rule as part to prevent users with push access from force pushing to matching branches.
+func NewNonFastForwardRule() (rule *RepositoryRule) {
+	return &RepositoryRule{
+		Type: "non_fast_forward",
+	}
+}
+
+// NewCommitMessagePatternRule creates a rule to restrict commit message patterns being pushed to matching branches.
+func NewCommitMessagePatternRule(params *RulePatternParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "commit_message_pattern",
+		Parameters: &rawParams,
+	}
+}
+
+// NewCommitAuthorEmailPatternRule creates a rule to restrict commits with author email patterns being merged into matching branches.
+func NewCommitAuthorEmailPatternRule(params *RulePatternParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "commit_author_email_pattern",
+		Parameters: &rawParams,
+	}
+}
+
+// NewCommitterEmailPatternRule creates a rule to restrict commits with committer email patterns being merged into matching branches.
+func NewCommitterEmailPatternRule(params *RulePatternParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "committer_email_pattern",
+		Parameters: &rawParams,
+	}
+}
+
+// NewBranchNamePatternRule creates a rule to restrict branch patterns from being merged into matching branches.
+func NewBranchNamePatternRule(params *RulePatternParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "branch_name_pattern",
+		Parameters: &rawParams,
+	}
+}
+
+// NewTagNamePatternRule creates a rule to restrict tag patterns contained in non-target branches from being merged into matching branches.
+func NewTagNamePatternRule(params *RulePatternParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "tag_name_pattern",
+		Parameters: &rawParams,
+	}
+}
+
+// Ruleset represents a GitHub ruleset object.
+type Ruleset struct {
+	ID   int64  `json:"id"`
+	Name string `json:"name"`
+	// Possible values for Target are branch, tag
+	Target *string `json:"target,omitempty"`
+	// Possible values for SourceType are: Repository, Organization
+	SourceType *string `json:"source_type,omitempty"`
+	Source     string  `json:"source"`
+	// Possible values for Enforcement are: disabled, active, evaluate
+	Enforcement string `json:"enforcement"`
+	// Possible values for BypassMode are: none, repository, organization
+	BypassMode   *string            `json:"bypass_mode,omitempty"`
+	BypassActors []*BypassActor     `json:"bypass_actors,omitempty"`
+	NodeID       *string            `json:"node_id,omitempty"`
+	Links        *RulesetLinks      `json:"_links,omitempty"`
+	Conditions   *RulesetConditions `json:"conditions,omitempty"`
+	Rules        []*RepositoryRule  `json:"rules,omitempty"`
+}
+
+// GetRulesForBranch gets all the rules that apply to the specified branch.
+//
+// GitHub API docs: https://docs.github.com/en/rest/repos/rules#get-rules-for-a-branch
+func (s *RepositoriesService) GetRulesForBranch(ctx context.Context, owner, repo, branch string) ([]*RepositoryRule, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/rules/branches/%v", owner, repo, branch)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rules []*RepositoryRule
+	resp, err := s.client.Do(ctx, req, &rules)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rules, resp, nil
+}
+
+// GetAllRulesets gets all the rules that apply to the specified repository.
+// If includesParents is true, rulesets configured at the organization level that apply to the repository will be returned.
+//
+// GitHub API docs: https://docs.github.com/en/rest/repos/rules#get-all-repository-rulesets
+func (s *RepositoriesService) GetAllRulesets(ctx context.Context, owner, repo string, includesParents bool) ([]*Ruleset, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/rulesets?includes_parents=%v", owner, repo, includesParents)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset []*Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// CreateRuleset creates a ruleset for the specified repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/repos/rules#create-a-repository-ruleset
+func (s *RepositoriesService) CreateRuleset(ctx context.Context, owner, repo string, rs *Ruleset) (*Ruleset, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/rulesets", owner, repo)
+
+	req, err := s.client.NewRequest("POST", u, rs)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset *Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// GetRuleset gets a ruleset for the specified repository.
+// If includesParents is true, rulesets configured at the organization level that apply to the repository will be returned.
+//
+// GitHub API docs: https://docs.github.com/en/rest/repos/rules#get-a-repository-ruleset
+func (s *RepositoriesService) GetRuleset(ctx context.Context, owner, repo string, rulesetID int64, includesParents bool) (*Ruleset, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/rulesets/%v?includes_parents=%v", owner, repo, rulesetID, includesParents)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset *Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// UpdateRuleset updates a ruleset for the specified repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/repos/rules#update-a-repository-ruleset
+func (s *RepositoriesService) UpdateRuleset(ctx context.Context, owner, repo string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID)
+
+	req, err := s.client.NewRequest("PUT", u, rs)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ruleset *Ruleset
+	resp, err := s.client.Do(ctx, req, &ruleset)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ruleset, resp, nil
+}
+
+// DeleteRuleset deletes a ruleset for the specified repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/repos/rules#delete-a-repository-ruleset
+func (s *RepositoriesService) DeleteRuleset(ctx context.Context, owner, repo string, rulesetID int64) (*Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index a29baa0f..15485393 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -2664,6 +2664,26 @@ func TestBranchProtectionRuleEvent_GetSender(tt *testing.T) {
 	b.GetSender()
 }
 
+func TestBypassActor_GetActorID(tt *testing.T) {
+	var zeroValue int64
+	b := &BypassActor{ActorID: &zeroValue}
+	b.GetActorID()
+	b = &BypassActor{}
+	b.GetActorID()
+	b = nil
+	b.GetActorID()
+}
+
+func TestBypassActor_GetActorType(tt *testing.T) {
+	var zeroValue string
+	b := &BypassActor{ActorType: &zeroValue}
+	b.GetActorType()
+	b = &BypassActor{}
+	b.GetActorType()
+	b = nil
+	b.GetActorType()
+}
+
 func TestCheckRun_GetApp(tt *testing.T) {
 	c := &CheckRun{}
 	c.GetApp()
@@ -20865,6 +20885,16 @@ func TestRepositoryRelease_GetZipballURL(tt *testing.T) {
 	r.GetZipballURL()
 }
 
+func TestRepositoryRule_GetParameters(tt *testing.T) {
+	var zeroValue json.RawMessage
+	r := &RepositoryRule{Parameters: &zeroValue}
+	r.GetParameters()
+	r = &RepositoryRule{}
+	r.GetParameters()
+	r = nil
+	r.GetParameters()
+}
+
 func TestRepositoryTag_GetCommit(tt *testing.T) {
 	r := &RepositoryTag{}
 	r.GetCommit()
@@ -21404,6 +21434,131 @@ func TestRule_GetSeverity(tt *testing.T) {
 	r.GetSeverity()
 }
 
+func TestRulePatternParameters_GetName(tt *testing.T) {
+	var zeroValue string
+	r := &RulePatternParameters{Name: &zeroValue}
+	r.GetName()
+	r = &RulePatternParameters{}
+	r.GetName()
+	r = nil
+	r.GetName()
+}
+
+func TestRulePatternParameters_GetNegate(tt *testing.T) {
+	var zeroValue bool
+	r := &RulePatternParameters{Negate: &zeroValue}
+	r.GetNegate()
+	r = &RulePatternParameters{}
+	r.GetNegate()
+	r = nil
+	r.GetNegate()
+}
+
+func TestRuleRequiredStatusChecks_GetIntegrationID(tt *testing.T) {
+	var zeroValue int64
+	r := &RuleRequiredStatusChecks{IntegrationID: &zeroValue}
+	r.GetIntegrationID()
+	r = &RuleRequiredStatusChecks{}
+	r.GetIntegrationID()
+	r = nil
+	r.GetIntegrationID()
+}
+
+func TestRuleset_GetBypassMode(tt *testing.T) {
+	var zeroValue string
+	r := &Ruleset{BypassMode: &zeroValue}
+	r.GetBypassMode()
+	r = &Ruleset{}
+	r.GetBypassMode()
+	r = nil
+	r.GetBypassMode()
+}
+
+func TestRuleset_GetConditions(tt *testing.T) {
+	r := &Ruleset{}
+	r.GetConditions()
+	r = nil
+	r.GetConditions()
+}
+
+func TestRuleset_GetLinks(tt *testing.T) {
+	r := &Ruleset{}
+	r.GetLinks()
+	r = nil
+	r.GetLinks()
+}
+
+func TestRuleset_GetNodeID(tt *testing.T) {
+	var zeroValue string
+	r := &Ruleset{NodeID: &zeroValue}
+	r.GetNodeID()
+	r = &Ruleset{}
+	r.GetNodeID()
+	r = nil
+	r.GetNodeID()
+}
+
+func TestRuleset_GetSourceType(tt *testing.T) {
+	var zeroValue string
+	r := &Ruleset{SourceType: &zeroValue}
+	r.GetSourceType()
+	r = &Ruleset{}
+	r.GetSourceType()
+	r = nil
+	r.GetSourceType()
+}
+
+func TestRuleset_GetTarget(tt *testing.T) {
+	var zeroValue string
+	r := &Ruleset{Target: &zeroValue}
+	r.GetTarget()
+	r = &Ruleset{}
+	r.GetTarget()
+	r = nil
+	r.GetTarget()
+}
+
+func TestRulesetConditions_GetRefName(tt *testing.T) {
+	r := &RulesetConditions{}
+	r.GetRefName()
+	r = nil
+	r.GetRefName()
+}
+
+func TestRulesetConditions_GetRepositoryName(tt *testing.T) {
+	r := &RulesetConditions{}
+	r.GetRepositoryName()
+	r = nil
+	r.GetRepositoryName()
+}
+
+func TestRulesetLink_GetHRef(tt *testing.T) {
+	var zeroValue string
+	r := &RulesetLink{HRef: &zeroValue}
+	r.GetHRef()
+	r = &RulesetLink{}
+	r.GetHRef()
+	r = nil
+	r.GetHRef()
+}
+
+func TestRulesetLinks_GetSelf(tt *testing.T) {
+	r := &RulesetLinks{}
+	r.GetSelf()
+	r = nil
+	r.GetSelf()
+}
+
+func TestRulesetRepositoryConditionParameters_GetProtected(tt *testing.T) {
+	var zeroValue bool
+	r := &RulesetRepositoryConditionParameters{Protected: &zeroValue}
+	r.GetProtected()
+	r = &RulesetRepositoryConditionParameters{}
+	r.GetProtected()
+	r = nil
+	r.GetProtected()
+}
+
 func TestRunner_GetBusy(tt *testing.T) {
 	var zeroValue bool
 	r := &Runner{Busy: &zeroValue}
diff --git a/github/orgs_rules_test.go b/github/orgs_rules_test.go
new file mode 100644
index 00000000..7719e142
--- /dev/null
+++ b/github/orgs_rules_test.go
@@ -0,0 +1,628 @@
+// 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_GetAllOrganizationRulesets(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/rulesets", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[{
+			"id": 26110,
+			"name": "test ruleset",
+			"target": "branch",
+			"source_type": "Organization",
+			"source": "o",
+			"enforcement": "active",
+			"bypass_mode": "none",
+			"node_id": "nid",
+			"_links": {
+			  "self": {
+				"href": "https://api.github.com/orgs/o/rulesets/26110"
+			  }
+			}
+		}]`)
+	})
+
+	ctx := context.Background()
+	rulesets, _, err := client.Organizations.GetAllOrganizationRulesets(ctx, "o")
+	if err != nil {
+		t.Errorf("Organizations.GetAllOrganizationRulesets returned error: %v", err)
+	}
+
+	want := []*Ruleset{{
+		ID:          26110,
+		Name:        "test ruleset",
+		Target:      String("branch"),
+		SourceType:  String("Organization"),
+		Source:      "o",
+		Enforcement: "active",
+		BypassMode:  String("none"),
+		NodeID:      String("nid"),
+		Links: &RulesetLinks{
+			Self: &RulesetLink{HRef: String("https://api.github.com/orgs/o/rulesets/26110")},
+		},
+	}}
+	if !cmp.Equal(rulesets, want) {
+		t.Errorf("Organizations.GetAllOrganizationRulesets returned %+v, want %+v", rulesets, want)
+	}
+
+	const methodName = "GetAllOrganizationRulesets"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetAllOrganizationRulesets(ctx, "o")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/rulesets", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{
+			"id": 21,
+			"name": "ruleset",
+			"target": "branch",
+			"source_type": "Organization",
+			"source": "o",
+			"enforcement": "active",
+			"bypass_actors": [
+			  {
+				"actor_id": 234,
+				"actor_type": "Team"
+			  }
+			],
+			"conditions": {
+			  "ref_name": {
+				"include": [
+				  "refs/heads/main",
+				  "refs/heads/master"
+				],
+				"exclude": [
+				  "refs/heads/dev*"
+				]
+			  },
+			  "repository_name": {
+				"include": [
+				  "important_repository",
+				  "another_important_repository"
+				],
+				"exclude": [
+				  "unimportant_repository"
+				],
+				"protected": true
+			  }
+			},
+			"rules": [
+			  {
+				"type": "creation"
+			  },
+			  {
+				"type": "update",
+				"parameters": {
+				  "update_allows_fetch_and_merge": true
+				}
+			  },
+			  {
+				"type": "deletion"
+			  },
+			  {
+				"type": "required_linear_history"
+			  },
+			  {
+				"type": "required_deployments",
+				"parameters": {
+				  "required_deployment_environments": ["test"]
+				}
+			  },
+			  {
+				"type": "required_signatures"
+			  },
+			  {
+				"type": "pull_request",
+				"parameters": {
+				  "dismiss_stale_reviews_on_push": true,
+				  "require_code_owner_review": true,
+				  "require_last_push_approval": true,
+				  "required_approving_review_count": 1,
+				  "required_review_thread_resolution": true
+				}
+			  },
+			  {
+				"type": "required_status_checks",
+				"parameters": {
+				  "required_status_checks": [
+					{
+					  "context": "test",
+					  "integration_id": 1
+					}
+				  ],
+				  "strict_required_status_checks_policy": true
+				}
+			  },
+			  {
+				"type": "non_fast_forward"
+			  },
+			  {
+				"type": "commit_message_pattern",
+				"parameters": {
+				  "name": "avoid test commits",
+				  "negate": true,
+				  "operator": "starts_with",
+				  "pattern": "[test]"
+				}
+			  },
+			  {
+				"type": "commit_author_email_pattern",
+				"parameters": {
+				  "operator": "contains",
+				  "pattern": "github"
+				}
+			  },
+			  {
+				"type": "committer_email_pattern",
+				"parameters": {
+				  "name": "avoid commit emails",
+				  "negate": true,
+				  "operator": "ends_with",
+				  "pattern": "abc"
+				}
+			  },
+			  {
+				"type": "branch_name_pattern",
+				"parameters": {
+				  "name": "avoid branch names",
+				  "negate": true,
+				  "operator": "regex",
+				  "pattern": "github$"
+				}
+			  },
+			  {
+				"type": "tag_name_pattern",
+				"parameters": {
+				  "name": "avoid tag names",
+				  "negate": true,
+				  "operator": "contains",
+				  "pattern": "github"
+				}
+			  }
+			]
+		  }`)
+	})
+
+	ctx := context.Background()
+	ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", &Ruleset{
+		ID:          21,
+		Name:        "ruleset",
+		Target:      String("branch"),
+		SourceType:  String("Organization"),
+		Source:      "o",
+		Enforcement: "active",
+		BypassActors: []*BypassActor{
+			{
+				ActorID:   Int64(234),
+				ActorType: String("Team"),
+			},
+		},
+		Conditions: &RulesetConditions{
+			RefName: &RulesetRefConditionParameters{
+				Include: []string{"refs/heads/main", "refs/heads/master"},
+				Exclude: []string{"refs/heads/dev*"},
+			},
+			RepositoryName: &RulesetRepositoryConditionParameters{
+				Include:   []string{"important_repository", "another_important_repository"},
+				Exclude:   []string{"unimportant_repository"},
+				Protected: Bool(true),
+			},
+		},
+		Rules: []*RepositoryRule{
+			NewCreationRule(),
+			NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{
+				UpdateAllowsFetchAndMerge: true,
+			}),
+			NewDeletionRule(),
+			NewRequiredLinearHistoryRule(),
+			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
+				RequiredDeploymentEnvironments: []string{"test"},
+			}),
+			NewRequiredSignaturesRule(),
+			NewPullRequestRule(&PullRequestRuleParameters{
+				RequireCodeOwnerReview:         true,
+				RequireLastPushApproval:        true,
+				RequiredApprovingReviewCount:   1,
+				RequiredReviewThreadResolution: true,
+				DismissStaleReviewsOnPush:      true,
+			}),
+			NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{
+				RequiredStatusChecks: []RuleRequiredStatusChecks{
+					{
+						Context:       "test",
+						IntegrationID: Int64(1),
+					},
+				},
+				StrictRequiredStatusChecksPolicy: true,
+			}),
+			NewNonFastForwardRule(),
+			NewCommitMessagePatternRule(&RulePatternParameters{
+				Name:     String("avoid test commits"),
+				Negate:   Bool(true),
+				Operator: "starts_with",
+				Pattern:  "[test]",
+			}),
+			NewCommitAuthorEmailPatternRule(&RulePatternParameters{
+				Operator: "contains",
+				Pattern:  "github",
+			}),
+			NewCommitterEmailPatternRule(&RulePatternParameters{
+				Name:     String("avoid commit emails"),
+				Negate:   Bool(true),
+				Operator: "ends_with",
+				Pattern:  "abc",
+			}),
+			NewBranchNamePatternRule(&RulePatternParameters{
+				Name:     String("avoid branch names"),
+				Negate:   Bool(true),
+				Operator: "regex",
+				Pattern:  "github$",
+			}),
+			NewTagNamePatternRule(&RulePatternParameters{
+				Name:     String("avoid tag names"),
+				Negate:   Bool(true),
+				Operator: "contains",
+				Pattern:  "github",
+			}),
+		},
+	})
+	if err != nil {
+		t.Errorf("Organizations.CreateOrganizationRuleset returned error: %v", err)
+	}
+
+	want := &Ruleset{
+		ID:          21,
+		Name:        "ruleset",
+		Target:      String("branch"),
+		SourceType:  String("Organization"),
+		Source:      "o",
+		Enforcement: "active",
+		BypassActors: []*BypassActor{
+			{
+				ActorID:   Int64(234),
+				ActorType: String("Team"),
+			},
+		},
+		Conditions: &RulesetConditions{
+			RefName: &RulesetRefConditionParameters{
+				Include: []string{"refs/heads/main", "refs/heads/master"},
+				Exclude: []string{"refs/heads/dev*"},
+			},
+			RepositoryName: &RulesetRepositoryConditionParameters{
+				Include:   []string{"important_repository", "another_important_repository"},
+				Exclude:   []string{"unimportant_repository"},
+				Protected: Bool(true),
+			},
+		},
+		Rules: []*RepositoryRule{
+			NewCreationRule(),
+			NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{
+				UpdateAllowsFetchAndMerge: true,
+			}),
+			NewDeletionRule(),
+			NewRequiredLinearHistoryRule(),
+			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
+				RequiredDeploymentEnvironments: []string{"test"},
+			}),
+			NewRequiredSignaturesRule(),
+			NewPullRequestRule(&PullRequestRuleParameters{
+				RequireCodeOwnerReview:         true,
+				RequireLastPushApproval:        true,
+				RequiredApprovingReviewCount:   1,
+				RequiredReviewThreadResolution: true,
+				DismissStaleReviewsOnPush:      true,
+			}),
+			NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{
+				RequiredStatusChecks: []RuleRequiredStatusChecks{
+					{
+						Context:       "test",
+						IntegrationID: Int64(1),
+					},
+				},
+				StrictRequiredStatusChecksPolicy: true,
+			}),
+			NewNonFastForwardRule(),
+			NewCommitMessagePatternRule(&RulePatternParameters{
+				Name:     String("avoid test commits"),
+				Negate:   Bool(true),
+				Operator: "starts_with",
+				Pattern:  "[test]",
+			}),
+			NewCommitAuthorEmailPatternRule(&RulePatternParameters{
+				Operator: "contains",
+				Pattern:  "github",
+			}),
+			NewCommitterEmailPatternRule(&RulePatternParameters{
+				Name:     String("avoid commit emails"),
+				Negate:   Bool(true),
+				Operator: "ends_with",
+				Pattern:  "abc",
+			}),
+			NewBranchNamePatternRule(&RulePatternParameters{
+				Name:     String("avoid branch names"),
+				Negate:   Bool(true),
+				Operator: "regex",
+				Pattern:  "github$",
+			}),
+			NewTagNamePatternRule(&RulePatternParameters{
+				Name:     String("avoid tag names"),
+				Negate:   Bool(true),
+				Operator: "contains",
+				Pattern:  "github",
+			}),
+		},
+	}
+	if !cmp.Equal(ruleset, want) {
+		t.Errorf("Organizations.CreateOrganizationRuleset returned %+v, want %+v", ruleset, want)
+	}
+
+	const methodName = "CreateOrganizationRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_GetOrganizationRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/rulesets/26110", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{
+			"id": 26110,
+			"name": "test ruleset",
+			"target": "branch",
+			"source_type": "Organization",
+			"source": "o",
+			"enforcement": "active",
+			"bypass_mode": "none",
+			"node_id": "nid",
+			"_links": {
+			  "self": {
+				"href": "https://api.github.com/orgs/o/rulesets/26110"
+			  }
+			},
+			"conditions": {
+				"ref_name": {
+				  "include": [
+					"refs/heads/main",
+					"refs/heads/master"
+				  ],
+				  "exclude": [
+					"refs/heads/dev*"
+				  ]
+				},
+				"repository_name": {
+				  "include": [
+					"important_repository",
+					"another_important_repository"
+				  ],
+				  "exclude": [
+					"unimportant_repository"
+				  ],
+				  "protected": true
+				}
+			  },
+			  "rules": [
+				{
+				  "type": "creation"
+				}
+			  ]
+		}`)
+	})
+
+	ctx := context.Background()
+	rulesets, _, err := client.Organizations.GetOrganizationRuleset(ctx, "o", 26110)
+	if err != nil {
+		t.Errorf("Organizations.GetOrganizationRepositoryRuleset returned error: %v", err)
+	}
+
+	want := &Ruleset{
+		ID:          26110,
+		Name:        "test ruleset",
+		Target:      String("branch"),
+		SourceType:  String("Organization"),
+		Source:      "o",
+		Enforcement: "active",
+		BypassMode:  String("none"),
+		NodeID:      String("nid"),
+		Links: &RulesetLinks{
+			Self: &RulesetLink{HRef: String("https://api.github.com/orgs/o/rulesets/26110")},
+		},
+		Conditions: &RulesetConditions{
+			RefName: &RulesetRefConditionParameters{
+				Include: []string{"refs/heads/main", "refs/heads/master"},
+				Exclude: []string{"refs/heads/dev*"},
+			},
+			RepositoryName: &RulesetRepositoryConditionParameters{
+				Include:   []string{"important_repository", "another_important_repository"},
+				Exclude:   []string{"unimportant_repository"},
+				Protected: Bool(true),
+			},
+		},
+		Rules: []*RepositoryRule{
+			NewCreationRule(),
+		},
+	}
+	if !cmp.Equal(rulesets, want) {
+		t.Errorf("Organizations.GetOrganizationRuleset returned %+v, want %+v", rulesets, want)
+	}
+
+	const methodName = "GetOrganizationRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetOrganizationRuleset(ctx, "o", 26110)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/rulesets/26110", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		fmt.Fprint(w, `{
+			"id": 26110,
+			"name": "test ruleset",
+			"target": "branch",
+			"source_type": "Organization",
+			"source": "o",
+			"enforcement": "active",
+			"bypass_mode": "none",
+			"node_id": "nid",
+			"_links": {
+			  "self": {
+				"href": "https://api.github.com/orgs/o/rulesets/26110"
+			  }
+			},
+			"conditions": {
+				"ref_name": {
+				  "include": [
+					"refs/heads/main",
+					"refs/heads/master"
+				  ],
+				  "exclude": [
+					"refs/heads/dev*"
+				  ]
+				},
+				"repository_name": {
+				  "include": [
+					"important_repository",
+					"another_important_repository"
+				  ],
+				  "exclude": [
+					"unimportant_repository"
+				  ],
+				  "protected": true
+				}
+			  },
+			  "rules": [
+				{
+				  "type": "creation"
+				}
+			  ]
+		}`)
+	})
+
+	ctx := context.Background()
+	rulesets, _, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, &Ruleset{
+		Name:        "test ruleset",
+		Target:      String("branch"),
+		Enforcement: "active",
+		BypassMode:  String("none"),
+		Conditions: &RulesetConditions{
+			RefName: &RulesetRefConditionParameters{
+				Include: []string{"refs/heads/main", "refs/heads/master"},
+				Exclude: []string{"refs/heads/dev*"},
+			},
+			RepositoryName: &RulesetRepositoryConditionParameters{
+				Include:   []string{"important_repository", "another_important_repository"},
+				Exclude:   []string{"unimportant_repository"},
+				Protected: Bool(true),
+			},
+		},
+		Rules: []*RepositoryRule{
+			NewCreationRule(),
+		},
+	})
+
+	if err != nil {
+		t.Errorf("Organizations.UpdateOrganizationRuleset returned error: %v", err)
+	}
+
+	want := &Ruleset{
+		ID:          26110,
+		Name:        "test ruleset",
+		Target:      String("branch"),
+		SourceType:  String("Organization"),
+		Source:      "o",
+		Enforcement: "active",
+		BypassMode:  String("none"),
+		NodeID:      String("nid"),
+		Links: &RulesetLinks{
+			Self: &RulesetLink{HRef: String("https://api.github.com/orgs/o/rulesets/26110")},
+		},
+		Conditions: &RulesetConditions{
+			RefName: &RulesetRefConditionParameters{
+				Include: []string{"refs/heads/main", "refs/heads/master"},
+				Exclude: []string{"refs/heads/dev*"},
+			},
+			RepositoryName: &RulesetRepositoryConditionParameters{
+				Include:   []string{"important_repository", "another_important_repository"},
+				Exclude:   []string{"unimportant_repository"},
+				Protected: Bool(true),
+			},
+		},
+		Rules: []*RepositoryRule{
+			NewCreationRule(),
+		},
+	}
+	if !cmp.Equal(rulesets, want) {
+		t.Errorf("Organizations.UpdateOrganizationRuleset returned %+v, want %+v", rulesets, want)
+	}
+
+	const methodName = "UpdateOrganizationRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_DeleteOrganizationRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/rulesets/26110", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	ctx := context.Background()
+	_, err := client.Organizations.DeleteOrganizationRuleset(ctx, "o", 26110)
+	if err != nil {
+		t.Errorf("Organizations.DeleteOrganizationRuleset returned error: %v", err)
+	}
+
+	const methodName = "DeleteOrganizationRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Organizations.DeleteOrganizationRuleset(ctx, "0", 26110)
+	})
+}
diff --git a/github/repos_contents_test.go b/github/repos_contents_test.go
index 29262ce2..295c2168 100644
--- a/github/repos_contents_test.go
+++ b/github/repos_contents_test.go
@@ -465,6 +465,20 @@ func TestRepositoriesService_GetContents_DirectoryWithSpaces(t *testing.T) {
 	}
 }
 
+func TestRepositoriesService_GetContents_PathWithParent(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/contents/some/../directory/file.go", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{}`)
+	})
+	ctx := context.Background()
+	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", "some/../directory/file.go", &RepositoryContentGetOptions{})
+	if err == nil {
+		t.Fatal("Repositories.GetContents expected error but got none")
+	}
+}
+
 func TestRepositoriesService_GetContents_DirectoryWithPlusChars(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/repos_rules_test.go b/github/repos_rules_test.go
new file mode 100644
index 00000000..42c7a6e2
--- /dev/null
+++ b/github/repos_rules_test.go
@@ -0,0 +1,510 @@
+// 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 TestRepositoryRule_UnmarshalJSON(t *testing.T) {
+	tests := map[string]struct {
+		data    string
+		want    *RepositoryRule
+		wantErr bool
+	}{
+		"Invalid JSON": {
+			data: `{`,
+			want: &RepositoryRule{
+				Type:       "",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid creation": {
+			data: `{"type":"creation"}`,
+			want: NewCreationRule(),
+		},
+		"Valid deletion": {
+			data: `{"type":"deletion"}`,
+			want: &RepositoryRule{
+				Type:       "deletion",
+				Parameters: nil,
+			},
+		},
+		"Valid required_linear_history": {
+			data: `{"type":"required_linear_history"}`,
+			want: &RepositoryRule{
+				Type:       "required_linear_history",
+				Parameters: nil,
+			},
+		},
+		"Valid required_signatures": {
+			data: `{"type":"required_signatures"}`,
+			want: &RepositoryRule{
+				Type:       "required_signatures",
+				Parameters: nil,
+			},
+		},
+		"Valid non_fast_forward": {
+			data: `{"type":"non_fast_forward"}`,
+			want: &RepositoryRule{
+				Type:       "non_fast_forward",
+				Parameters: nil,
+			},
+		},
+		"Valid update params": {
+			data: `{"type":"update","parameters":{"update_allows_fetch_and_merge":true}}`,
+			want: NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{UpdateAllowsFetchAndMerge: true}),
+		},
+		"Invalid update params": {
+			data: `{"type":"update","parameters":{"update_allows_fetch_and_merge":"true"}}`,
+			want: &RepositoryRule{
+				Type:       "update",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid required_deployments params": {
+			data: `{"type":"required_deployments","parameters":{"required_deployment_environments":["test"]}}`,
+			want: NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
+				RequiredDeploymentEnvironments: []string{"test"},
+			}),
+		},
+		"Invalid required_deployments params": {
+			data: `{"type":"required_deployments","parameters":{"required_deployment_environments":true}}`,
+			want: &RepositoryRule{
+				Type:       "required_deployments",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid commit_message_pattern params": {
+			data: `{"type":"commit_message_pattern","parameters":{"operator":"starts_with","pattern":"github"}}`,
+			want: NewCommitMessagePatternRule(&RulePatternParameters{
+				Operator: "starts_with",
+				Pattern:  "github",
+			}),
+		},
+		"Invalid commit_message_pattern params": {
+			data: `{"type":"commit_message_pattern","parameters":{"operator":"starts_with","pattern":1}}`,
+			want: &RepositoryRule{
+				Type:       "commit_message_pattern",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid commit_author_email_pattern params": {
+			data: `{"type":"commit_author_email_pattern","parameters":{"operator":"starts_with","pattern":"github"}}`,
+			want: NewCommitAuthorEmailPatternRule(&RulePatternParameters{
+				Operator: "starts_with",
+				Pattern:  "github",
+			}),
+		},
+		"Invalid commit_author_email_pattern params": {
+			data: `{"type":"commit_author_email_pattern","parameters":{"operator":"starts_with","pattern":1}}`,
+			want: &RepositoryRule{
+				Type:       "commit_author_email_pattern",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid committer_email_pattern params": {
+			data: `{"type":"committer_email_pattern","parameters":{"operator":"starts_with","pattern":"github"}}`,
+			want: NewCommitterEmailPatternRule(&RulePatternParameters{
+				Operator: "starts_with",
+				Pattern:  "github",
+			}),
+		},
+		"Invalid committer_email_pattern params": {
+			data: `{"type":"committer_email_pattern","parameters":{"operator":"starts_with","pattern":1}}`,
+			want: &RepositoryRule{
+				Type:       "committer_email_pattern",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid branch_name_pattern params": {
+			data: `{"type":"branch_name_pattern","parameters":{"operator":"starts_with","pattern":"github"}}`,
+			want: NewBranchNamePatternRule(&RulePatternParameters{
+				Operator: "starts_with",
+				Pattern:  "github",
+			}),
+		},
+		"Invalid branch_name_pattern params": {
+			data: `{"type":"branch_name_pattern","parameters":{"operator":"starts_with","pattern":1}}`,
+			want: &RepositoryRule{
+				Type:       "branch_name_pattern",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid tag_name_pattern params": {
+			data: `{"type":"tag_name_pattern","parameters":{"operator":"starts_with","pattern":"github"}}`,
+			want: NewTagNamePatternRule(&RulePatternParameters{
+				Operator: "starts_with",
+				Pattern:  "github",
+			}),
+		},
+		"Invalid tag_name_pattern params": {
+			data: `{"type":"tag_name_pattern","parameters":{"operator":"starts_with","pattern":1}}`,
+			want: &RepositoryRule{
+				Type:       "tag_name_pattern",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid pull_request params": {
+			data: `{
+				"type":"pull_request",
+				"parameters":{
+					"dismiss_stale_reviews_on_push": true,
+					"require_code_owner_review": true,
+					"require_last_push_approval": true,
+					"required_approving_review_count": 1,
+					"required_review_thread_resolution":true
+				}
+			}`,
+			want: NewPullRequestRule(&PullRequestRuleParameters{
+				DismissStaleReviewsOnPush:      true,
+				RequireCodeOwnerReview:         true,
+				RequireLastPushApproval:        true,
+				RequiredApprovingReviewCount:   1,
+				RequiredReviewThreadResolution: true,
+			}),
+		},
+		"Invalid pull_request params": {
+			data: `{"type":"pull_request","parameters": {"dismiss_stale_reviews_on_push":"true"}}`,
+			want: &RepositoryRule{
+				Type:       "pull_request",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid required_status_checks params": {
+			data: `{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test","integration_id":1}],"strict_required_status_checks_policy":true}}`,
+			want: NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{
+				RequiredStatusChecks: []RuleRequiredStatusChecks{
+					{
+						Context:       "test",
+						IntegrationID: Int64(1),
+					},
+				},
+				StrictRequiredStatusChecksPolicy: true,
+			}),
+		},
+		"Invalid required_status_checks params": {
+			data: `{"type":"required_status_checks",
+			"parameters": {
+				"required_status_checks": [
+				  {
+					"context": 1
+				  }
+				]
+			  }}`,
+			want: &RepositoryRule{
+				Type:       "required_status_checks",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Invalid type": {
+			data: `{"type":"unknown"}`,
+			want: &RepositoryRule{
+				Type:       "",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+	}
+
+	for name, tc := range tests {
+		rule := &RepositoryRule{}
+
+		t.Run(name, func(t *testing.T) {
+			err := rule.UnmarshalJSON([]byte(tc.data))
+			if err == nil && tc.wantErr {
+				t.Errorf("RepositoryRule.UnmarshalJSON returned nil instead of an error")
+			}
+			if err != nil && !tc.wantErr {
+				t.Errorf("RepositoryRule.UnmarshalJSON returned an unexpected error: %+v", err)
+			}
+			if !cmp.Equal(tc.want, rule) {
+				t.Errorf("RepositoryRule.UnmarshalJSON expected rule %+v, got %+v", tc.want, rule)
+			}
+		})
+	}
+}
+
+func TestRepositoriesService_GetRulesForBranch(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/repo/rules/branches/branch", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[
+			{
+			  "type": "creation"
+			},
+			{
+			  "type": "update",
+			  "parameters": {
+			    "update_allows_fetch_and_merge": true
+			  }
+			}
+		]`)
+	})
+
+	ctx := context.Background()
+	rules, _, err := client.Repositories.GetRulesForBranch(ctx, "o", "repo", "branch")
+	if err != nil {
+		t.Errorf("Repositories.GetRulesForBranch returned error: %v", err)
+	}
+
+	creationRule := NewCreationRule()
+	updateRule := NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{
+		UpdateAllowsFetchAndMerge: true,
+	})
+
+	want := []*RepositoryRule{
+		creationRule,
+		updateRule,
+	}
+	if !cmp.Equal(rules, want) {
+		t.Errorf("Repositories.GetRulesForBranch returned %+v, want %+v", rules, want)
+	}
+
+	const methodName = "GetRulesForBranch"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.GetRulesForBranch(ctx, "o", "repo", "branch")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_GetAllRulesets(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/repo/rulesets", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[
+			{
+			  "id": 42,
+			  "name": "ruleset",
+			  "source_type": "Repository",
+			  "source": "o/repo",
+			  "enforcement": "enabled"
+			},
+			{
+			  "id": 314,
+			  "name": "Another ruleset",
+			  "source_type": "Repository",
+			  "source": "o/repo",
+			  "enforcement": "enabled"
+			}
+		]`)
+	})
+
+	ctx := context.Background()
+	ruleSet, _, err := client.Repositories.GetAllRulesets(ctx, "o", "repo", false)
+	if err != nil {
+		t.Errorf("Repositories.GetAllRulesets returned error: %v", err)
+	}
+
+	want := []*Ruleset{
+		{
+			ID:          42,
+			Name:        "ruleset",
+			SourceType:  String("Repository"),
+			Source:      "o/repo",
+			Enforcement: "enabled",
+		},
+		{
+			ID:          314,
+			Name:        "Another ruleset",
+			SourceType:  String("Repository"),
+			Source:      "o/repo",
+			Enforcement: "enabled",
+		},
+	}
+	if !cmp.Equal(ruleSet, want) {
+		t.Errorf("Repositories.GetAllRulesets returned %+v, want %+v", ruleSet, want)
+	}
+
+	const methodName = "GetAllRulesets"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.GetAllRulesets(ctx, "o", "repo", false)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_CreateRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/repo/rulesets", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{
+			"id": 42,
+			"name": "ruleset",
+			"source_type": "Repository",
+			"source": "o/repo",
+			"enforcement": "enabled"
+		}`)
+	})
+
+	ctx := context.Background()
+	ruleSet, _, err := client.Repositories.CreateRuleset(ctx, "o", "repo", &Ruleset{
+		Name:        "ruleset",
+		Enforcement: "enabled",
+	})
+	if err != nil {
+		t.Errorf("Repositories.CreateRuleset returned error: %v", err)
+	}
+
+	want := &Ruleset{
+		ID:          42,
+		Name:        "ruleset",
+		SourceType:  String("Repository"),
+		Source:      "o/repo",
+		Enforcement: "enabled",
+	}
+	if !cmp.Equal(ruleSet, want) {
+		t.Errorf("Repositories.CreateRuleset returned %+v, want %+v", ruleSet, want)
+	}
+
+	const methodName = "CreateRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.CreateRuleset(ctx, "o", "repo", &Ruleset{})
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_GetRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/repo/rulesets/42", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{
+			"id": 42,
+			"name": "ruleset",
+			"source_type": "Organization",
+			"source": "o",
+			"enforcement": "enabled"
+		}`)
+	})
+
+	ctx := context.Background()
+	ruleSet, _, err := client.Repositories.GetRuleset(ctx, "o", "repo", 42, true)
+	if err != nil {
+		t.Errorf("Repositories.GetRuleset returned error: %v", err)
+	}
+
+	want := &Ruleset{
+		ID:          42,
+		Name:        "ruleset",
+		SourceType:  String("Organization"),
+		Source:      "o",
+		Enforcement: "enabled",
+	}
+	if !cmp.Equal(ruleSet, want) {
+		t.Errorf("Repositories.GetRuleset returned %+v, want %+v", ruleSet, want)
+	}
+
+	const methodName = "GetRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.GetRuleset(ctx, "o", "repo", 42, true)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_UpdateRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/repo/rulesets/42", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		fmt.Fprint(w, `{
+			"id": 42,
+			"name": "ruleset",
+			"source_type": "Repository",
+			"source": "o/repo",
+			"enforcement": "enabled"
+		}`)
+	})
+
+	ctx := context.Background()
+	ruleSet, _, err := client.Repositories.UpdateRuleset(ctx, "o", "repo", 42, &Ruleset{
+		Name:        "ruleset",
+		Enforcement: "enabled",
+	})
+	if err != nil {
+		t.Errorf("Repositories.UpdateRuleset returned error: %v", err)
+	}
+
+	want := &Ruleset{
+		ID:          42,
+		Name:        "ruleset",
+		SourceType:  String("Repository"),
+		Source:      "o/repo",
+		Enforcement: "enabled",
+	}
+	if !cmp.Equal(ruleSet, want) {
+		t.Errorf("Repositories.UpdateRuleset returned %+v, want %+v", ruleSet, want)
+	}
+
+	const methodName = "UpdateRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.UpdateRuleset(ctx, "o", "repo", 42, nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_DeleteRuleset(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/repo/rulesets/42", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	ctx := context.Background()
+	_, err := client.Repositories.DeleteRuleset(ctx, "o", "repo", 42)
+	if err != nil {
+		t.Errorf("Repositories.DeleteRuleset returned error: %v", err)
+	}
+
+	const methodName = "DeleteRuleset"
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Repositories.DeleteRuleset(ctx, "o", "repo", 42)
+	})
+}