Repository: google/go-github
Pull request #3088 (2024-03-01 15:06:30)

Add audit log rate limit category and make rate limit category getter public
When calling `https://api.github.com/rate_limit`, the endpoint returns a `audit_log` rate limit category as well. This is used for `/audit-log` endpoints:
- https://docs.github.com/en/enterprise-cloud@latest/rest/orgs/orgs?apiVersion=2022-11-28#get-the-audit-log-for-an-organization
- https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/using-the-audit-log-api-for-your-enterprise#using-the-audit-log-api

This PR adds this new rate limit category.

I have a use case where I need to know the rate limit category before making the request so as to not reimplement what's already done in `go-github`, I made this logic public. Other users could also benefit of this change.
If you think it's not reasonable and it should be kept private, I will revert this change.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3088?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 92.87%. Comparing base [(`2b8c7fa`)](https://app.codecov.io/gh/google/go-github/commit/2b8c7fa86fcd1cb4a89a44c8e7b5a49fe4be8a45?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`cfae9c6`)](https://app.codecov.io/gh/google/go-github/pull/3088?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> Report is 16 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3088      +/-   ##
==========================================
- Coverage   97.72%   92.87%   -4.86%     
==========================================
  Files         153      170      +17     
  Lines       13390    11404    -1986     
==========================================
- Hits        13085    10591    -2494     
- Misses        215      723     +508     
  Partials       90       90              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/go-github/pull/3088?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).


Patch:
diff --git a/example/go.mod b/example/go.mod
index 17e9dd10..7a247393 100644
--- a/example/go.mod
+++ b/example/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/google/go-querystring v1.1.0 // indirect
 	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/sys v0.15.0 // indirect
-	google.golang.org/protobuf v1.28.0 // indirect
+	google.golang.org/protobuf v1.33.0 // indirect
 )
 
 // Use version at HEAD, not the latest published.
diff --git a/example/go.sum b/example/go.sum
index 81f5d70a..d8e05e56 100644
--- a/example/go.sum
+++ b/example/go.sum
@@ -82,5 +82,5 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 89728d38..01b81375 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -1574,6 +1574,14 @@ func (b *Branch) GetProtected() bool {
 	return *b.Protected
 }
 
