Repository: google/go-github
Pull request #2825 (2023-07-03 11:42:51)

Support repository_id in org ruleset conditions
This PR adds support for referencing repositories by ID in Ruleset conditions.
See https://docs.github.com/en/rest/orgs/rules?apiVersion=2022-11-28#create-an-organization-repository-ruleset

API is changed a bit, probably a low impact since v53.2.0 (with ruleset support added) was released just recently and hopefully not many have code using it in production.

The change `RulesetRepositoryConditionParameters` -> `RulesetRepositoryNamesConditionParameters`

I'm open for suggestions for a change that would not shake the API.

google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/google/go-github/pull/2825/checks?check_run_id=14736513295) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/2825?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2825](https://app.codecov.io/gh/google/go-github/pull/2825?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (062975e) into [master](https://app.codecov.io/gh/google/go-github/commit/96726d8192f34c0d46e7b64b3c400962e8e64898?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (96726d8) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##           master    #2825   +/-   ##
=======================================
  Coverage   98.06%   98.06%           
=======================================
  Files         136      136           
  Lines       12279    12279           
=======================================
  Hits        12041    12041           
  Misses        162      162           
  Partials       76       76           
```


| [Impacted Files](https://app.codecov.io/gh/google/go-github/pull/2825?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/2825?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL3JlcG9zX3J1bGVzLmdv) | `100.00% <ø> (ø)` | |
| [github/secret\_scanning.go](https://app.codecov.io/gh/google/go-github/pull/2825?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL3NlY3JldF9zY2FubmluZy5nbw==) | `100.00% <ø> (ø)` | |


gmlewis: Please also update the unit tests so that `go test ./...` passes on your local machine.
ysoldak: 🤦 PTAL
ysoldak: Thanks, @gmlewis

I thought worth to reference GitHub's own statement about API change:
https://github.blog/changelog/2023-06-27-repository-rules-public-beta-updates/

The part this PR addresses is "Added a repository picker to target select repositories for organization rulesets."

Patch:
diff --git a/github/event.go b/github/event.go
index 4ee25603..4ee60e33 100644
--- a/github/event.go
+++ b/github/event.go
@@ -93,6 +93,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
 		payload = &PackageEvent{}
 	case "PageBuildEvent":
 		payload = &PageBuildEvent{}
+	case "PersonalAccessTokenRequestEvent":
+		payload = &PersonalAccessTokenRequestEvent{}
 	case "PingEvent":
 		payload = &PingEvent{}
 	case "ProjectEvent":
diff --git a/github/event_types.go b/github/event_types.go
index 6a13b286..fca897af 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -774,6 +774,74 @@ type PageBuildEvent struct {
 	Installation *Installation `json:"installation,omitempty"`
 }
 
+// PersonalAccessTokenRequestEvent occurs when there is activity relating to a
+// request for a fine-grained personal access token to access resources that
+// belong to a resource owner that requires approval for token access.
+// The webhook event name is "personal_access_token_request".
+//
+// GitHub API docs: https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#personal_access_token_request
+type PersonalAccessTokenRequestEvent struct {
+	// Action is the action that was performed. Possible values are:
+	// "approved", "cancelled", "created" or "denied"
+	Action                     *string                     `json:"action,omitempty"`
+	PersonalAccessTokenRequest *PersonalAccessTokenRequest `json:"personal_access_token_request,omitempty"`
+	Org                        *Organization               `json:"organization,omitempty"`
+	Sender                     *User                       `json:"sender,omitempty"`
+	Installation               *Installation               `json:"installation,omitempty"`
+}
+
+// PersonalAccessTokenRequest contains the details of a PersonalAccessTokenRequestEvent.
+type PersonalAccessTokenRequest struct {
+	// Unique identifier of the request for access via fine-grained personal
+	// access token. Used as the pat_request_id parameter in the list and review
+	// API calls.
+	ID    *int64 `json:"id,omitempty"`
+	Owner *User  `json:"owner,omitempty"`
+
+	// New requested permissions, categorized by type of permission.
+	PermissionsAdded *PersonalAccessTokenPermissions `json:"permissions_added,omitempty"`
+
+	// Requested permissions that elevate access for a previously approved
+	// request for access, categorized by type of permission.
+	PermissionsUpgraded *PersonalAccessTokenPermissions `json:"permissions_upgraded,omitempty"`
+
+	// Permissions requested, categorized by type of permission.
+	// This field incorporates permissions_added and permissions_upgraded.
+	PermissionsResult *PersonalAccessTokenPermissions `json:"permissions_result,omitempty"`
+
+	// Type of repository selection requested. Possible values are:
+	// "none", "all" or "subset"
+	RepositorySelection *string `json:"repository_selection,omitempty"`
+
+	// The number of repositories the token is requesting access to.
+	// This field is only populated when repository_selection is subset.
+	RepositoryCount *int64 `json:"repository_count,omitempty"`
+
+	// An array of repository objects the token is requesting access to.
+	// This field is only populated when repository_selection is subset.
+	Repositories []*Repository `json:"repositories,omitempty"`
+
+	// Date and time when the request for access was created.
+	CreatedAt *Timestamp `json:"created_at,omitempty"`
+
+	// Whether the associated fine-grained personal access token has expired.
+	TokenExpired *bool `json:"token_expired,omitempty"`
+
+	// Date and time when the associated fine-grained personal access token expires.
+	TokenExpiresAt *Timestamp `json:"token_expires_at,omitempty"`
+
+	// Date and time when the associated fine-grained personal access token was last used for authentication.
+	TokenLastUsedAt *Timestamp `json:"token_last_used_at,omitempty"`
+}
+
+// PersonalAccessTokenPermissions represents the original or newly requested
+// scope of permissions for a fine-grained personal access token within a PersonalAccessTokenRequest.
+type PersonalAccessTokenPermissions struct {
+	Org   map[string]string `json:"organization,omitempty"`
+	Repo  map[string]string `json:"repository,omitempty"`
+	Other map[string]string `json:"other,omitempty"`
+}
+
 // PingEvent is triggered when a Webhook is added to GitHub.
 //
 // GitHub API docs: https://developer.github.com/webhooks/#ping-event
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 41cd582c..009b1beb 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -13134,6 +13134,158 @@ func (p *PagesUpdate) GetSource() *PagesSource {
 	return p.Source
 }
 
+// GetOrg returns the Org map if it's non-nil, an empty map otherwise.
+func (p *PersonalAccessTokenPermissions) GetOrg() map[string]string {
+	if p == nil || p.Org == nil {
+		return map[string]string{}
+	}
+	return p.Org
+}
+
+// GetOther returns the Other map if it's non-nil, an empty map otherwise.
+func (p *PersonalAccessTokenPermissions) GetOther() map[string]string {
+	if p == nil || p.Other == nil {
+		return map[string]string{}
+	}
+	return p.Other
+}
+
+// GetRepo returns the Repo map if it's non-nil, an empty map otherwise.
+func (p *PersonalAccessTokenPermissions) GetRepo() map[string]string {
+	if p == nil || p.Repo == nil {
+		return map[string]string{}
+	}
+	return p.Repo
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetCreatedAt() Timestamp {
+	if p == nil || p.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *p.CreatedAt
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetID() int64 {
+	if p == nil || p.ID == nil {
+		return 0
+	}
+	return *p.ID
+}
+
+// GetOwner returns the Owner field.
+func (p *PersonalAccessTokenRequest) GetOwner() *User {
+	if p == nil {
+		return nil
+	}
+	return p.Owner
+}
+
+// GetPermissionsAdded returns the PermissionsAdded field.
+func (p *PersonalAccessTokenRequest) GetPermissionsAdded() *PersonalAccessTokenPermissions {
+	if p == nil {
+		return nil
+	}
+	return p.PermissionsAdded
+}
+
+// GetPermissionsResult returns the PermissionsResult field.
+func (p *PersonalAccessTokenRequest) GetPermissionsResult() *PersonalAccessTokenPermissions {
+	if p == nil {
+		return nil
+	}
+	return p.PermissionsResult
+}
+
+// GetPermissionsUpgraded returns the PermissionsUpgraded field.
+func (p *PersonalAccessTokenRequest) GetPermissionsUpgraded() *PersonalAccessTokenPermissions {
+	if p == nil {
+		return nil
+	}
+	return p.PermissionsUpgraded
+}
+
+// GetRepositoryCount returns the RepositoryCount field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetRepositoryCount() int64 {
+	if p == nil || p.RepositoryCount == nil {
+		return 0
+	}
+	return *p.RepositoryCount
+}
+
+// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetRepositorySelection() string {
+	if p == nil || p.RepositorySelection == nil {
+		return ""
+	}
+	return *p.RepositorySelection
+}
+
+// GetTokenExpired returns the TokenExpired field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetTokenExpired() bool {
+	if p == nil || p.TokenExpired == nil {
+		return false
+	}
+	return *p.TokenExpired
+}
+
+// GetTokenExpiresAt returns the TokenExpiresAt field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetTokenExpiresAt() Timestamp {
+	if p == nil || p.TokenExpiresAt == nil {
+		return Timestamp{}
+	}
+	return *p.TokenExpiresAt
+}
+
+// GetTokenLastUsedAt returns the TokenLastUsedAt field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequest) GetTokenLastUsedAt() Timestamp {
+	if p == nil || p.TokenLastUsedAt == nil {
+		return Timestamp{}
+	}
+	return *p.TokenLastUsedAt
+}
+
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessTokenRequestEvent) GetAction() string {
+	if p == nil || p.Action == nil {
+		return ""
+	}
+	return *p.Action
+}
+
+// GetInstallation returns the Installation field.
+func (p *PersonalAccessTokenRequestEvent) GetInstallation() *Installation {
+	if p == nil {
+		return nil
+	}
+	return p.Installation
+}
+
+// GetOrg returns the Org field.
+func (p *PersonalAccessTokenRequestEvent) GetOrg() *Organization {
+	if p == nil {
+		return nil
+	}
+	return p.Org
+}
+
+// GetPersonalAccessTokenRequest returns the PersonalAccessTokenRequest field.
+func (p *PersonalAccessTokenRequestEvent) GetPersonalAccessTokenRequest() *PersonalAccessTokenRequest {
+	if p == nil {
+		return nil
+	}
+	return p.PersonalAccessTokenRequest
+}
+
+// GetSender returns the Sender field.
+func (p *PersonalAccessTokenRequestEvent) GetSender() *User {
+	if p == nil {
+		return nil
+	}
+	return p.Sender
+}
+
 // GetHook returns the Hook field.
 func (p *PingEvent) GetHook() *Hook {
 	if p == nil {
@@ -18806,6 +18958,14 @@ func (r *ReviewersRequest) GetNodeID() string {
 	return *r.NodeID
 }
 
+// GetReason returns the Reason field if it's non-nil, zero value otherwise.
+func (r *ReviewPersonalAccessTokenRequestOptions) GetReason() string {
+	if r == nil || r.Reason == nil {
+		return ""
+	}
+	return *r.Reason
+}
+
 // GetDescription returns the Description field if it's non-nil, zero value otherwise.
 func (r *Rule) GetDescription() string {
 	if r == nil || r.Description == nil {
@@ -18942,8 +19102,16 @@ func (r *RulesetConditions) GetRefName() *RulesetRefConditionParameters {
 	return r.RefName
 }
 
+// GetRepositoryID returns the RepositoryID field.
+func (r *RulesetConditions) GetRepositoryID() *RulesetRepositoryIDsConditionParameters {
+	if r == nil {
+		return nil
+	}
+	return r.RepositoryID
+}
+
 // GetRepositoryName returns the RepositoryName field.
-func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryConditionParameters {
+func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryNamesConditionParameters {
 	if r == nil {
 		return nil
 	}
@@ -18967,7 +19135,7 @@ func (r *RulesetLinks) GetSelf() *RulesetLink {
 }
 
 // GetProtected returns the Protected field if it's non-nil, zero value otherwise.
-func (r *RulesetRepositoryConditionParameters) GetProtected() bool {
+func (r *RulesetRepositoryNamesConditionParameters) GetProtected() bool {
 	if r == nil || r.Protected == nil {
 		return false
 	}
diff --git a/github/messages.go b/github/messages.go
index bb5ae3f3..e61ef1c0 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -76,6 +76,7 @@ var (
 		"org_block":                      "OrgBlockEvent",
 		"package":                        "PackageEvent",
 		"page_build":                     "PageBuildEvent",
+		"personal_access_token_request":  "PersonalAccessTokenRequestEvent",
 		"ping":                           "PingEvent",
 		"project":                        "ProjectEvent",
 		"project_card":                   "ProjectCardEvent",
diff --git a/github/orgs_personal_access_tokens.go b/github/orgs_personal_access_tokens.go
new file mode 100644
index 00000000..c30ff284
--- /dev/null
+++ b/github/orgs_personal_access_tokens.go
@@ -0,0 +1,34 @@
+// 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"
+)
+
+// ReviewPersonalAccessTokenRequestOptions specifies the parameters to the ReviewPersonalAccessTokenRequest method.
+type ReviewPersonalAccessTokenRequestOptions struct {
+	Action string  `json:"action"`
+	Reason *string `json:"reason,omitempty"`
+}
+
+// ReviewPersonalAccessTokenRequest approves or denies a pending request to access organization resources via a fine-grained personal access token.
+// Only GitHub Apps can call this API, using the `organization_personal_access_token_requests: write` permission.
+// `action` can be one of `approve` or `deny`.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/personal-access-tokens?apiVersion=2022-11-28#review-a-request-to-access-organization-resources-with-a-fine-grained-personal-access-token
+func (s *OrganizationsService) ReviewPersonalAccessTokenRequest(ctx context.Context, org string, requestID int64, opts ReviewPersonalAccessTokenRequestOptions) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/personal-access-token-requests/%v", org, requestID)
+
+	req, err := s.client.NewRequest(http.MethodPost, u, &opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/github/repos_rules.go b/github/repos_rules.go
index 9299d3e7..ee9c82e7 100644
--- a/github/repos_rules.go
+++ b/github/repos_rules.go
@@ -34,17 +34,24 @@ type RulesetRefConditionParameters struct {
 	Exclude []string `json:"exclude"`
 }
 
-// RulesetRepositoryConditionParameters represents the conditions object for repository_names.
-type RulesetRepositoryConditionParameters struct {
+// RulesetRepositoryNamesConditionParameters represents the conditions object for repository_names.
+type RulesetRepositoryNamesConditionParameters struct {
 	Include   []string `json:"include,omitempty"`
 	Exclude   []string `json:"exclude,omitempty"`
 	Protected *bool    `json:"protected,omitempty"`
 }
 
+// RulesetRepositoryIDsConditionParameters represents the conditions object for repository_ids.
+type RulesetRepositoryIDsConditionParameters struct {
+	RepositoryIDs []int64 `json:"repository_ids,omitempty"`
+}
+
 // RulesetCondition represents the conditions object in a ruleset.
+// Set either RepositoryName or RepositoryID, not both.
 type RulesetConditions struct {
-	RefName        *RulesetRefConditionParameters        `json:"ref_name,omitempty"`
-	RepositoryName *RulesetRepositoryConditionParameters `json:"repository_name,omitempty"`
+	RefName        *RulesetRefConditionParameters             `json:"ref_name,omitempty"`
+	RepositoryName *RulesetRepositoryNamesConditionParameters `json:"repository_name,omitempty"`
+	RepositoryID   *RulesetRepositoryIDsConditionParameters   `json:"repository_id,omitempty"`
 }
 
 // RulePatternParameters represents the rule pattern parameters.


Test patch:
diff --git a/github/event_types_test.go b/github/event_types_test.go
index db284036..298477fe 100644
--- a/github/event_types_test.go
+++ b/github/event_types_test.go
@@ -7089,6 +7089,80 @@ func TestPackageEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, u, want)
 }
 
+func TestPersonalAccessTokenRequestEvent_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PersonalAccessTokenRequestEvent{}, "{}")
+
+	event := &PersonalAccessTokenRequestEvent{
+		Action: String("a"),
+		PersonalAccessTokenRequest: &PersonalAccessTokenRequest{
+			ID:    Int64(1),
+			Owner: &User{Login: String("l")},
+			PermissionsAdded: &PersonalAccessTokenPermissions{
+				Org:  map[string]string{"organization_events": "read"},
+				Repo: map[string]string{"security_events": "write"},
+			},
+			CreatedAt:           &Timestamp{referenceTime},
+			TokenExpired:        Bool(false),
+			TokenExpiresAt:      &Timestamp{referenceTime},
+			TokenLastUsedAt:     &Timestamp{referenceTime},
+			RepositoryCount:     Int64(1),
+			RepositorySelection: String("rs"),
+			Repositories: []*Repository{
+				{
+					Name: String("n"),
+				},
+			},
+		},
+		Org: &Organization{Name: String("n")},
+		Sender: &User{
+			Login: String("l"),
+		},
+		Installation: &Installation{
+			ID: Int64(1),
+		},
+	}
+
+	want := `{
+		"action": "a",
+		"personal_access_token_request": {
+			"id": 1,
+			"owner": {
+				"login": "l"
+			},
+			"permissions_added": {
+				"organization": {
+					"organization_events": "read"
+				},
+				"repository": {
+					"security_events": "write"
+				}
+			},
+			"created_at": ` + referenceTimeStr + `,
+			"token_expired": false,
+			"token_expires_at": ` + referenceTimeStr + `,
+			"token_last_used_at": ` + referenceTimeStr + `,
+			"repository_count": 1,
+			"repository_selection": "rs",
+			"repositories": [
+				{
+					"name": "n"
+				}
+			]
+		},
+		"organization": {
+			"name": "n"
+		},
+		"sender": {
+			"login": "l"
+		},
+		"installation": {
+			"id": 1
+		}
+	}`
+
+	testJSONMarshal(t, event, want)
+}
+
 func TestPingEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, &PingEvent{}, "{}")
 
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 993a1248..4671670c 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -15422,6 +15422,172 @@ func TestPagesUpdate_GetSource(tt *testing.T) {
 	p.GetSource()
 }
 
+func TestPersonalAccessTokenPermissions_GetOrg(tt *testing.T) {
+	zeroValue := map[string]string{}
+	p := &PersonalAccessTokenPermissions{Org: zeroValue}
+	p.GetOrg()
+	p = &PersonalAccessTokenPermissions{}
+	p.GetOrg()
+	p = nil
+	p.GetOrg()
+}
+
+func TestPersonalAccessTokenPermissions_GetOther(tt *testing.T) {
+	zeroValue := map[string]string{}
+	p := &PersonalAccessTokenPermissions{Other: zeroValue}
+	p.GetOther()
+	p = &PersonalAccessTokenPermissions{}
+	p.GetOther()
+	p = nil
+	p.GetOther()
+}
+
+func TestPersonalAccessTokenPermissions_GetRepo(tt *testing.T) {
+	zeroValue := map[string]string{}
+	p := &PersonalAccessTokenPermissions{Repo: zeroValue}
+	p.GetRepo()
+	p = &PersonalAccessTokenPermissions{}
+	p.GetRepo()
+	p = nil
+	p.GetRepo()
+}
+
+func TestPersonalAccessTokenRequest_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PersonalAccessTokenRequest{CreatedAt: &zeroValue}
+	p.GetCreatedAt()
+	p = &PersonalAccessTokenRequest{}
+	p.GetCreatedAt()
+	p = nil
+	p.GetCreatedAt()
+}
+
+func TestPersonalAccessTokenRequest_GetID(tt *testing.T) {
+	var zeroValue int64
+	p := &PersonalAccessTokenRequest{ID: &zeroValue}
+	p.GetID()
+	p = &PersonalAccessTokenRequest{}
+	p.GetID()
+	p = nil
+	p.GetID()
+}
+
+func TestPersonalAccessTokenRequest_GetOwner(tt *testing.T) {
+	p := &PersonalAccessTokenRequest{}
+	p.GetOwner()
+	p = nil
+	p.GetOwner()
+}
+
+func TestPersonalAccessTokenRequest_GetPermissionsAdded(tt *testing.T) {
+	p := &PersonalAccessTokenRequest{}
+	p.GetPermissionsAdded()
+	p = nil
+	p.GetPermissionsAdded()
+}
+
+func TestPersonalAccessTokenRequest_GetPermissionsResult(tt *testing.T) {
+	p := &PersonalAccessTokenRequest{}
+	p.GetPermissionsResult()
+	p = nil
+	p.GetPermissionsResult()
+}
+
+func TestPersonalAccessTokenRequest_GetPermissionsUpgraded(tt *testing.T) {
+	p := &PersonalAccessTokenRequest{}
+	p.GetPermissionsUpgraded()
+	p = nil
+	p.GetPermissionsUpgraded()
+}
+
+func TestPersonalAccessTokenRequest_GetRepositoryCount(tt *testing.T) {
+	var zeroValue int64
+	p := &PersonalAccessTokenRequest{RepositoryCount: &zeroValue}
+	p.GetRepositoryCount()
+	p = &PersonalAccessTokenRequest{}
+	p.GetRepositoryCount()
+	p = nil
+	p.GetRepositoryCount()
+}
+
+func TestPersonalAccessTokenRequest_GetRepositorySelection(tt *testing.T) {
+	var zeroValue string
+	p := &PersonalAccessTokenRequest{RepositorySelection: &zeroValue}
+	p.GetRepositorySelection()
+	p = &PersonalAccessTokenRequest{}
+	p.GetRepositorySelection()
+	p = nil
+	p.GetRepositorySelection()
+}
+
+func TestPersonalAccessTokenRequest_GetTokenExpired(tt *testing.T) {
+	var zeroValue bool
+	p := &PersonalAccessTokenRequest{TokenExpired: &zeroValue}
+	p.GetTokenExpired()
+	p = &PersonalAccessTokenRequest{}
+	p.GetTokenExpired()
+	p = nil
+	p.GetTokenExpired()
+}
+
+func TestPersonalAccessTokenRequest_GetTokenExpiresAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PersonalAccessTokenRequest{TokenExpiresAt: &zeroValue}
+	p.GetTokenExpiresAt()
+	p = &PersonalAccessTokenRequest{}
+	p.GetTokenExpiresAt()
+	p = nil
+	p.GetTokenExpiresAt()
+}
+
+func TestPersonalAccessTokenRequest_GetTokenLastUsedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PersonalAccessTokenRequest{TokenLastUsedAt: &zeroValue}
+	p.GetTokenLastUsedAt()
+	p = &PersonalAccessTokenRequest{}
+	p.GetTokenLastUsedAt()
+	p = nil
+	p.GetTokenLastUsedAt()
+}
+
+func TestPersonalAccessTokenRequestEvent_GetAction(tt *testing.T) {
+	var zeroValue string
+	p := &PersonalAccessTokenRequestEvent{Action: &zeroValue}
+	p.GetAction()
+	p = &PersonalAccessTokenRequestEvent{}
+	p.GetAction()
+	p = nil
+	p.GetAction()
+}
+
+func TestPersonalAccessTokenRequestEvent_GetInstallation(tt *testing.T) {
+	p := &PersonalAccessTokenRequestEvent{}
+	p.GetInstallation()
+	p = nil
+	p.GetInstallation()
+}
+
+func TestPersonalAccessTokenRequestEvent_GetOrg(tt *testing.T) {
+	p := &PersonalAccessTokenRequestEvent{}
+	p.GetOrg()
+	p = nil
+	p.GetOrg()
+}
+
+func TestPersonalAccessTokenRequestEvent_GetPersonalAccessTokenRequest(tt *testing.T) {
+	p := &PersonalAccessTokenRequestEvent{}
+	p.GetPersonalAccessTokenRequest()
+	p = nil
+	p.GetPersonalAccessTokenRequest()
+}
+
+func TestPersonalAccessTokenRequestEvent_GetSender(tt *testing.T) {
+	p := &PersonalAccessTokenRequestEvent{}
+	p.GetSender()
+	p = nil
+	p.GetSender()
+}
+
 func TestPingEvent_GetHook(tt *testing.T) {
 	p := &PingEvent{}
 	p.GetHook()
@@ -21933,6 +22099,16 @@ func TestReviewersRequest_GetNodeID(tt *testing.T) {
 	r.GetNodeID()
 }
 
+func TestReviewPersonalAccessTokenRequestOptions_GetReason(tt *testing.T) {
+	var zeroValue string
+	r := &ReviewPersonalAccessTokenRequestOptions{Reason: &zeroValue}
+	r.GetReason()
+	r = &ReviewPersonalAccessTokenRequestOptions{}
+	r.GetReason()
+	r = nil
+	r.GetReason()
+}
+
 func TestRule_GetDescription(tt *testing.T) {
 	var zeroValue string
 	r := &Rule{Description: &zeroValue}
@@ -22094,6 +22270,13 @@ func TestRulesetConditions_GetRefName(tt *testing.T) {
 	r.GetRefName()
 }
 
+func TestRulesetConditions_GetRepositoryID(tt *testing.T) {
+	r := &RulesetConditions{}
+	r.GetRepositoryID()
+	r = nil
+	r.GetRepositoryID()
+}
+
 func TestRulesetConditions_GetRepositoryName(tt *testing.T) {
 	r := &RulesetConditions{}
 	r.GetRepositoryName()
@@ -22118,11 +22301,11 @@ func TestRulesetLinks_GetSelf(tt *testing.T) {
 	r.GetSelf()
 }
 
-func TestRulesetRepositoryConditionParameters_GetProtected(tt *testing.T) {
+func TestRulesetRepositoryNamesConditionParameters_GetProtected(tt *testing.T) {
 	var zeroValue bool
-	r := &RulesetRepositoryConditionParameters{Protected: &zeroValue}
+	r := &RulesetRepositoryNamesConditionParameters{Protected: &zeroValue}
 	r.GetProtected()
-	r = &RulesetRepositoryConditionParameters{}
+	r = &RulesetRepositoryNamesConditionParameters{}
 	r.GetProtected()
 	r = nil
 	r.GetProtected()
diff --git a/github/messages_test.go b/github/messages_test.go
index 80fb3e5f..5230b87a 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -380,6 +380,10 @@ func TestParseWebHook(t *testing.T) {
 			payload:     &PageBuildEvent{},
 			messageType: "page_build",
 		},
+		{
+			payload:     &PersonalAccessTokenRequestEvent{},
+			messageType: "personal_access_token_request",
+		},
 		{
 			payload:     &PingEvent{},
 			messageType: "ping",
diff --git a/github/orgs_personal_access_tokens_test.go b/github/orgs_personal_access_tokens_test.go
new file mode 100644
index 00000000..93ee1b3d
--- /dev/null
+++ b/github/orgs_personal_access_tokens_test.go
@@ -0,0 +1,73 @@
+// 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"
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestOrganizationsService_ReviewPersonalAccessTokenRequest(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	input := ReviewPersonalAccessTokenRequestOptions{
+		Action: "a",
+		Reason: String("r"),
+	}
+
+	mux.HandleFunc("/orgs/o/personal-access-token-requests/1", func(w http.ResponseWriter, r *http.Request) {
+		v := new(ReviewPersonalAccessTokenRequestOptions)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, http.MethodPost)
+		if !cmp.Equal(v, &input) {
+			t.Errorf("Request body = %+v, want %+v", v, input)
+		}
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	res, err := client.Organizations.ReviewPersonalAccessTokenRequest(ctx, "o", 1, input)
+	if err != nil {
+		t.Errorf("Organizations.ReviewPersonalAccessTokenRequest returned error: %v", err)
+	}
+
+	if res.StatusCode != http.StatusNoContent {
+		t.Errorf("Organizations.ReviewPersonalAccessTokenRequest returned %v, want %v", res.StatusCode, http.StatusNoContent)
+	}
+
+	const methodName = "ReviewPersonalAccessTokenRequest"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Organizations.ReviewPersonalAccessTokenRequest(ctx, "\n", 0, input)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Organizations.ReviewPersonalAccessTokenRequest(ctx, "o", 1, input)
+	})
+}
+
+func TestReviewPersonalAccessTokenRequestOptions_Marshal(t *testing.T) {
+	testJSONMarshal(t, &ReviewPersonalAccessTokenRequestOptions{}, "{}")
+
+	u := &ReviewPersonalAccessTokenRequestOptions{
+		Action: "a",
+		Reason: String("r"),
+	}
+
+	want := `{
+		"action": "a",
+		"reason": "r"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/orgs_rules_test.go b/github/orgs_rules_test.go
index 7719e142..48c6c5e2 100644
--- a/github/orgs_rules_test.go
+++ b/github/orgs_rules_test.go
@@ -71,7 +71,7 @@ func TestOrganizationsService_GetAllOrganizationRulesets(t *testing.T) {
 	})
 }
 
