Repository: google/go-github
Pull request #2856 (2023-08-05 15:26:48)

Add support for Dependency Graph Snapshots endpoint
Starts Dependency Graph service support by starting with the [Create Snapshot](https://docs.github.com/en/rest/dependency-graph/dependency-submission?apiVersion=2022-11-28#create-a-snapshot-of-dependencies-for-a-repository) endpoint

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

```diff
@@           Coverage Diff           @@
##           master    #2856   +/-   ##
=======================================
  Coverage   98.06%   98.07%           
=======================================
  Files         138      139    +1     
  Lines       12327    12341   +14     
=======================================
+ Hits        12089    12103   +14     
  Misses        162      162           
  Partials       76       76           
```


| [Files Changed](https://app.codecov.io/gh/google/go-github/pull/2856?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/dependency\_graph\_snapshots.go](https://app.codecov.io/gh/google/go-github/pull/2856?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2RlcGVuZGVuY3lfZ3JhcGhfc25hcHNob3RzLmdv) | `100.00% <100.00%> (ø)` | |
| [github/github.go](https://app.codecov.io/gh/google/go-github/pull/2856?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `97.97% <100.00%> (+<0.01%)` | :arrow_up: |


gmlewis: @be0x74a - we'll leave this PR open for a week or two, but if we haven't heard back from you, we will close this PR as abandoned.
be0x74a: > @be0x74a - we'll leave this PR open for a week or two, but if we haven't heard back from you, we will close this PR as abandoned.

@gmlewis finally got to it! Sorry for the delay and the force-push, was trying to appease codecov and make it use a recent base but I completely failed...
be0x74a: Thanks for the input @gmlewis!

Patch:
diff --git a/README.md b/README.md
index 94e1cc54..d1cd2df1 100644
--- a/README.md
+++ b/README.md
@@ -180,6 +180,12 @@ if _, ok := err.(*github.AbuseRateLimitError); ok {
 }
 ```
 
+Alternatively, you can block until the rate limit is reset by using the `context.WithValue` method:
+
+```go
+repos, _, err := client.Repositories.List(context.WithValue(ctx, github.SleepUntilPrimaryRateLimitResetWhenRateLimited, true), "", nil)
+```
+
 You can use [go-github-ratelimit](https://github.com/gofri/go-github-ratelimit) to handle
 secondary rate limit sleep-and-retry for you.
 
diff --git a/github/actions_runners.go b/github/actions_runners.go
index c5c90279..c4ae4823 100644
--- a/github/actions_runners.go
+++ b/github/actions_runners.go
@@ -151,12 +151,18 @@ type Runners struct {
 	Runners    []*Runner `json:"runners"`
 }
 
+// ListRunnersOptions specifies the optional parameters to the ListRunners and ListOrganizationRunners methods.
+type ListRunnersOptions struct {
+	Name *string `url:"name,omitempty"`
+	ListOptions
+}
+
 // ListRunners lists all the self-hosted runners for a repository.
 //
 // GitHub API docs: https://docs.github.com/rest/actions/self-hosted-runners#list-self-hosted-runners-for-a-repository
 //
 //meta:operation GET /repos/{owner}/{repo}/actions/runners
-func (s *ActionsService) ListRunners(ctx context.Context, owner, repo string, opts *ListOptions) (*Runners, *Response, error) {
+func (s *ActionsService) ListRunners(ctx context.Context, owner, repo string, opts *ListRunnersOptions) (*Runners, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/actions/runners", owner, repo)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -290,7 +296,7 @@ func (s *ActionsService) CreateOrganizationRegistrationToken(ctx context.Context
 // GitHub API docs: https://docs.github.com/rest/actions/self-hosted-runners#list-self-hosted-runners-for-an-organization
 //
 //meta:operation GET /orgs/{org}/actions/runners
-func (s *ActionsService) ListOrganizationRunners(ctx context.Context, org string, opts *ListOptions) (*Runners, *Response, error) {
+func (s *ActionsService) ListOrganizationRunners(ctx context.Context, org string, opts *ListRunnersOptions) (*Runners, *Response, error) {
 	u := fmt.Sprintf("orgs/%v/actions/runners", org)
 	u, err := addOptions(u, opts)
 	if err != nil {
diff --git a/github/apps.go b/github/apps.go
index 2ebfee50..6969daba 100644
--- a/github/apps.go
+++ b/github/apps.go
@@ -56,6 +56,20 @@ type InstallationTokenOptions struct {
 	Permissions *InstallationPermissions `json:"permissions,omitempty"`
 }
 
+type InstallationTokenListRepoOptions struct {
+	// The IDs of the repositories that the installation token can access.
+	// Providing repository IDs restricts the access of an installation token to specific repositories.
+	RepositoryIDs []int64 `json:"repository_ids"`
+
+	// The names of the repositories that the installation token can access.
+	// Providing repository names restricts the access of an installation token to specific repositories.
+	Repositories []string `json:"repositories,omitempty"`
+
+	// The permissions granted to the access token.
+	// The permissions object includes the permission names and their access type.
+	Permissions *InstallationPermissions `json:"permissions,omitempty"`
+}
+
 // InstallationPermissions lists the repository and organization permissions for an installation.
 //
 // Permission names taken from:
@@ -344,6 +358,30 @@ func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opt
 	return t, resp, nil
 }
 
+// CreateInstallationTokenListRepos creates a new installation token with a list of all repositories in an installation which is not possible with CreateInstallationToken.
+//
+// It differs from CreateInstallationToken by taking InstallationTokenListRepoOptions as a parameter which does not omit RepositoryIDs if that field is nil or an empty array.
+//
+// GitHub API docs: https://docs.github.com/rest/apps/apps#create-an-installation-access-token-for-an-app
+//
+//meta:operation POST /app/installations/{installation_id}/access_tokens
+func (s *AppsService) CreateInstallationTokenListRepos(ctx context.Context, id int64, opts *InstallationTokenListRepoOptions) (*InstallationToken, *Response, error) {
+	u := fmt.Sprintf("app/installations/%v/access_tokens", id)
+
+	req, err := s.client.NewRequest("POST", u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(InstallationToken)
+	resp, err := s.client.Do(ctx, req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, nil
+}
+
 // CreateAttachment creates a new attachment on user comment containing a url.
 //
 // GitHub API docs: https://docs.github.com/enterprise-server@3.3/rest/reference/apps#create-a-content-attachment
diff --git a/github/dependency_graph_snapshots.go b/github/dependency_graph_snapshots.go
new file mode 100644
index 00000000..0606b981
--- /dev/null
+++ b/github/dependency_graph_snapshots.go
@@ -0,0 +1,113 @@
+// 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"
+)
+
+// DependencyGraphSnapshotResolvedDependency represents a resolved dependency in a dependency graph snapshot.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshotResolvedDependency struct {
+	PackageURL *string `json:"package_url,omitempty"`
+	// Represents whether the dependency is requested directly by the manifest or is a dependency of another dependency.
+	// Can have the following values:
+	//   - "direct": indicates that the dependency is requested directly by the manifest.
+	//   - "indirect": indicates that the dependency is a dependency of another dependency.
+	Relationship *string `json:"relationship,omitempty"`
+	// Represents whether the dependency is required for the primary build artifact or is only used for development.
+	// Can have the following values:
+	//   - "runtime": indicates that the dependency is required for the primary build artifact.
+	//   - "development": indicates that the dependency is only used for development.
+	Scope        *string  `json:"scope,omitempty"`
+	Dependencies []string `json:"dependencies,omitempty"`
+}
+
+// DependencyGraphSnapshotJob represents the job that created the snapshot.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshotJob struct {
+	Correlator *string `json:"correlator,omitempty"`
+	ID         *string `json:"id,omitempty"`
+	HTMLURL    *string `json:"html_url,omitempty"`
+}
+
+// DependencyGraphSnapshotDetector represents a description of the detector used.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshotDetector struct {
+	Name    *string `json:"name,omitempty"`
+	Version *string `json:"version,omitempty"`
+	URL     *string `json:"url,omitempty"`
+}
+
+// DependencyGraphSnapshotManifestFile represents the file declaring the repository's dependencies.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshotManifestFile struct {
+	SourceLocation *string `json:"source_location,omitempty"`
+}
+
+// DependencyGraphSnapshotManifest represents a collection of related dependencies declared in a file or representing a logical group of dependencies.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshotManifest struct {
+	Name     *string                                               `json:"name,omitempty"`
+	File     *DependencyGraphSnapshotManifestFile                  `json:"file,omitempty"`
+	Resolved map[string]*DependencyGraphSnapshotResolvedDependency `json:"resolved,omitempty"`
+}
+
+// DependencyGraphSnapshot represent a snapshot of a repository's dependencies.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshot struct {
+	Version   int                                         `json:"version"`
+	Sha       *string                                     `json:"sha,omitempty"`
+	Ref       *string                                     `json:"ref,omitempty"`
+	Job       *DependencyGraphSnapshotJob                 `json:"job,omitempty"`
+	Detector  *DependencyGraphSnapshotDetector            `json:"detector,omitempty"`
+	Scanned   *Timestamp                                  `json:"scanned,omitempty"`
+	Manifests map[string]*DependencyGraphSnapshotManifest `json:"manifests,omitempty"`
+}
+
+// DependencyGraphSnapshotCreationData represents the dependency snapshot's creation result.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+type DependencyGraphSnapshotCreationData struct {
+	ID        int64      `json:"id"`
+	CreatedAt *Timestamp `json:"created_at,omitempty"`
+	Message   *string    `json:"message,omitempty"`
+	// Represents the snapshot creation result.
+	// Can have the following values:
+	//   - "SUCCESS": indicates that the snapshot was successfully created and the repository's dependencies were updated.
+	//   - "ACCEPTED": indicates that the snapshot was successfully created, but the repository's dependencies were not updated.
+	//   - "INVALID": indicates that the snapshot was malformed.
+	Result *string `json:"result,omitempty"`
+}
+
+// CreateSnapshot creates a new snapshot of a repository's dependencies.
+//
+// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
+//
+//meta:operation POST /repos/{owner}/{repo}/dependency-graph/snapshots
+func (s *DependencyGraphService) CreateSnapshot(ctx context.Context, owner, repo string, dependencyGraphSnapshot *DependencyGraphSnapshot) (*DependencyGraphSnapshotCreationData, *Response, error) {
+	url := fmt.Sprintf("repos/%v/%v/dependency-graph/snapshots", owner, repo)
+
+	req, err := s.client.NewRequest("POST", url, dependencyGraphSnapshot)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var snapshotCreationData *DependencyGraphSnapshotCreationData
+	resp, err := s.client.Do(ctx, req, &snapshotCreationData)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return snapshotCreationData, resp, nil
+}
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 13bf7d66..3ba66c80 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -5206,6 +5206,166 @@ func (d *Dependency) GetScope() string {
 	return *d.Scope
 }
 
+// GetDetector returns the Detector field.
+func (d *DependencyGraphSnapshot) GetDetector() *DependencyGraphSnapshotDetector {
+	if d == nil {
+		return nil
+	}
+	return d.Detector
+}
+
+// GetJob returns the Job field.
+func (d *DependencyGraphSnapshot) GetJob() *DependencyGraphSnapshotJob {
+	if d == nil {
+		return nil
+	}
+	return d.Job
+}
+
+// GetRef returns the Ref field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshot) GetRef() string {
+	if d == nil || d.Ref == nil {
+		return ""
+	}
+	return *d.Ref
+}
+
+// GetScanned returns the Scanned field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshot) GetScanned() Timestamp {
+	if d == nil || d.Scanned == nil {
+		return Timestamp{}
+	}
+	return *d.Scanned
+}
+
+// GetSha returns the Sha field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshot) GetSha() string {
+	if d == nil || d.Sha == nil {
+		return ""
+	}
+	return *d.Sha
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotCreationData) GetCreatedAt() Timestamp {
+	if d == nil || d.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *d.CreatedAt
+}
+
+// GetMessage returns the Message field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotCreationData) GetMessage() string {
+	if d == nil || d.Message == nil {
+		return ""
+	}
+	return *d.Message
+}
+
+// GetResult returns the Result field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotCreationData) GetResult() string {
+	if d == nil || d.Result == nil {
+		return ""
+	}
+	return *d.Result
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotDetector) GetName() string {
+	if d == nil || d.Name == nil {
+		return ""
+	}
+	return *d.Name
+}
+
+// GetURL returns the URL field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotDetector) GetURL() string {
+	if d == nil || d.URL == nil {
+		return ""
+	}
+	return *d.URL
+}
+
+// GetVersion returns the Version field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotDetector) GetVersion() string {
+	if d == nil || d.Version == nil {
+		return ""
+	}
+	return *d.Version
+}
+
+// GetCorrelator returns the Correlator field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotJob) GetCorrelator() string {
+	if d == nil || d.Correlator == nil {
+		return ""
+	}
+	return *d.Correlator
+}
+
+// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotJob) GetHTMLURL() string {
+	if d == nil || d.HTMLURL == nil {
+		return ""
+	}
+	return *d.HTMLURL
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotJob) GetID() string {
+	if d == nil || d.ID == nil {
+		return ""
+	}
+	return *d.ID
+}
+
+// GetFile returns the File field.
+func (d *DependencyGraphSnapshotManifest) GetFile() *DependencyGraphSnapshotManifestFile {
+	if d == nil {
+		return nil
+	}
+	return d.File
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotManifest) GetName() string {
+	if d == nil || d.Name == nil {
+		return ""
+	}
+	return *d.Name
+}
+
+// GetSourceLocation returns the SourceLocation field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotManifestFile) GetSourceLocation() string {
+	if d == nil || d.SourceLocation == nil {
+		return ""
+	}
+	return *d.SourceLocation
+}
+
+// GetPackageURL returns the PackageURL field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotResolvedDependency) GetPackageURL() string {
+	if d == nil || d.PackageURL == nil {
+		return ""
+	}
+	return *d.PackageURL
+}
+
+// GetRelationship returns the Relationship field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotResolvedDependency) GetRelationship() string {
+	if d == nil || d.Relationship == nil {
+		return ""
+	}
+	return *d.Relationship
+}
+
+// GetScope returns the Scope field if it's non-nil, zero value otherwise.
+func (d *DependencyGraphSnapshotResolvedDependency) GetScope() string {
+	if d == nil || d.Scope == nil {
+		return ""
+	}
+	return *d.Scope
+}
+
 // GetAction returns the Action field if it's non-nil, zero value otherwise.
 func (d *DeployKeyEvent) GetAction() string {
 	if d == nil || d.Action == nil {
@@ -9062,6 +9222,14 @@ func (i *InstallationToken) GetToken() string {
 	return *i.Token
 }
 
+// GetPermissions returns the Permissions field.
+func (i *InstallationTokenListRepoOptions) GetPermissions() *InstallationPermissions {
+	if i == nil {
+		return nil
+	}
+	return i.Permissions
+}
+
 // GetPermissions returns the Permissions field.
 func (i *InstallationTokenOptions) GetPermissions() *InstallationPermissions {
 	if i == nil {
@@ -10758,6 +10926,14 @@ func (l *ListRepositories) GetTotalCount() int {
 	return *l.TotalCount
 }
 
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (l *ListRunnersOptions) GetName() string {
+	if l == nil || l.Name == nil {
+		return ""
+	}
+	return *l.Name
+}
+
 // GetCount returns the Count field if it's non-nil, zero value otherwise.
 func (l *ListSCIMProvisionedIdentitiesOptions) GetCount() int {
 	if l == nil || l.Count == nil {
diff --git a/github/github.go b/github/github.go
index 03e2e627..ad426959 100644
--- a/github/github.go
+++ b/github/github.go
@@ -804,6 +804,7 @@ type requestContext uint8
 
 const (
 	bypassRateLimitCheck requestContext = iota
+	SleepUntilPrimaryRateLimitResetWhenRateLimited
 )
 
 // BareDo sends an API request and lets you handle the api response. If an error
@@ -889,6 +890,15 @@ func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, erro
 			err = aerr
 		}
 
+		rateLimitError, ok := err.(*RateLimitError)
+		if ok && req.Context().Value(SleepUntilPrimaryRateLimitResetWhenRateLimited) != nil {
+			if err := sleepUntilResetWithBuffer(req.Context(), rateLimitError.Rate.Reset.Time); err != nil {
+				return response, err
+			}
+			// retry the request once when the rate limit has reset
+			return c.BareDo(context.WithValue(req.Context(), SleepUntilPrimaryRateLimitResetWhenRateLimited, nil), req)
+		}
+
 		// Update the secondary rate limit if we hit it.
 		rerr, ok := err.(*AbuseRateLimitError)
 		if ok && rerr.RetryAfter != nil {
@@ -950,6 +960,18 @@ func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory Rat
 			Header:     make(http.Header),
 			Body:       io.NopCloser(strings.NewReader("")),
 		}
+
+		if req.Context().Value(SleepUntilPrimaryRateLimitResetWhenRateLimited) != nil {
+			if err := sleepUntilResetWithBuffer(req.Context(), rate.Reset.Time); err == nil {
+				return nil
+			}
+			return &RateLimitError{
+				Rate:     rate,
+				Response: resp,
+				Message:  fmt.Sprintf("Context cancelled while waiting for rate limit to reset until %v, not making remote request.", rate.Reset.Time),
+			}
+		}
+
 		return &RateLimitError{
 			Rate:     rate,
 			Response: resp,
@@ -1514,6 +1536,20 @@ func formatRateReset(d time.Duration) string {
 	return fmt.Sprintf("[rate reset in %v]", timeString)
 }
 
+func sleepUntilResetWithBuffer(ctx context.Context, reset time.Time) error {
+	buffer := time.Second
+	timer := time.NewTimer(time.Until(reset) + buffer)
+	select {
+	case <-ctx.Done():
+		if !timer.Stop() {
+			<-timer.C
+		}
+		return ctx.Err()
+	case <-timer.C:
+	}
+	return nil
+}
+
 // When using roundTripWithOptionalFollowRedirect, note that it
 // is the responsibility of the caller to close the response body.
 func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, maxRedirects int, opts ...RequestOption) (*http.Response, error) {
diff --git a/github/repos.go b/github/repos.go
index 6fb0030a..630ce748 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -2402,3 +2402,27 @@ func (s *RepositoriesService) DisablePrivateReporting(ctx context.Context, owner
 
 	return resp, nil
 }
+
+// checkPrivateReporting represents whether private vulnerability reporting is enabled.
+type checkPrivateReporting struct {
+	Enabled bool `json:"enabled,omitempty"`
+}
+
+// IsPrivateReportingEnabled checks if private vulnerability reporting is enabled
+// for the repository and returns a boolean indicating the status.
+//
+// GitHub API docs: https://docs.github.com/rest/repos/repos#check-if-private-vulnerability-reporting-is-enabled-for-a-repository
+//
+//meta:operation GET /repos/{owner}/{repo}/private-vulnerability-reporting
+func (s *RepositoriesService) IsPrivateReportingEnabled(ctx context.Context, owner, repo string) (bool, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/private-vulnerability-reporting", owner, repo)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return false, nil, err
+	}
+
+	privateReporting := new(checkPrivateReporting)
+	resp, err := s.client.Do(ctx, req, privateReporting)
+	return privateReporting.Enabled, resp, err
+}


Test patch:
diff --git a/github/actions_runners_test.go b/github/actions_runners_test.go
index 581e4594..f425e8af 100644
--- a/github/actions_runners_test.go
+++ b/github/actions_runners_test.go
@@ -188,11 +188,14 @@ func TestActionsService_ListRunners(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/actions/runners", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testFormValues(t, r, values{"per_page": "2", "page": "2"})
-		fmt.Fprint(w, `{"total_count":2,"runners":[{"id":23,"name":"MBP","os":"macos","status":"online"},{"id":24,"name":"iMac","os":"macos","status":"offline"}]}`)
+		testFormValues(t, r, values{"name": "MBP", "per_page": "2", "page": "2"})
+		fmt.Fprint(w, `{"total_count":1,"runners":[{"id":23,"name":"MBP","os":"macos","status":"online"}]}`)
 	})
 
-	opts := &ListOptions{Page: 2, PerPage: 2}
+	opts := &ListRunnersOptions{
+		Name:        String("MBP"),
+		ListOptions: ListOptions{Page: 2, PerPage: 2},
+	}
 	ctx := context.Background()
 	runners, _, err := client.Actions.ListRunners(ctx, "o", "r", opts)
 	if err != nil {
@@ -200,10 +203,9 @@ func TestActionsService_ListRunners(t *testing.T) {
 	}
 
 	want := &Runners{
-		TotalCount: 2,
+		TotalCount: 1,
 		Runners: []*Runner{
 			{ID: Int64(23), Name: String("MBP"), OS: String("macos"), Status: String("online")},
-			{ID: Int64(24), Name: String("iMac"), OS: String("macos"), Status: String("offline")},
 		},
 	}
 	if !cmp.Equal(runners, want) {
@@ -413,7 +415,9 @@ func TestActionsService_ListOrganizationRunners(t *testing.T) {
 		fmt.Fprint(w, `{"total_count":2,"runners":[{"id":23,"name":"MBP","os":"macos","status":"online"},{"id":24,"name":"iMac","os":"macos","status":"offline"}]}`)
 	})
 
-	opts := &ListOptions{Page: 2, PerPage: 2}
+	opts := &ListRunnersOptions{
+		ListOptions: ListOptions{Page: 2, PerPage: 2},
+	}
 	ctx := context.Background()
 	runners, _, err := client.Actions.ListOrganizationRunners(ctx, "o", opts)
 	if err != nil {
diff --git a/github/apps_test.go b/github/apps_test.go
index a58326ea..ea670344 100644
--- a/github/apps_test.go
+++ b/github/apps_test.go
@@ -469,6 +469,77 @@ func TestAppsService_CreateInstallationTokenWithOptions(t *testing.T) {
 	}
 }
 
+func TestAppsService_CreateInstallationTokenListReposWithOptions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	installationTokenListRepoOptions := &InstallationTokenListRepoOptions{
+		Repositories: []string{"foo"},
+		Permissions: &InstallationPermissions{
+			Contents: String("write"),
+			Issues:   String("read"),
+		},
+	}
+
+	mux.HandleFunc("/app/installations/1/access_tokens", func(w http.ResponseWriter, r *http.Request) {
+		v := new(InstallationTokenListRepoOptions)
+		assertNilError(t, json.NewDecoder(r.Body).Decode(v))
+
+		if !cmp.Equal(v, installationTokenListRepoOptions) {
+			t.Errorf("request sent %+v, want %+v", v, installationTokenListRepoOptions)
+		}
+
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{"token":"t"}`)
+	})
+
+	ctx := context.Background()
+	token, _, err := client.Apps.CreateInstallationTokenListRepos(ctx, 1, installationTokenListRepoOptions)
+	if err != nil {
+		t.Errorf("Apps.CreateInstallationTokenListRepos returned error: %v", err)
+	}
+
+	want := &InstallationToken{Token: String("t")}
+	if !cmp.Equal(token, want) {
+		t.Errorf("Apps.CreateInstallationTokenListRepos returned %+v, want %+v", token, want)
+	}
+}
+
+func TestAppsService_CreateInstallationTokenListReposWithNoOptions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/app/installations/1/access_tokens", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{"token":"t"}`)
+	})
+
+	ctx := context.Background()
+	token, _, err := client.Apps.CreateInstallationTokenListRepos(ctx, 1, nil)
+	if err != nil {
+		t.Errorf("Apps.CreateInstallationTokenListRepos returned error: %v", err)
+	}
+
+	want := &InstallationToken{Token: String("t")}
+	if !cmp.Equal(token, want) {
+		t.Errorf("Apps.CreateInstallationTokenListRepos returned %+v, want %+v", token, want)
+	}
+
+	const methodName = "CreateInstallationTokenListRepos"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Apps.CreateInstallationTokenListRepos(ctx, -1, nil)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Apps.CreateInstallationTokenListRepos(ctx, 1, nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
 func TestAppsService_CreateAttachement(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/copilot_test.go b/github/copilot_test.go
index bc295af3..008630ab 100644
--- a/github/copilot_test.go
+++ b/github/copilot_test.go
@@ -336,6 +336,10 @@ func TestCopilotService_ListCopilotSeats(t *testing.T) {
 
 	mux.HandleFunc("/orgs/o/copilot/billing/seats", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
+		testFormValues(t, r, values{
+			"per_page": "100",
+			"page":     "1",
+		})
 		fmt.Fprint(w, `{
 				"total_seats": 4,
 				"seats": [
@@ -473,7 +477,8 @@ func TestCopilotService_ListCopilotSeats(t *testing.T) {
 	lastActivityAt2 := Timestamp{tmp}
 
 	ctx := context.Background()
-	got, _, err := client.Copilot.ListCopilotSeats(ctx, "o", nil)
+	opts := &ListOptions{Page: 1, PerPage: 100}
+	got, _, err := client.Copilot.ListCopilotSeats(ctx, "o", opts)
 	if err != nil {
 		t.Errorf("Copilot.ListCopilotSeats returned error: %v", err)
 	}
@@ -584,14 +589,13 @@ func TestCopilotService_ListCopilotSeats(t *testing.T) {
 
 	const methodName = "ListCopilotSeats"
 
-	opts := &ListOptions{Page: 2}
 	testBadOptions(t, methodName, func() (err error) {
 		_, _, err = client.Copilot.ListCopilotSeats(ctx, "\n", opts)
 		return err
 	})
 
 	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Copilot.ListCopilotSeats(ctx, "", nil)
+		got, resp, err := client.Copilot.ListCopilotSeats(ctx, "o", opts)
 		if got != nil {
 			t.Errorf("Copilot.ListCopilotSeats returned %+v, want nil", got)
 		}
diff --git a/github/dependency_graph_snapshots_test.go b/github/dependency_graph_snapshots_test.go
new file mode 100644
index 00000000..ed70ed57
--- /dev/null
+++ b/github/dependency_graph_snapshots_test.go
@@ -0,0 +1,94 @@
+// 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"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestDependencyGraphService_CreateSnapshot(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/dependency-graph/snapshots", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		testBody(t, r, `{"version":0,"sha":"ce587453ced02b1526dfb4cb910479d431683101","ref":"refs/heads/main","job":{"correlator":"yourworkflowname_youractionname","id":"yourrunid","html_url":"https://example.com"},"detector":{"name":"octo-detector","version":"0.0.1","url":"https://github.com/octo-org/octo-repo"},"scanned":"2022-06-14T20:25:00Z","manifests":{"package-lock.json":{"name":"package-lock.json","file":{"source_location":"src/package-lock.json"},"resolved":{"@actions/core":{"package_url":"pkg:/npm/%40actions/core@1.1.9","relationship":"direct","scope":"runtime","dependencies":["@actions/http-client"]},"@actions/http-client":{"package_url":"pkg:/npm/%40actions/http-client@1.0.7","relationship":"indirect","scope":"runtime","dependencies":["tunnel"]},"tunnel":{"package_url":"pkg:/npm/tunnel@0.0.6","relationship":"indirect","scope":"runtime"}}}}}`+"\n")
+		fmt.Fprint(w, `{"id":12345,"created_at":"2022-06-14T20:25:01Z","message":"Dependency results for the repo have been successfully updated.","result":"SUCCESS"}`)
+	})
+
+	ctx := context.Background()
+	snapshot := &DependencyGraphSnapshot{
+		Version: 0,
+		Sha:     String("ce587453ced02b1526dfb4cb910479d431683101"),
+		Ref:     String("refs/heads/main"),
+		Job: &DependencyGraphSnapshotJob{
+			Correlator: String("yourworkflowname_youractionname"),
+			ID:         String("yourrunid"),
+			HTMLURL:    String("https://example.com"),
+		},
+		Detector: &DependencyGraphSnapshotDetector{
+			Name:    String("octo-detector"),
+			Version: String("0.0.1"),
+			URL:     String("https://github.com/octo-org/octo-repo"),
+		},
+		Scanned: &Timestamp{time.Date(2022, time.June, 14, 20, 25, 00, 0, time.UTC)},
+		Manifests: map[string]*DependencyGraphSnapshotManifest{
+			"package-lock.json": {
+				Name: String("package-lock.json"),
+				File: &DependencyGraphSnapshotManifestFile{SourceLocation: String("src/package-lock.json")},
+				Resolved: map[string]*DependencyGraphSnapshotResolvedDependency{
+					"@actions/core": {
+						PackageURL:   String("pkg:/npm/%40actions/core@1.1.9"),
+						Relationship: String("direct"),
+						Scope:        String("runtime"),
+						Dependencies: []string{"@actions/http-client"},
+					},
+					"@actions/http-client": {
+						PackageURL:   String("pkg:/npm/%40actions/http-client@1.0.7"),
+						Relationship: String("indirect"),
+						Scope:        String("runtime"),
+						Dependencies: []string{"tunnel"},
+					},
+					"tunnel": {
+						PackageURL:   String("pkg:/npm/tunnel@0.0.6"),
+						Relationship: String("indirect"),
+						Scope:        String("runtime"),
+					},
+				},
+			},
+		},
+	}
+
+	snapshotCreationData, _, err := client.DependencyGraph.CreateSnapshot(ctx, "o", "r", snapshot)
+	if err != nil {
+		t.Errorf("DependencyGraph.CreateSnapshot returned error: %v", err)
+	}
+
+	want := &DependencyGraphSnapshotCreationData{
+		ID:        12345,
+		CreatedAt: &Timestamp{time.Date(2022, time.June, 14, 20, 25, 01, 0, time.UTC)},
+		Message:   String("Dependency results for the repo have been successfully updated."),
+		Result:    String("SUCCESS"),
+	}
+	if !cmp.Equal(snapshotCreationData, want) {
+		t.Errorf("DependencyGraph.CreateSnapshot returned %+v, want %+v", snapshotCreationData, want)
+	}
+
+	const methodName = "CreateSnapshot"
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.DependencyGraph.CreateSnapshot(ctx, "o", "r", snapshot)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 313e92c0..5b0e1de6 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -6112,6 +6112,197 @@ func TestDependency_GetScope(tt *testing.T) {
 	d.GetScope()
 }
 
+func TestDependencyGraphSnapshot_GetDetector(tt *testing.T) {
+	d := &DependencyGraphSnapshot{}
+	d.GetDetector()
+	d = nil
+	d.GetDetector()
+}
+
+func TestDependencyGraphSnapshot_GetJob(tt *testing.T) {
+	d := &DependencyGraphSnapshot{}
+	d.GetJob()
+	d = nil
+	d.GetJob()
+}
+
+func TestDependencyGraphSnapshot_GetRef(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshot{Ref: &zeroValue}
+	d.GetRef()
+	d = &DependencyGraphSnapshot{}
+	d.GetRef()
+	d = nil
+	d.GetRef()
+}
+
+func TestDependencyGraphSnapshot_GetScanned(tt *testing.T) {
+	var zeroValue Timestamp
+	d := &DependencyGraphSnapshot{Scanned: &zeroValue}
+	d.GetScanned()
+	d = &DependencyGraphSnapshot{}
+	d.GetScanned()
+	d = nil
+	d.GetScanned()
+}
+
+func TestDependencyGraphSnapshot_GetSha(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshot{Sha: &zeroValue}
+	d.GetSha()
+	d = &DependencyGraphSnapshot{}
+	d.GetSha()
+	d = nil
+	d.GetSha()
+}
+
+func TestDependencyGraphSnapshotCreationData_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	d := &DependencyGraphSnapshotCreationData{CreatedAt: &zeroValue}
+	d.GetCreatedAt()
+	d = &DependencyGraphSnapshotCreationData{}
+	d.GetCreatedAt()
+	d = nil
+	d.GetCreatedAt()
+}
+
+func TestDependencyGraphSnapshotCreationData_GetMessage(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotCreationData{Message: &zeroValue}
+	d.GetMessage()
+	d = &DependencyGraphSnapshotCreationData{}
+	d.GetMessage()
+	d = nil
+	d.GetMessage()
+}
+
+func TestDependencyGraphSnapshotCreationData_GetResult(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotCreationData{Result: &zeroValue}
+	d.GetResult()
+	d = &DependencyGraphSnapshotCreationData{}
+	d.GetResult()
+	d = nil
+	d.GetResult()
+}
+
+func TestDependencyGraphSnapshotDetector_GetName(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotDetector{Name: &zeroValue}
+	d.GetName()
+	d = &DependencyGraphSnapshotDetector{}
+	d.GetName()
+	d = nil
+	d.GetName()
+}
+
+func TestDependencyGraphSnapshotDetector_GetURL(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotDetector{URL: &zeroValue}
+	d.GetURL()
+	d = &DependencyGraphSnapshotDetector{}
+	d.GetURL()
+	d = nil
+	d.GetURL()
+}
+
+func TestDependencyGraphSnapshotDetector_GetVersion(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotDetector{Version: &zeroValue}
+	d.GetVersion()
+	d = &DependencyGraphSnapshotDetector{}
+	d.GetVersion()
+	d = nil
+	d.GetVersion()
+}
+
+func TestDependencyGraphSnapshotJob_GetCorrelator(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotJob{Correlator: &zeroValue}
+	d.GetCorrelator()
+	d = &DependencyGraphSnapshotJob{}
+	d.GetCorrelator()
+	d = nil
+	d.GetCorrelator()
+}
+
+func TestDependencyGraphSnapshotJob_GetHTMLURL(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotJob{HTMLURL: &zeroValue}
+	d.GetHTMLURL()
+	d = &DependencyGraphSnapshotJob{}
+	d.GetHTMLURL()
+	d = nil
+	d.GetHTMLURL()
+}
+
+func TestDependencyGraphSnapshotJob_GetID(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotJob{ID: &zeroValue}
+	d.GetID()
+	d = &DependencyGraphSnapshotJob{}
+	d.GetID()
+	d = nil
+	d.GetID()
+}
+
+func TestDependencyGraphSnapshotManifest_GetFile(tt *testing.T) {
+	d := &DependencyGraphSnapshotManifest{}
+	d.GetFile()
+	d = nil
+	d.GetFile()
+}
+
+func TestDependencyGraphSnapshotManifest_GetName(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotManifest{Name: &zeroValue}
+	d.GetName()
+	d = &DependencyGraphSnapshotManifest{}
+	d.GetName()
+	d = nil
+	d.GetName()
+}
+
+func TestDependencyGraphSnapshotManifestFile_GetSourceLocation(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotManifestFile{SourceLocation: &zeroValue}
+	d.GetSourceLocation()
+	d = &DependencyGraphSnapshotManifestFile{}
+	d.GetSourceLocation()
+	d = nil
+	d.GetSourceLocation()
+}
+
+func TestDependencyGraphSnapshotResolvedDependency_GetPackageURL(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotResolvedDependency{PackageURL: &zeroValue}
+	d.GetPackageURL()
+	d = &DependencyGraphSnapshotResolvedDependency{}
+	d.GetPackageURL()
+	d = nil
+	d.GetPackageURL()
+}
+
+func TestDependencyGraphSnapshotResolvedDependency_GetRelationship(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotResolvedDependency{Relationship: &zeroValue}
+	d.GetRelationship()
+	d = &DependencyGraphSnapshotResolvedDependency{}
+	d.GetRelationship()
+	d = nil
+	d.GetRelationship()
+}
+
+func TestDependencyGraphSnapshotResolvedDependency_GetScope(tt *testing.T) {
+	var zeroValue string
+	d := &DependencyGraphSnapshotResolvedDependency{Scope: &zeroValue}
+	d.GetScope()
+	d = &DependencyGraphSnapshotResolvedDependency{}
+	d.GetScope()
+	d = nil
+	d.GetScope()
+}
+
 func TestDeployKeyEvent_GetAction(tt *testing.T) {
 	var zeroValue string
 	d := &DeployKeyEvent{Action: &zeroValue}
@@ -10608,6 +10799,13 @@ func TestInstallationToken_GetToken(tt *testing.T) {
 	i.GetToken()
 }
 
+func TestInstallationTokenListRepoOptions_GetPermissions(tt *testing.T) {
+	i := &InstallationTokenListRepoOptions{}
+	i.GetPermissions()
+	i = nil
+	i.GetPermissions()
+}
+
 func TestInstallationTokenOptions_GetPermissions(tt *testing.T) {
 	i := &InstallationTokenOptions{}
 	i.GetPermissions()
@@ -12581,6 +12779,16 @@ func TestListRepositories_GetTotalCount(tt *testing.T) {
 	l.GetTotalCount()
 }
 
+func TestListRunnersOptions_GetName(tt *testing.T) {
+	var zeroValue string
+	l := &ListRunnersOptions{Name: &zeroValue}
+	l.GetName()
+	l = &ListRunnersOptions{}
+	l.GetName()
+	l = nil
+	l.GetName()
+}
+
 func TestListSCIMProvisionedIdentitiesOptions_GetCount(tt *testing.T) {
 	var zeroValue int
 	l := &ListSCIMProvisionedIdentitiesOptions{Count: &zeroValue}
diff --git a/github/github_test.go b/github/github_test.go
index efc42d43..8c6c38a8 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -1381,6 +1381,176 @@ func TestDo_rateLimit_ignoredFromCache(t *testing.T) {
 	}
 }
 
+// Ensure sleeps until the rate limit is reset when the client is rate limited.
+func TestDo_rateLimit_sleepUntilResponseResetLimit(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	reset := time.Now().UTC().Add(time.Second)
+
+	var firstRequest = true
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		if firstRequest {
+			firstRequest = false
+			w.Header().Set(headerRateLimit, "60")
+			w.Header().Set(headerRateRemaining, "0")
+			w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix()))
+			w.Header().Set("Content-Type", "application/json; charset=utf-8")
+			w.WriteHeader(http.StatusForbidden)
+			fmt.Fprintln(w, `{
+   "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
+   "documentation_url": "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits"
+}`)
+			return
+		}
+		w.Header().Set(headerRateLimit, "5000")
+		w.Header().Set(headerRateRemaining, "5000")
+		w.Header().Set(headerRateReset, fmt.Sprint(reset.Add(time.Hour).Unix()))
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintln(w, `{}`)
+	})
+
+	req, _ := client.NewRequest("GET", ".", nil)
+	ctx := context.Background()
+	resp, err := client.Do(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), req, nil)
+	if err != nil {
+		t.Errorf("Do returned unexpected error: %v", err)
+	}
+	if got, want := resp.StatusCode, http.StatusOK; got != want {
+		t.Errorf("Response status code = %v, want %v", got, want)
+	}
+}
+
+// Ensure tries to sleep until the rate limit is reset when the client is rate limited, but only once.
+func TestDo_rateLimit_sleepUntilResponseResetLimitRetryOnce(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	reset := time.Now().UTC().Add(time.Second)
+
+	requestCount := 0
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		requestCount++
+		w.Header().Set(headerRateLimit, "60")
+		w.Header().Set(headerRateRemaining, "0")
+		w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix()))
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		w.WriteHeader(http.StatusForbidden)
+		fmt.Fprintln(w, `{
+   "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
+   "documentation_url": "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits"
+}`)
+	})
+
+	req, _ := client.NewRequest("GET", ".", nil)
+	ctx := context.Background()
+	_, err := client.Do(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), req, nil)
+	if err == nil {
+		t.Error("Expected error to be returned.")
+	}
+	if got, want := requestCount, 2; got != want {
+		t.Errorf("Expected 2 requests, got %d", got)
+	}
+}
+
+// Ensure a network call is not made when it's known that API rate limit is still exceeded.
+func TestDo_rateLimit_sleepUntilClientResetLimit(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	reset := time.Now().UTC().Add(time.Second)
+	client.rateLimits[CoreCategory] = Rate{Limit: 5000, Remaining: 0, Reset: Timestamp{reset}}
+	requestCount := 0
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		requestCount++
+		w.Header().Set(headerRateLimit, "5000")
+		w.Header().Set(headerRateRemaining, "5000")
+		w.Header().Set(headerRateReset, fmt.Sprint(reset.Add(time.Hour).Unix()))
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintln(w, `{}`)
+	})
+	req, _ := client.NewRequest("GET", ".", nil)
+	ctx := context.Background()
+	resp, err := client.Do(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), req, nil)
+	if err != nil {
+		t.Errorf("Do returned unexpected error: %v", err)
+	}
+	if got, want := resp.StatusCode, http.StatusOK; got != want {
+		t.Errorf("Response status code = %v, want %v", got, want)
+	}
+	if got, want := requestCount, 1; got != want {
+		t.Errorf("Expected 1 request, got %d", got)
+	}
+}
+
+// Ensure sleep is aborted when the context is cancelled.
+func TestDo_rateLimit_abortSleepContextCancelled(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	// We use a 1 minute reset time to ensure the sleep is not completed.
+	reset := time.Now().UTC().Add(time.Minute)
+	requestCount := 0
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		requestCount++
+		w.Header().Set(headerRateLimit, "60")
+		w.Header().Set(headerRateRemaining, "0")
+		w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix()))
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		w.WriteHeader(http.StatusForbidden)
+		fmt.Fprintln(w, `{
+   "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
+   "documentation_url": "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits"
+}`)
+	})
+
+	req, _ := client.NewRequest("GET", ".", nil)
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
+	defer cancel()
+	_, err := client.Do(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), req, nil)
+	if !errors.Is(err, context.DeadlineExceeded) {
+		t.Error("Expected context deadline exceeded error.")
+	}
+	if got, want := requestCount, 1; got != want {
+		t.Errorf("Expected 1 requests, got %d", got)
+	}
+}
+
+// Ensure sleep is aborted when the context is cancelled on initial request.
+func TestDo_rateLimit_abortSleepContextCancelledClientLimit(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	reset := time.Now().UTC().Add(time.Minute)
+	client.rateLimits[CoreCategory] = Rate{Limit: 5000, Remaining: 0, Reset: Timestamp{reset}}
+	requestCount := 0
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		requestCount++
+		w.Header().Set(headerRateLimit, "5000")
+		w.Header().Set(headerRateRemaining, "5000")
+		w.Header().Set(headerRateReset, fmt.Sprint(reset.Add(time.Hour).Unix()))
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprintln(w, `{}`)
+	})
+	req, _ := client.NewRequest("GET", ".", nil)
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
+	defer cancel()
+	_, err := client.Do(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), req, nil)
+	rateLimitError, ok := err.(*RateLimitError)
+	if !ok {
+		t.Fatalf("Expected a *rateLimitError error; got %#v.", err)
+	}
+	if got, wantSuffix := rateLimitError.Message, "Context cancelled while waiting for rate limit to reset until"; !strings.HasPrefix(got, wantSuffix) {
+		t.Errorf("Expected request to be prevented because context cancellation, got: %v.", got)
+	}
+	if got, want := requestCount, 0; got != want {
+		t.Errorf("Expected 1 requests, got %d", got)
+	}
+}
+
 // Ensure *AbuseRateLimitError is returned when the response indicates that
 // the client has triggered an abuse detection mechanism.
 func TestDo_rateLimit_abuseRateLimitError(t *testing.T) {
diff --git a/github/repos_test.go b/github/repos_test.go
index 2b742bef..e07b1b36 100644
--- a/github/repos_test.go
+++ b/github/repos_test.go
@@ -4430,3 +4430,36 @@ func TestRepositoriesService_DisablePrivateReporting(t *testing.T) {
 		return client.Repositories.DisablePrivateReporting(ctx, "owner", "repo")
 	})
 }
+
+func TestRepositoriesService_IsPrivateReportingEnabled(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/owner/repo/private-vulnerability-reporting", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"enabled": true}`)
+	})
+
+	ctx := context.Background()
+	enabled, _, err := client.Repositories.IsPrivateReportingEnabled(ctx, "owner", "repo")
+	if err != nil {
+		t.Errorf("Repositories.IsPrivateReportingEnabled returned error: %v", err)
+	}
+	if want := true; enabled != want {
+		t.Errorf("Repositories.IsPrivateReportingEnabled returned %+v, want %+v", enabled, want)
+	}
+
+	const methodName = "IsPrivateReportingEnabled"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.IsPrivateReportingEnabled(ctx, "\n", "\n")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.IsPrivateReportingEnabled(ctx, "owner", "repo")
+		if got {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want false", methodName, got)
+		}
+		return resp, err
+	})
+}