+// GetProtection returns the Protection field.
+func (b *Branch) GetProtection() *Protection {
+	if b == nil {
+		return nil
+	}
+	return b.Protection
+}
+
 // GetCommit returns the Commit field.
 func (b *BranchCommit) GetCommit() *Commit {
 	if b == nil {
@@ -17534,6 +17542,14 @@ func (r *RateLimits) GetActionsRunnerRegistration() *Rate {
 	return r.ActionsRunnerRegistration
 }
 
+// GetAuditLog returns the AuditLog field.
+func (r *RateLimits) GetAuditLog() *Rate {
+	if r == nil {
+		return nil
+	}
+	return r.AuditLog
+}
+
 // GetCodeScanningUpload returns the CodeScanningUpload field.
 func (r *RateLimits) GetCodeScanningUpload() *Rate {
 	if r == nil {
diff --git a/github/github.go b/github/github.go
index 05c3acd5..bf0b5715 100644
--- a/github/github.go
+++ b/github/github.go
@@ -170,7 +170,7 @@ type Client struct {
 	UserAgent string
 
 	rateMu                  sync.Mutex
-	rateLimits              [categories]Rate // Rate limits for the client as determined by the most recent API calls.
+	rateLimits              [Categories]Rate // Rate limits for the client as determined by the most recent API calls.
 	secondaryRateLimitReset time.Time        // Secondary rate limit reset for the client as determined by the most recent API calls.
 
 	common service // Reuse a single struct instead of allocating one for each service on the heap.
@@ -821,7 +821,7 @@ func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, erro
 
 	req = withContext(ctx, req)
 
-	rateLimitCategory := category(req.Method, req.URL.Path)
+	rateLimitCategory := GetRateLimitCategory(req.Method, req.URL.Path)
 
 	if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil {
 		// If we've hit rate limit, don't make further requests before Reset time.
@@ -937,7 +937,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res
 // current client state in order to quickly check if *RateLimitError can be immediately returned
 // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
 // Otherwise it returns nil, and Client.Do should proceed normally.
-func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
+func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory RateLimitCategory) *RateLimitError {
 	c.rateMu.Lock()
 	rate := c.rateLimits[rateLimitCategory]
 	c.rateMu.Unlock()
@@ -1303,65 +1303,70 @@ func parseBoolResponse(err error) (bool, error) {
 	return false, err
 }
 
-type rateLimitCategory uint8
+type RateLimitCategory uint8
 
 const (
-	coreCategory rateLimitCategory = iota
-	searchCategory
-	graphqlCategory
-	integrationManifestCategory
-	sourceImportCategory
-	codeScanningUploadCategory
-	actionsRunnerRegistrationCategory
-	scimCategory
-	dependencySnapshotsCategory
-	codeSearchCategory
-
-	categories // An array of this length will be able to contain all rate limit categories.
+	CoreCategory RateLimitCategory = iota
+	SearchCategory
+	GraphqlCategory
+	IntegrationManifestCategory
+	SourceImportCategory
+	CodeScanningUploadCategory
+	ActionsRunnerRegistrationCategory
+	ScimCategory
+	DependencySnapshotsCategory
+	CodeSearchCategory
+	AuditLogCategory
+
+	Categories // An array of this length will be able to contain all rate limit categories.
 )
 
-// category returns the rate limit category of the endpoint, determined by HTTP method and Request.URL.Path.
-func category(method, path string) rateLimitCategory {
+// GetRateLimitCategory returns the rate limit RateLimitCategory of the endpoint, determined by HTTP method and Request.URL.Path.
+func GetRateLimitCategory(method, path string) RateLimitCategory {
 	switch {
 	// https://docs.github.com/rest/rate-limit#about-rate-limits
 	default:
 		// NOTE: coreCategory is returned for actionsRunnerRegistrationCategory too,
 		// because no API found for this category.
-		return coreCategory
+		return CoreCategory
 
 	// https://docs.github.com/en/rest/search/search#search-code
 	case strings.HasPrefix(path, "/search/code") &&
 		method == http.MethodGet:
-		return codeSearchCategory
+		return CodeSearchCategory
 
 	case strings.HasPrefix(path, "/search/"):
-		return searchCategory
+		return SearchCategory
 	case path == "/graphql":
-		return graphqlCategory
+		return GraphqlCategory
 	case strings.HasPrefix(path, "/app-manifests/") &&
 		strings.HasSuffix(path, "/conversions") &&
 		method == http.MethodPost:
-		return integrationManifestCategory
+		return IntegrationManifestCategory
 
 	// https://docs.github.com/rest/migrations/source-imports#start-an-import
 	case strings.HasPrefix(path, "/repos/") &&
 		strings.HasSuffix(path, "/import") &&
 		method == http.MethodPut:
-		return sourceImportCategory
+		return SourceImportCategory
 
 	// https://docs.github.com/rest/code-scanning#upload-an-analysis-as-sarif-data
 	case strings.HasSuffix(path, "/code-scanning/sarifs"):
-		return codeScanningUploadCategory
+		return CodeScanningUploadCategory
 
 	// https://docs.github.com/enterprise-cloud@latest/rest/scim
 	case strings.HasPrefix(path, "/scim/"):
-		return scimCategory
+		return ScimCategory
 
 	// https://docs.github.com/en/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
 	case strings.HasPrefix(path, "/repos/") &&
 		strings.HasSuffix(path, "/dependency-graph/snapshots") &&
 		method == http.MethodPost:
-		return dependencySnapshotsCategory
+		return DependencySnapshotsCategory
+
+	// https://docs.github.com/en/enterprise-cloud@latest/rest/orgs/orgs?apiVersion=2022-11-28#get-the-audit-log-for-an-organization
+	case strings.HasSuffix(path, "/audit-log"):
+		return AuditLogCategory
 	}
 }
 
diff --git a/github/rate_limit.go b/github/rate_limit.go
index febe5edc..5b01b573 100644
--- a/github/rate_limit.go
+++ b/github/rate_limit.go
@@ -54,6 +54,7 @@ type RateLimits struct {
 	SCIM                      *Rate `json:"scim"`
 	DependencySnapshots       *Rate `json:"dependency_snapshots"`
 	CodeSearch                *Rate `json:"code_search"`
+	AuditLog                  *Rate `json:"audit_log"`
 }
 
 func (r RateLimits) String() string {
@@ -85,34 +86,37 @@ func (s *RateLimitService) Get(ctx context.Context) (*RateLimits, *Response, err
 	if response.Resources != nil {
 		s.client.rateMu.Lock()
 		if response.Resources.Core != nil {
-			s.client.rateLimits[coreCategory] = *response.Resources.Core
+			s.client.rateLimits[CoreCategory] = *response.Resources.Core
 		}
 		if response.Resources.Search != nil {
-			s.client.rateLimits[searchCategory] = *response.Resources.Search
+			s.client.rateLimits[SearchCategory] = *response.Resources.Search
 		}
 		if response.Resources.GraphQL != nil {
-			s.client.rateLimits[graphqlCategory] = *response.Resources.GraphQL
+			s.client.rateLimits[GraphqlCategory] = *response.Resources.GraphQL
 		}
 		if response.Resources.IntegrationManifest != nil {
-			s.client.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest
+			s.client.rateLimits[IntegrationManifestCategory] = *response.Resources.IntegrationManifest
 		}
 		if response.Resources.SourceImport != nil {
-			s.client.rateLimits[sourceImportCategory] = *response.Resources.SourceImport
+			s.client.rateLimits[SourceImportCategory] = *response.Resources.SourceImport
 		}
 		if response.Resources.CodeScanningUpload != nil {
-			s.client.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload
+			s.client.rateLimits[CodeScanningUploadCategory] = *response.Resources.CodeScanningUpload
 		}
 		if response.Resources.ActionsRunnerRegistration != nil {
-			s.client.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration
+			s.client.rateLimits[ActionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration
 		}
 		if response.Resources.SCIM != nil {
-			s.client.rateLimits[scimCategory] = *response.Resources.SCIM
+			s.client.rateLimits[ScimCategory] = *response.Resources.SCIM
 		}
 		if response.Resources.DependencySnapshots != nil {
-			s.client.rateLimits[dependencySnapshotsCategory] = *response.Resources.DependencySnapshots
+			s.client.rateLimits[DependencySnapshotsCategory] = *response.Resources.DependencySnapshots
 		}
 		if response.Resources.CodeSearch != nil {
-			s.client.rateLimits[codeSearchCategory] = *response.Resources.CodeSearch
+			s.client.rateLimits[CodeSearchCategory] = *response.Resources.CodeSearch
+		}
+		if response.Resources.AuditLog != nil {
+			s.client.rateLimits[AuditLogCategory] = *response.Resources.AuditLog
 		}
 		s.client.rateMu.Unlock()
 	}
diff --git a/github/repos.go b/github/repos.go
index 2fb4c6f1..6fb0030a 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -1003,6 +1003,14 @@ type Branch struct {
 	Name      *string           `json:"name,omitempty"`
 	Commit    *RepositoryCommit `json:"commit,omitempty"`
 	Protected *bool             `json:"protected,omitempty"`
+
+	// Protection will always be included in APIs which return the
+	// 'Branch With Protection' schema such as 'Get a branch', but may
+	// not be included in APIs that return the `Short Branch` schema
+	// such as 'List branches'. In such cases, if branch protection is
+	// enabled, Protected will be `true` but this will be nil, and
+	// additional protection details can be obtained by calling GetBranch().
+	Protection *Protection `json:"protection,omitempty"`
 }
 
 // Protection represents a repository branch's protection.
diff --git a/github/repos_rules.go b/github/repos_rules.go
index 479806c2..9f4e0245 100644
--- a/github/repos_rules.go
+++ b/github/repos_rules.go
@@ -112,8 +112,11 @@ type RequiredWorkflowsRuleParameters struct {
 
 // RepositoryRule represents a GitHub Rule.
 type RepositoryRule struct {
-	Type       string           `json:"type"`
-	Parameters *json.RawMessage `json:"parameters,omitempty"`
+	Type              string           `json:"type"`
+	Parameters        *json.RawMessage `json:"parameters,omitempty"`
+	RulesetSourceType string           `json:"ruleset_source_type"`
+	RulesetSource     string           `json:"ruleset_source"`
+	RulesetID         int64            `json:"ruleset_id"`
 }
 
 // UnmarshalJSON implements the json.Unmarshaler interface.
@@ -125,6 +128,9 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
+	r.RulesetID = RepositoryRule.RulesetID
+	r.RulesetSourceType = RepositoryRule.RulesetSourceType
+	r.RulesetSource = RepositoryRule.RulesetSource
 	r.Type = RepositoryRule.Type
 
 	switch RepositoryRule.Type {
diff --git a/scrape/go.mod b/scrape/go.mod
index 7a41ce4f..f48713d0 100644
--- a/scrape/go.mod
+++ b/scrape/go.mod
@@ -5,11 +5,11 @@ go 1.21
 toolchain go1.22.0
 
 require (
-	github.com/PuerkitoBio/goquery v1.9.0
+	github.com/PuerkitoBio/goquery v1.9.1
 	github.com/google/go-cmp v0.6.0
 	github.com/google/go-github/v60 v60.0.0
 	github.com/xlzd/gotp v0.1.0
-	golang.org/x/net v0.21.0
+	golang.org/x/net v0.22.0
 )
 
 require (
diff --git a/scrape/go.sum b/scrape/go.sum
index 22a8b903..3c8442f6 100644
--- a/scrape/go.sum
+++ b/scrape/go.sum
@@ -1,5 +1,5 @@
-github.com/PuerkitoBio/goquery v1.9.0 h1:zgjKkdpRY9T97Q5DCtcXwfqkcylSFIVCocZmn2huTp8=
-github.com/PuerkitoBio/goquery v1.9.0/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
+github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
+github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
 github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
 github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -21,8 +21,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/tools/go.mod b/tools/go.mod
index b9e425c6..874aa769 100644
--- a/tools/go.mod
+++ b/tools/go.mod
@@ -5,7 +5,7 @@ go 1.21
 toolchain go1.22.0
 
 require (
-	github.com/alecthomas/kong v0.8.1
+	github.com/alecthomas/kong v0.9.0
 	github.com/getkin/kin-openapi v0.123.0
 	github.com/google/go-cmp v0.6.0
 	github.com/google/go-github/v60 v60.0.0
diff --git a/tools/go.sum b/tools/go.sum
index 87c9d843..ce5b0414 100644
--- a/tools/go.sum
+++ b/tools/go.sum
@@ -1,9 +1,9 @@
-github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
-github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
-github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
-github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
-github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
-github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
+github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
+github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
+github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
+github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
+github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
+github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index cf6f7ec2..fdde6151 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -73,4 +73,4 @@ jobs:
 
     - name: Upload coverage to Codecov
       if: ${{ matrix.update-coverage }}
-      uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 #v4.0.2
+      uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab #v4.1.0
diff --git a/github/code-scanning_test.go b/github/code-scanning_test.go
index a081a6ba..1edbebae 100644
--- a/github/code-scanning_test.go
+++ b/github/code-scanning_test.go
@@ -93,7 +93,7 @@ func TestCodeScanningService_UploadSarif(t *testing.T) {
 		return err
 	})
 
-	testNewRequestAndDoFailureCategory(t, methodName, client, codeScanningUploadCategory, func() (*Response, error) {
+	testNewRequestAndDoFailureCategory(t, methodName, client, CodeScanningUploadCategory, func() (*Response, error) {
 		_, resp, err := client.CodeScanning.UploadSarif(ctx, "o", "r", sarifAnalysis)
 		return resp, err
 	})
diff --git a/github/enterprise_audit_log_test.go b/github/enterprise_audit_log_test.go
index 0d9e44a3..1ae94bab 100644
--- a/github/enterprise_audit_log_test.go
+++ b/github/enterprise_audit_log_test.go
@@ -84,7 +84,7 @@ func TestEnterpriseService_GetAuditLog(t *testing.T) {
 		return err
 	})
 
-	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+	testNewRequestAndDoFailureCategory(t, methodName, client, AuditLogCategory, func() (*Response, error) {
 		got, resp, err := client.Enterprise.GetAuditLog(ctx, "o", &GetAuditLogOptions{})
 		if got != nil {
 			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 1dcb4dc2..ba328735 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -1881,6 +1881,13 @@ func TestBranch_GetProtected(tt *testing.T) {
 	b.GetProtected()
 }
 
+func TestBranch_GetProtection(tt *testing.T) {
+	b := &Branch{}
+	b.GetProtection()
+	b = nil
+	b.GetProtection()
+}
+
 func TestBranchCommit_GetCommit(tt *testing.T) {
 	b := &BranchCommit{}
 	b.GetCommit()
@@ -20310,6 +20317,13 @@ func TestRateLimits_GetActionsRunnerRegistration(tt *testing.T) {
 	r.GetActionsRunnerRegistration()
 }
 
+func TestRateLimits_GetAuditLog(tt *testing.T) {
+	r := &RateLimits{}
+	r.GetAuditLog()
+	r = nil
+	r.GetAuditLog()
+}
+
 func TestRateLimits_GetCodeScanningUpload(tt *testing.T) {
 	r := &RateLimits{}
 	r.GetCodeScanningUpload()
diff --git a/github/github_test.go b/github/github_test.go
index 9a369a25..efc42d43 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -212,11 +212,11 @@ func testBadOptions(t *testing.T, methodName string, f func() error) {
 // Method f should be a regular call that would normally succeed, but
 // should return an error when NewRequest or s.client.Do fails.
 func testNewRequestAndDoFailure(t *testing.T, methodName string, client *Client, f func() (*Response, error)) {
-	testNewRequestAndDoFailureCategory(t, methodName, client, coreCategory, f)
+	testNewRequestAndDoFailureCategory(t, methodName, client, CoreCategory, f)
 }
 
 // testNewRequestAndDoFailureCategory works Like testNewRequestAndDoFailure, but allows setting the category
-func testNewRequestAndDoFailureCategory(t *testing.T, methodName string, client *Client, category rateLimitCategory, f func() (*Response, error)) {
+func testNewRequestAndDoFailureCategory(t *testing.T, methodName string, client *Client, category RateLimitCategory, f func() (*Response, error)) {
 	t.Helper()
 	if methodName == "" {
 		t.Error("testNewRequestAndDoFailure: must supply method methodName")
@@ -1132,68 +1132,73 @@ func TestDo_rateLimitCategory(t *testing.T) {
 	tests := []struct {
 		method   string
 		url      string
-		category rateLimitCategory
+		category RateLimitCategory
 	}{
 		{
 			method:   http.MethodGet,
 			url:      "/",
-			category: coreCategory,
+			category: CoreCategory,
 		},
 		{
 			method:   http.MethodGet,
 			url:      "/search/issues?q=rate",
-			category: searchCategory,
+			category: SearchCategory,
 		},
 		{
 			method:   http.MethodGet,
 			url:      "/graphql",
-			category: graphqlCategory,
+			category: GraphqlCategory,
 		},
 		{
 			method:   http.MethodPost,
 			url:      "/app-manifests/code/conversions",
-			category: integrationManifestCategory,
+			category: IntegrationManifestCategory,
 		},
 		{
 			method:   http.MethodGet,
 			url:      "/app-manifests/code/conversions",
-			category: coreCategory, // only POST requests are in the integration manifest category
+			category: CoreCategory, // only POST requests are in the integration manifest category
 		},
 		{
 			method:   http.MethodPut,
 			url:      "/repos/google/go-github/import",
-			category: sourceImportCategory,
+			category: SourceImportCategory,
 		},
 		{
 			method:   http.MethodGet,
 			url:      "/repos/google/go-github/import",
-			category: coreCategory, // only PUT requests are in the source import category
+			category: CoreCategory, // only PUT requests are in the source import category
 		},
 		{
 			method:   http.MethodPost,
 			url:      "/repos/google/go-github/code-scanning/sarifs",
-			category: codeScanningUploadCategory,
+			category: CodeScanningUploadCategory,
 		},
 		{
 			method:   http.MethodGet,
 			url:      "/scim/v2/organizations/ORG/Users",
-			category: scimCategory,
+			category: ScimCategory,
 		},
 		{
 			method:   http.MethodPost,
 			url:      "/repos/google/go-github/dependency-graph/snapshots",
-			category: dependencySnapshotsCategory,
+			category: DependencySnapshotsCategory,
 		},
 		{
 			method:   http.MethodGet,
 			url:      "/search/code?q=rate",
-			category: codeSearchCategory,
+			category: CodeSearchCategory,
+		},
+		{
+			method:   http.MethodGet,
+			url:      "/orgs/google/audit-log",
+			category: AuditLogCategory,
 		},
 		// missing a check for actionsRunnerRegistrationCategory: API not found
 	}
 
 	for _, tt := range tests {
-		if got, want := category(tt.method, tt.url), tt.category; got != want {
+		if got, want := GetRateLimitCategory(tt.method, tt.url), tt.category; got != want {
 			t.Errorf("expecting category %v, found %v", got, want)
 		}
 	}
diff --git a/github/orgs_audit_log_test.go b/github/orgs_audit_log_test.go
index 7c8de74c..feebd0af 100644
--- a/github/orgs_audit_log_test.go
+++ b/github/orgs_audit_log_test.go
@@ -164,7 +164,7 @@ func TestOrganizationService_GetAuditLog(t *testing.T) {
 		return err
 	})
 
-	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+	testNewRequestAndDoFailureCategory(t, methodName, client, AuditLogCategory, func() (*Response, error) {
 		got, resp, err := client.Organizations.GetAuditLog(ctx, "o", &GetAuditLogOptions{})
 		if got != nil {
 			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
diff --git a/github/rate_limit_test.go b/github/rate_limit_test.go
index da8a68c9..6ff87824 100644
--- a/github/rate_limit_test.go
+++ b/github/rate_limit_test.go
@@ -27,8 +27,9 @@ func TestRateLimits_String(t *testing.T) {
 		SCIM:                      &Rate{},
 		DependencySnapshots:       &Rate{},
 		CodeSearch:                &Rate{},
+		AuditLog:                  &Rate{},
 	}
-	want := `github.RateLimits{Core:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, Search:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, GraphQL:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, IntegrationManifest:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SourceImport:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeScanningUpload:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, ActionsRunnerRegistration:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SCIM:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, DependencySnapshots:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeSearch:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}}`
+	want := `github.RateLimits{Core:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, Search:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, GraphQL:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, IntegrationManifest:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SourceImport:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeScanningUpload:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, ActionsRunnerRegistration:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SCIM:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, DependencySnapshots:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeSearch:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, AuditLog:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}}`
 	if got := v.String(); got != want {
 		t.Errorf("RateLimits.String = %v, want %v", got, want)
 	}
@@ -50,7 +51,8 @@ func TestRateLimits(t *testing.T) {
 			"actions_runner_registration": {"limit":8,"remaining":7,"reset":1372700879},
 			"scim": {"limit":9,"remaining":8,"reset":1372700880},
 			"dependency_snapshots": {"limit":10,"remaining":9,"reset":1372700881},
-			"code_search": {"limit":11,"remaining":10,"reset":1372700882}
+			"code_search": {"limit":11,"remaining":10,"reset":1372700882},
+			"audit_log": {"limit": 12,"remaining":11,"reset":1372700883}
 		}}`)
 	})
 
@@ -111,54 +113,63 @@ func TestRateLimits(t *testing.T) {
 			Remaining: 10,
 			Reset:     Timestamp{time.Date(2013, time.July, 1, 17, 48, 2, 0, time.UTC).Local()},
 		},
+		AuditLog: &Rate{
+			Limit:     12,
+			Remaining: 11,
+			Reset:     Timestamp{time.Date(2013, time.July, 1, 17, 48, 3, 0, time.UTC).Local()},
+		},
 	}
 	if !cmp.Equal(rate, want) {
 		t.Errorf("RateLimits returned %+v, want %+v", rate, want)
 	}
 	tests := []struct {
-		category rateLimitCategory
+		category RateLimitCategory
 		rate     *Rate
 	}{
 		{
-			category: coreCategory,
+			category: CoreCategory,
 			rate:     want.Core,
 		},
 		{
-			category: searchCategory,
+			category: SearchCategory,
 			rate:     want.Search,
 		},
 		{
-			category: graphqlCategory,
+			category: GraphqlCategory,
 			rate:     want.GraphQL,
 		},
 		{
-			category: integrationManifestCategory,
+			category: IntegrationManifestCategory,
 			rate:     want.IntegrationManifest,
 		},
 		{
-			category: sourceImportCategory,
+			category: SourceImportCategory,
 			rate:     want.SourceImport,
 		},
 		{
-			category: codeScanningUploadCategory,
+			category: CodeScanningUploadCategory,
 			rate:     want.CodeScanningUpload,
 		},
 		{
-			category: actionsRunnerRegistrationCategory,
+			category: ActionsRunnerRegistrationCategory,
 			rate:     want.ActionsRunnerRegistration,
 		},
 		{
-			category: scimCategory,
+			category: ScimCategory,
 			rate:     want.SCIM,
 		},
 		{
-			category: dependencySnapshotsCategory,
+			category: DependencySnapshotsCategory,
 			rate:     want.DependencySnapshots,
 		},
 		{
-			category: codeSearchCategory,
+			category: CodeSearchCategory,
 			rate:     want.CodeSearch,
 		},
+		{
+			category: AuditLogCategory,
+			rate:     want.AuditLog,
+		},
 	}
 
 	for _, tt := range tests {
@@ -185,7 +196,7 @@ func TestRateLimits_overQuota(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
-	client.rateLimits[coreCategory] = Rate{
+	client.rateLimits[CoreCategory] = Rate{
 		Limit:     1,
 		Remaining: 0,
 		Reset:     Timestamp{time.Now().Add(time.Hour).Local()},
@@ -201,7 +212,8 @@ func TestRateLimits_overQuota(t *testing.T) {
 			"actions_runner_registration": {"limit":8,"remaining":7,"reset":1372700879},
 			"scim": {"limit":9,"remaining":8,"reset":1372700880},
 			"dependency_snapshots": {"limit":10,"remaining":9,"reset":1372700881},
-			"code_search": {"limit":11,"remaining":10,"reset":1372700882}
+			"code_search": {"limit":11,"remaining":10,"reset":1372700882},
+			"audit_log": {"limit":12,"remaining":11,"reset":1372700883}
 		}}`)
 	})
 
@@ -262,55 +274,64 @@ func TestRateLimits_overQuota(t *testing.T) {
 			Remaining: 10,
 			Reset:     Timestamp{time.Date(2013, time.July, 1, 17, 48, 2, 0, time.UTC).Local()},
 		},
+		AuditLog: &Rate{
+			Limit:     12,
+			Remaining: 11,
+			Reset:     Timestamp{time.Date(2013, time.July, 1, 17, 48, 3, 0, time.UTC).Local()},
+		},
 	}
 	if !cmp.Equal(rate, want) {
 		t.Errorf("RateLimits returned %+v, want %+v", rate, want)
 	}
 
 	tests := []struct {
-		category rateLimitCategory
+		category RateLimitCategory
 		rate     *Rate
 	}{
 		{
-			category: coreCategory,
+			category: CoreCategory,
 			rate:     want.Core,
 		},
 		{
-			category: searchCategory,
+			category: SearchCategory,
 			rate:     want.Search,
 		},
 		{
-			category: graphqlCategory,
+			category: GraphqlCategory,
 			rate:     want.GraphQL,
 		},
 		{
-			category: integrationManifestCategory,
+			category: IntegrationManifestCategory,
 			rate:     want.IntegrationManifest,
 		},
 		{
-			category: sourceImportCategory,
+			category: SourceImportCategory,
 			rate:     want.SourceImport,
 		},
 		{
-			category: codeScanningUploadCategory,
+			category: CodeScanningUploadCategory,
 			rate:     want.CodeScanningUpload,
 		},
 		{
-			category: actionsRunnerRegistrationCategory,
+			category: ActionsRunnerRegistrationCategory,
 			rate:     want.ActionsRunnerRegistration,
 		},
 		{
-			category: scimCategory,
+			category: ScimCategory,
 			rate:     want.SCIM,
 		},
 		{
-			category: dependencySnapshotsCategory,
+			category: DependencySnapshotsCategory,
 			rate:     want.DependencySnapshots,
 		},
 		{
-			category: codeSearchCategory,
+			category: CodeSearchCategory,
 			rate:     want.CodeSearch,
 		},
+		{
+			category: AuditLogCategory,
+			rate:     want.AuditLog,
+		},
 	}
 	for _, tt := range tests {
 		if got, want := client.rateLimits[tt.category], *tt.rate; got != want {
@@ -373,6 +394,11 @@ func TestRateLimits_Marshal(t *testing.T) {
 			Remaining: 1,
 			Reset:     Timestamp{referenceTime},
 		},
+		AuditLog: &Rate{
+			Limit:     1,
+			Remaining: 1,
+			Reset:     Timestamp{referenceTime},
+		},
 	}
 
 	want := `{
@@ -425,6 +451,11 @@ func TestRateLimits_Marshal(t *testing.T) {
 			"limit": 1,
 			"remaining": 1,
 			"reset": ` + referenceTimeStr + `
+		},
+		"audit_log": {
+			"limit": 1,
+			"remaining": 1,
+			"reset": ` + referenceTimeStr + `
 		}
 	}`
 
diff --git a/github/repos_rules_test.go b/github/repos_rules_test.go
index cd8e49c2..cbc3e5ce 100644
--- a/github/repos_rules_test.go
+++ b/github/repos_rules_test.go
@@ -28,6 +28,20 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) {
 			},
 			wantErr: true,
 		},
+		"With Metadata": {
+			data: `{
+                    "type": "creation",
+					"ruleset_source_type": "Repository",
+					"ruleset_source": "google",
+					"ruleset_id": 1984
+           		   }`,
+			want: &RepositoryRule{
+				RulesetSource:     "google",
+				RulesetSourceType: "Repository",
+				RulesetID:         1984,
+				Type:              "creation",
+			},
+		},
 		"Valid creation": {
 			data: `{"type":"creation"}`,
 			want: NewCreationRule(),
@@ -262,9 +276,15 @@ func TestRepositoriesService_GetRulesForBranch(t *testing.T) {
 		testMethod(t, r, "GET")
 		fmt.Fprint(w, `[
 			{
+			  "ruleset_id": 42069,
+			  "ruleset_source_type": "Repository",
+			  "ruleset_source": "google",
 			  "type": "creation"
 			},
 			{
+			  "ruleset_id": 42069,
+			  "ruleset_source_type": "Organization",
+			  "ruleset_source": "google",
 			  "type": "update",
 			  "parameters": {
 			    "update_allows_fetch_and_merge": true
@@ -280,9 +300,15 @@ func TestRepositoriesService_GetRulesForBranch(t *testing.T) {
 	}
 
 	creationRule := NewCreationRule()
+	creationRule.RulesetID = 42069
+	creationRule.RulesetSource = "google"
+	creationRule.RulesetSourceType = "Repository"
 	updateRule := NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{
 		UpdateAllowsFetchAndMerge: true,
 	})
+	updateRule.RulesetID = 42069
+	updateRule.RulesetSource = "google"
+	updateRule.RulesetSourceType = "Organization"
 
 	want := []*RepositoryRule{
 		creationRule,
diff --git a/github/repos_test.go b/github/repos_test.go
index 0ea381eb..2b742bef 100644
--- a/github/repos_test.go
+++ b/github/repos_test.go
@@ -930,7 +930,7 @@ func TestRepositoriesService_GetBranch(t *testing.T) {
 	for _, test := range tests {
 		mux.HandleFunc(test.urlPath, func(w http.ResponseWriter, r *http.Request) {
 			testMethod(t, r, "GET")
-			fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s","commit":{"message":"m"}}, "protected":true}`)
+			fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s","commit":{"message":"m"}}, "protected":true, "protection":{"required_status_checks":{"contexts":["c"]}}}`)
 		})
 
 		ctx := context.Background()
@@ -948,6 +948,11 @@ func TestRepositoriesService_GetBranch(t *testing.T) {
 				},
 			},
 			Protected: Bool(true),
+			Protection: &Protection{
+				RequiredStatusChecks: &RequiredStatusChecks{
+					Contexts: &[]string{"c"},
+				},
+			},
 		}
 
 		if !cmp.Equal(branch, want) {
@@ -1000,7 +1005,7 @@ func TestRepositoriesService_GetBranch_StatusMovedPermanently_followRedirects(t
 	})
 	mux.HandleFunc("/repos/o/r/branches/br", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s","commit":{"message":"m"}}, "protected":true}`)
+		fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s","commit":{"message":"m"}}, "protected":true, "protection":{"required_status_checks":{"contexts":["c"]}}}`)
 	})
 	ctx := context.Background()
 	branch, resp, err := client.Repositories.GetBranch(ctx, "o", "r", "b", 1)
@@ -1020,6 +1025,11 @@ func TestRepositoriesService_GetBranch_StatusMovedPermanently_followRedirects(t
 			},
 		},
 		Protected: Bool(true),
+		Protection: &Protection{
+			RequiredStatusChecks: &RequiredStatusChecks{
+				Contexts: &[]string{"c"},
+			},
+		},
 	}
 	if !cmp.Equal(branch, want) {
 		t.Errorf("Repositories.GetBranch returned %+v, want %+v", branch, want)