-func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) {
+func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
@@ -227,7 +227,7 @@ func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) {
 				Include: []string{"refs/heads/main", "refs/heads/master"},
 				Exclude: []string{"refs/heads/dev*"},
 			},
-			RepositoryName: &RulesetRepositoryConditionParameters{
+			RepositoryName: &RulesetRepositoryNamesConditionParameters{
 				Include:   []string{"important_repository", "another_important_repository"},
 				Exclude:   []string{"unimportant_repository"},
 				Protected: Bool(true),
@@ -313,7 +313,7 @@ func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) {
 				Include: []string{"refs/heads/main", "refs/heads/master"},
 				Exclude: []string{"refs/heads/dev*"},
 			},
-			RepositoryName: &RulesetRepositoryConditionParameters{
+			RepositoryName: &RulesetRepositoryNamesConditionParameters{
 				Include:   []string{"important_repository", "another_important_repository"},
 				Exclude:   []string{"unimportant_repository"},
 				Protected: Bool(true),
@@ -392,6 +392,316 @@ func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) {
 	})
 }
 
+func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(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_id": {
+					"repository_ids": [ 123, 456 ]
+				}
+			},
+			"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*"},
+			},
+			RepositoryID: &RulesetRepositoryIDsConditionParameters{
+				RepositoryIDs: []int64{123, 456},
+			},
+		},
+		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*"},
+			},
+			RepositoryID: &RulesetRepositoryIDsConditionParameters{
+				RepositoryIDs: []int64{123, 456},
+			},
+		},
+		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()
@@ -464,7 +774,7 @@ func TestOrganizationsService_GetOrganizationRuleset(t *testing.T) {
 				Include: []string{"refs/heads/main", "refs/heads/master"},
 				Exclude: []string{"refs/heads/dev*"},
 			},
-			RepositoryName: &RulesetRepositoryConditionParameters{
+			RepositoryName: &RulesetRepositoryNamesConditionParameters{
 				Include:   []string{"important_repository", "another_important_repository"},
 				Exclude:   []string{"unimportant_repository"},
 				Protected: Bool(true),
@@ -549,7 +859,7 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) {
 				Include: []string{"refs/heads/main", "refs/heads/master"},
 				Exclude: []string{"refs/heads/dev*"},
 			},
-			RepositoryName: &RulesetRepositoryConditionParameters{
+			RepositoryName: &RulesetRepositoryNamesConditionParameters{
 				Include:   []string{"important_repository", "another_important_repository"},
 				Exclude:   []string{"unimportant_repository"},
 				Protected: Bool(true),
@@ -581,7 +891,7 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) {
 				Include: []string{"refs/heads/main", "refs/heads/master"},
 				Exclude: []string{"refs/heads/dev*"},
 			},
-			RepositoryName: &RulesetRepositoryConditionParameters{
+			RepositoryName: &RulesetRepositoryNamesConditionParameters{
 				Include:   []string{"important_repository", "another_important_repository"},
 				Exclude:   []string{"unimportant_repository"},
 				Protected: Bool(true),