Repository: google/go-github
Pull request #2581 (2022-11-29 14:34:12)

Support new GitHub v3 API calendar-based versioning
Fixes: #2580.

@willnorris - please see if you agree with this approach for supporting the new GitHub v3 API versioning mechanism.

My thought is that when they add breaking API changes, that we will continue to support them, but will now add the `WithVersion` option to each request that needs it.

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/2581?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2581](https://codecov.io/gh/google/go-github/pull/2581?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (3cd13ca) into [master](https://codecov.io/gh/google/go-github/commit/18cd63d0e2bda56f9018d7f85bf11f81e6ce2dd2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (18cd63d) will **decrease** coverage by `0.05%`.
> The diff coverage is `76.00%`.

```diff
@@            Coverage Diff             @@
##           master    #2581      +/-   ##
==========================================
- Coverage   97.98%   97.93%   -0.06%     
==========================================
  Files         125      125              
  Lines       10875    10894      +19     
==========================================
+ Hits        10656    10669      +13     
- Misses        150      154       +4     
- Partials       69       71       +2     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/2581?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/github.go](https://codecov.io/gh/google/go-github/pull/2581/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `96.50% <76.00%> (-0.88%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

gmlewis: Excellent feedback, @willnorris - thank you very much!

I've attempted to capture your ideas into the README.md file.
Please take a look and see if that is what you had in mind.

Patch:
diff --git a/README.md b/README.md
index 0f99698d..9406f76e 100644
--- a/README.md
+++ b/README.md
@@ -332,6 +332,40 @@ Preview functionality may take the form of entire methods or simply additional
 data returned from an otherwise non-preview method. Refer to the GitHub API
 documentation for details on preview functionality.
 
+### Calendar Versioning ###
+
+As of 2022-11-28, GitHub [has announced](https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/)
+that they are starting to version their v3 API based on "calendar-versioning".
+
+In practice, our goal is to make per-method version overrides (at
+least in the core library) rare and temporary.
+
+Our understanding of the GitHub docs is that they will be revving the
+entire API to each new date-based version, even if only a few methods
+have breaking changes. Other methods will accept the new version with
+their existing functionality. So when a new date-based version of the
+GitHub API is released, we (the repo maintainers) plan to:
+
+* update each method that had breaking changes, overriding their
+  per-method API version header. This may happen in one or multiple
+  commits and PRs, and is all done in the main branch.
+
+* once all of the methods with breaking changes have been updated,
+  have a final commit that bumps the default API version, and remove
+  all of the per-method overrides. That would now get a major version
+  bump when the next go-github release is made.
+
+### Version Compatibility Table ###
+
+The following table identifies which version of the GitHub API is
+supported by this (and past) versions of this repo (go-github).
+Versions prior to 48.2.0 are not listed.
+
+| go-github Version | GitHub v3 API Version |
+| ----------------- | --------------------- |
+| 48.2.0            | 2022-11-28            |
+
+
 ## License ##
 
 This library is distributed under the BSD-style license found in the [LICENSE](./LICENSE)
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 33de4b7e..f2cb16ff 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -7470,6 +7470,14 @@ func (i *Issue) GetState() string {
 	return *i.State
 }
 
+// GetStateReason returns the StateReason field if it's non-nil, zero value otherwise.
+func (i *Issue) GetStateReason() string {
+	if i == nil || i.StateReason == nil {
+		return ""
+	}
+	return *i.StateReason
+}
+
 // GetTitle returns the Title field if it's non-nil, zero value otherwise.
 func (i *Issue) GetTitle() string {
 	if i == nil || i.Title == nil {
@@ -15150,6 +15158,14 @@ func (r *Repository) GetGitURL() string {
 	return *r.GitURL
 }
 
+// GetHasDiscussions returns the HasDiscussions field if it's non-nil, zero value otherwise.
+func (r *Repository) GetHasDiscussions() bool {
+	if r == nil || r.HasDiscussions == nil {
+		return false
+	}
+	return *r.HasDiscussions
+}
+
 // GetHasDownloads returns the HasDownloads field if it's non-nil, zero value otherwise.
 func (r *Repository) GetHasDownloads() bool {
 	if r == nil || r.HasDownloads == nil {
@@ -15662,6 +15678,14 @@ func (r *Repository) GetWatchersCount() int {
 	return *r.WatchersCount
 }
 
+// GetAccessLevel returns the AccessLevel field if it's non-nil, zero value otherwise.
+func (r *RepositoryActionsAccessLevel) GetAccessLevel() string {
+	if r == nil || r.AccessLevel == nil {
+		return ""
+	}
+	return *r.AccessLevel
+}
+
 // GetAdvancedSecurityCommitters returns the AdvancedSecurityCommitters field if it's non-nil, zero value otherwise.
 func (r *RepositoryActiveCommitters) GetAdvancedSecurityCommitters() int {
 	if r == nil || r.AdvancedSecurityCommitters == nil {
@@ -16382,6 +16406,14 @@ func (r *RepositoryRelease) GetID() int64 {
 	return *r.ID
 }
 
+// GetMakeLatest returns the MakeLatest field if it's non-nil, zero value otherwise.
+func (r *RepositoryRelease) GetMakeLatest() string {
+	if r == nil || r.MakeLatest == nil {
+		return ""
+	}
+	return *r.MakeLatest
+}
+
 // GetName returns the Name field if it's non-nil, zero value otherwise.
 func (r *RepositoryRelease) GetName() string {
 	if r == nil || r.Name == nil {
diff --git a/github/github.go b/github/github.go
index 38a5bcd2..3599a88d 100644
--- a/github/github.go
+++ b/github/github.go
@@ -27,12 +27,14 @@ import (
 )
 
 const (
-	Version = "v48.0.0"
+	Version = "v48.2.0"
 
-	defaultBaseURL   = "https://api.github.com/"
-	defaultUserAgent = "go-github" + "/" + Version
-	uploadBaseURL    = "https://uploads.github.com/"
+	defaultAPIVersion = "2022-11-28"
+	defaultBaseURL    = "https://api.github.com/"
+	defaultUserAgent  = "go-github" + "/" + Version
+	uploadBaseURL     = "https://uploads.github.com/"
 
+	headerAPIVersion    = "X-GitHub-Api-Version"
 	headerRateLimit     = "X-RateLimit-Limit"
 	headerRateRemaining = "X-RateLimit-Remaining"
 	headerRateReset     = "X-RateLimit-Reset"
@@ -392,12 +394,24 @@ func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*C
 	return c, nil
 }
 
+// RequestOption represents an option that can modify an http.Request.
+type RequestOption func(req *http.Request)
+
+// WithVersion overrides the GitHub v3 API version for this individual request.
+// For more information, see:
+// https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/
+func WithVersion(version string) RequestOption {
+	return func(req *http.Request) {
+		req.Header.Set(headerAPIVersion, version)
+	}
+}
+
 // NewRequest creates an API request. A relative URL can be provided in urlStr,
 // in which case it is resolved relative to the BaseURL of the Client.
 // Relative URLs should always be specified without a preceding slash. If
 // specified, the value pointed to by body is JSON encoded and included as the
 // request body.
-func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
+func (c *Client) NewRequest(method, urlStr string, body interface{}, opts ...RequestOption) (*http.Request, error) {
 	if !strings.HasSuffix(c.BaseURL.Path, "/") {
 		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
 	}
@@ -430,6 +444,12 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
 	if c.UserAgent != "" {
 		req.Header.Set("User-Agent", c.UserAgent)
 	}
+	req.Header.Set(headerAPIVersion, defaultAPIVersion)
+
+	for _, opt := range opts {
+		opt(req)
+	}
+
 	return req, nil
 }
 
@@ -437,7 +457,7 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
 // in which case it is resolved relative to the BaseURL of the Client.
 // Relative URLs should always be specified without a preceding slash.
 // Body is sent with Content-Type: application/x-www-form-urlencoded.
-func (c *Client) NewFormRequest(urlStr string, body io.Reader) (*http.Request, error) {
+func (c *Client) NewFormRequest(urlStr string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
 	if !strings.HasSuffix(c.BaseURL.Path, "/") {
 		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
 	}
@@ -457,13 +477,19 @@ func (c *Client) NewFormRequest(urlStr string, body io.Reader) (*http.Request, e
 	if c.UserAgent != "" {
 		req.Header.Set("User-Agent", c.UserAgent)
 	}
+	req.Header.Set(headerAPIVersion, defaultAPIVersion)
+
+	for _, opt := range opts {
+		opt(req)
+	}
+
 	return req, nil
 }
 
 // NewUploadRequest creates an upload request. A relative URL can be provided in
 // urlStr, in which case it is resolved relative to the UploadURL of the Client.
 // Relative URLs should always be specified without a preceding slash.
-func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) {
+func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {
 	if !strings.HasSuffix(c.UploadURL.Path, "/") {
 		return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
 	}
@@ -485,6 +511,12 @@ func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, m
 	req.Header.Set("Content-Type", mediaType)
 	req.Header.Set("Accept", mediaTypeV3)
 	req.Header.Set("User-Agent", c.UserAgent)
+	req.Header.Set(headerAPIVersion, defaultAPIVersion)
+
+	for _, opt := range opts {
+		opt(req)
+	}
+
 	return req, nil
 }
 
@@ -1358,8 +1390,8 @@ func formatRateReset(d time.Duration) string {
 
 // 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, followRedirects bool) (*http.Response, error) {
-	req, err := c.NewRequest("GET", u, nil)
+func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, followRedirects bool, opts ...RequestOption) (*http.Response, error) {
+	req, err := c.NewRequest("GET", u, nil, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -1380,7 +1412,7 @@ func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u stri
 	if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
 		resp.Body.Close()
 		u = resp.Header.Get("Location")
-		resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false)
+		resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false, opts...)
 	}
 	return resp, err
 }
diff --git a/github/issues.go b/github/issues.go
index 351ec09c..571a7211 100644
--- a/github/issues.go
+++ b/github/issues.go
@@ -25,9 +25,11 @@ type IssuesService service
 // this is an issue, and if PullRequestLinks is not nil, this is a pull request.
 // The IsPullRequest helper method can be used to check that.
 type Issue struct {
-	ID                *int64            `json:"id,omitempty"`
-	Number            *int              `json:"number,omitempty"`
-	State             *string           `json:"state,omitempty"`
+	ID     *int64  `json:"id,omitempty"`
+	Number *int    `json:"number,omitempty"`
+	State  *string `json:"state,omitempty"`
+	// StateReason can be one of: "completed", "not_planned", "reopened".
+	StateReason       *string           `json:"state_reason,omitempty"`
 	Locked            *bool             `json:"locked,omitempty"`
 	Title             *string           `json:"title,omitempty"`
 	Body              *string           `json:"body,omitempty"`
diff --git a/github/repos.go b/github/repos.go
index 2b1a263f..fa4b4ae1 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -88,6 +88,7 @@ type Repository struct {
 	HasPages          *bool   `json:"has_pages,omitempty"`
 	HasProjects       *bool   `json:"has_projects,omitempty"`
 	HasDownloads      *bool   `json:"has_downloads,omitempty"`
+	HasDiscussions    *bool   `json:"has_discussions,omitempty"`
 	IsTemplate        *bool   `json:"is_template,omitempty"`
 	LicenseTemplate   *string `json:"license_template,omitempty"`
 	GitignoreTemplate *string `json:"gitignore_template,omitempty"`
@@ -365,12 +366,13 @@ type createRepoRequest struct {
 	Description *string `json:"description,omitempty"`
 	Homepage    *string `json:"homepage,omitempty"`
 
-	Private     *bool   `json:"private,omitempty"`
-	Visibility  *string `json:"visibility,omitempty"`
-	HasIssues   *bool   `json:"has_issues,omitempty"`
-	HasProjects *bool   `json:"has_projects,omitempty"`
-	HasWiki     *bool   `json:"has_wiki,omitempty"`
-	IsTemplate  *bool   `json:"is_template,omitempty"`
+	Private        *bool   `json:"private,omitempty"`
+	Visibility     *string `json:"visibility,omitempty"`
+	HasIssues      *bool   `json:"has_issues,omitempty"`
+	HasProjects    *bool   `json:"has_projects,omitempty"`
+	HasWiki        *bool   `json:"has_wiki,omitempty"`
+	HasDiscussions *bool   `json:"has_discussions,omitempty"`
+	IsTemplate     *bool   `json:"is_template,omitempty"`
 
 	// Creating an organization repository. Required for non-owners.
 	TeamID *int64 `json:"team_id,omitempty"`
@@ -423,6 +425,7 @@ func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repo
 		HasIssues:                 repo.HasIssues,
 		HasProjects:               repo.HasProjects,
 		HasWiki:                   repo.HasWiki,
+		HasDiscussions:            repo.HasDiscussions,
 		IsTemplate:                repo.IsTemplate,
 		TeamID:                    repo.TeamID,
 		AutoInit:                  repo.AutoInit,
diff --git a/github/repos_actions_access.go b/github/repos_actions_access.go
new file mode 100644
index 00000000..55761eeb
--- /dev/null
+++ b/github/repos_actions_access.go
@@ -0,0 +1,55 @@
+// Copyright 2022 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"
+)
+
+// RepositoryActionsAccessLevel represents the repository actions access level.
+//
+// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#set-the-level-of-access-for-workflows-outside-of-the-repository
+type RepositoryActionsAccessLevel struct {
+	// AccessLevel specifies the level of access that workflows outside of the repository have
+	// to actions and reusable workflows within the repository.
+	// Possible values are: "none", "organization" "enterprise".
+	AccessLevel *string `json:"access_level,omitempty"`
+}
+
+// GetActionsAccessLevel gets the level of access that workflows outside of the repository have
+// to actions and reusable workflows in the repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#get-the-level-of-access-for-workflows-outside-of-the-repository
+func (s *RepositoriesService) GetActionsAccessLevel(ctx context.Context, owner, repo string) (*RepositoryActionsAccessLevel, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/permissions/access", owner, repo)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	raal := new(RepositoryActionsAccessLevel)
+	resp, err := s.client.Do(ctx, req, raal)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return raal, resp, nil
+}
+
+// EditActionsAccessLevel sets the level of access that workflows outside of the repository have
+// to actions and reusable workflows in the repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#set-the-level-of-access-for-workflows-outside-of-the-repository
+func (s *RepositoriesService) EditActionsAccessLevel(ctx context.Context, owner, repo string, repositoryActionsAccessLevel RepositoryActionsAccessLevel) (*Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/permissions/access", owner, repo)
+	req, err := s.client.NewRequest("PUT", u, repositoryActionsAccessLevel)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/github/repos_forks.go b/github/repos_forks.go
index 5c7d5dbd..f175dfe3 100644
--- a/github/repos_forks.go
+++ b/github/repos_forks.go
@@ -7,9 +7,8 @@ package github
 
 import (
 	"context"
-	"fmt"
-
 	"encoding/json"
+	"fmt"
 )
 
 // RepositoryListForksOptions specifies the optional parameters to the
@@ -53,9 +52,9 @@ func (s *RepositoriesService) ListForks(ctx context.Context, owner, repo string,
 // RepositoriesService.CreateFork method.
 type RepositoryCreateForkOptions struct {
 	// The organization to fork the repository into.
-	Organization      string `url:"organization,omitempty"`
-	Name              string `url:"name,omitempty"`
-	DefaultBranchOnly bool   `url:"default_branch_only,omitempty"`
+	Organization      string `json:"organization,omitempty"`
+	Name              string `json:"name,omitempty"`
+	DefaultBranchOnly bool   `json:"default_branch_only,omitempty"`
 }
 
 // CreateFork creates a fork of the specified repository.
@@ -70,12 +69,8 @@ type RepositoryCreateForkOptions struct {
 // GitHub API docs: https://docs.github.com/en/rest/repos/forks#create-a-fork
 func (s *RepositoriesService) CreateFork(ctx context.Context, owner, repo string, opts *RepositoryCreateForkOptions) (*Repository, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/forks", owner, repo)
-	u, err := addOptions(u, opts)
-	if err != nil {
-		return nil, nil, err
-	}
 
-	req, err := s.client.NewRequest("POST", u, nil)
+	req, err := s.client.NewRequest("POST", u, opts)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/github/repos_releases.go b/github/repos_releases.go
index c030f04d..464c2ee1 100644
--- a/github/repos_releases.go
+++ b/github/repos_releases.go
@@ -19,12 +19,14 @@ import (
 
 // RepositoryRelease represents a GitHub release in a repository.
 type RepositoryRelease struct {
-	TagName                *string `json:"tag_name,omitempty"`
-	TargetCommitish        *string `json:"target_commitish,omitempty"`
-	Name                   *string `json:"name,omitempty"`
-	Body                   *string `json:"body,omitempty"`
-	Draft                  *bool   `json:"draft,omitempty"`
-	Prerelease             *bool   `json:"prerelease,omitempty"`
+	TagName         *string `json:"tag_name,omitempty"`
+	TargetCommitish *string `json:"target_commitish,omitempty"`
+	Name            *string `json:"name,omitempty"`
+	Body            *string `json:"body,omitempty"`
+	Draft           *bool   `json:"draft,omitempty"`
+	Prerelease      *bool   `json:"prerelease,omitempty"`
+	// MakeLatest can be one of: "true", "false", or "legacy".
+	MakeLatest             *string `json:"make_latest,omitempty"`
 	DiscussionCategoryName *string `json:"discussion_category_name,omitempty"`
 
 	// The following fields are not used in EditRelease:
@@ -176,6 +178,7 @@ type repositoryReleaseRequest struct {
 	Body                   *string `json:"body,omitempty"`
 	Draft                  *bool   `json:"draft,omitempty"`
 	Prerelease             *bool   `json:"prerelease,omitempty"`
+	MakeLatest             *string `json:"make_latest,omitempty"`
 	GenerateReleaseNotes   *bool   `json:"generate_release_notes,omitempty"`
 	DiscussionCategoryName *string `json:"discussion_category_name,omitempty"`
 }
@@ -196,6 +199,7 @@ func (s *RepositoriesService) CreateRelease(ctx context.Context, owner, repo str
 		Body:                   release.Body,
 		Draft:                  release.Draft,
 		Prerelease:             release.Prerelease,
+		MakeLatest:             release.MakeLatest,
 		DiscussionCategoryName: release.DiscussionCategoryName,
 		GenerateReleaseNotes:   release.GenerateReleaseNotes,
 	}
@@ -229,6 +233,7 @@ func (s *RepositoriesService) EditRelease(ctx context.Context, owner, repo strin
 		Body:                   release.Body,
 		Draft:                  release.Draft,
 		Prerelease:             release.Prerelease,
+		MakeLatest:             release.MakeLatest,
 		DiscussionCategoryName: release.DiscussionCategoryName,
 	}
 


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3a0366d4..d4a19774 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,3 +1,7 @@
+concurrency:
+  group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 on:
   push:
     branches:
@@ -15,9 +19,6 @@ permissions:
 
 jobs:
   test:
-    permissions:
-      actions: write  # for styfle/cancel-workflow-action to cancel/stop running workflows
-      contents: read  # for actions/checkout to fetch code
     strategy:
       matrix:
         go-version: [1.x, 1.18.x]
@@ -35,11 +36,6 @@ jobs:
     runs-on: ${{ matrix.platform }}
 
     steps:
-    - name: Cancel previous
-      uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 #0.11.0
-      with:
-        access_token: ${{ github.token }}
-
     - uses: actions/setup-go@v3
       with:
         go-version: ${{ matrix.go-version }}
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index ecc0d761..71fc6c1f 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -8780,6 +8780,16 @@ func TestIssue_GetState(tt *testing.T) {
 	i.GetState()
 }
 
+func TestIssue_GetStateReason(tt *testing.T) {
+	var zeroValue string
+	i := &Issue{StateReason: &zeroValue}
+	i.GetStateReason()
+	i = &Issue{}
+	i.GetStateReason()
+	i = nil
+	i.GetStateReason()
+}
+
 func TestIssue_GetTitle(tt *testing.T) {
 	var zeroValue string
 	i := &Issue{Title: &zeroValue}
@@ -17609,6 +17619,16 @@ func TestRepository_GetGitURL(tt *testing.T) {
 	r.GetGitURL()
 }
 
+func TestRepository_GetHasDiscussions(tt *testing.T) {
+	var zeroValue bool
+	r := &Repository{HasDiscussions: &zeroValue}
+	r.GetHasDiscussions()
+	r = &Repository{}
+	r.GetHasDiscussions()
+	r = nil
+	r.GetHasDiscussions()
+}
+
 func TestRepository_GetHasDownloads(tt *testing.T) {
 	var zeroValue bool
 	r := &Repository{HasDownloads: &zeroValue}
@@ -18228,6 +18248,16 @@ func TestRepository_GetWatchersCount(tt *testing.T) {
 	r.GetWatchersCount()
 }
 
+func TestRepositoryActionsAccessLevel_GetAccessLevel(tt *testing.T) {
+	var zeroValue string
+	r := &RepositoryActionsAccessLevel{AccessLevel: &zeroValue}
+	r.GetAccessLevel()
+	r = &RepositoryActionsAccessLevel{}
+	r.GetAccessLevel()
+	r = nil
+	r.GetAccessLevel()
+}
+
 func TestRepositoryActiveCommitters_GetAdvancedSecurityCommitters(tt *testing.T) {
 	var zeroValue int
 	r := &RepositoryActiveCommitters{AdvancedSecurityCommitters: &zeroValue}
@@ -19047,6 +19077,16 @@ func TestRepositoryRelease_GetID(tt *testing.T) {
 	r.GetID()
 }
 
+func TestRepositoryRelease_GetMakeLatest(tt *testing.T) {
+	var zeroValue string
+	r := &RepositoryRelease{MakeLatest: &zeroValue}
+	r.GetMakeLatest()
+	r = &RepositoryRelease{}
+	r.GetMakeLatest()
+	r = nil
+	r.GetMakeLatest()
+}
+
 func TestRepositoryRelease_GetName(tt *testing.T) {
 	var zeroValue string
 	r := &RepositoryRelease{Name: &zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index b83ff237..b60fd0ad 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -713,6 +713,7 @@ func TestIssue_String(t *testing.T) {
 		ID:                Int64(0),
 		Number:            Int(0),
 		State:             String(""),
+		StateReason:       String(""),
 		Locked:            Bool(false),
 		Title:             String(""),
 		Body:              String(""),
@@ -734,7 +735,7 @@ func TestIssue_String(t *testing.T) {
 		NodeID:            String(""),
 		ActiveLockReason:  String(""),
 	}
-	want := `github.Issue{ID:0, Number:0, State:"", Locked:false, Title:"", Body:"", AuthorAssociation:"", User:github.User{}, Assignee:github.User{}, Comments:0, ClosedBy:github.User{}, URL:"", HTMLURL:"", CommentsURL:"", EventsURL:"", LabelsURL:"", RepositoryURL:"", Milestone:github.Milestone{}, PullRequestLinks:github.PullRequestLinks{}, Repository:github.Repository{}, Reactions:github.Reactions{}, NodeID:"", ActiveLockReason:""}`
+	want := `github.Issue{ID:0, Number:0, State:"", StateReason:"", Locked:false, Title:"", Body:"", AuthorAssociation:"", User:github.User{}, Assignee:github.User{}, Comments:0, ClosedBy:github.User{}, URL:"", HTMLURL:"", CommentsURL:"", EventsURL:"", LabelsURL:"", RepositoryURL:"", Milestone:github.Milestone{}, PullRequestLinks:github.PullRequestLinks{}, Repository:github.Repository{}, Reactions:github.Reactions{}, NodeID:"", ActiveLockReason:""}`
 	if got := v.String(); got != want {
 		t.Errorf("Issue.String = %v, want %v", got, want)
 	}
@@ -1545,6 +1546,7 @@ func TestRepository_String(t *testing.T) {
 		HasPages:                  Bool(false),
 		HasProjects:               Bool(false),
 		HasDownloads:              Bool(false),
+		HasDiscussions:            Bool(false),
 		IsTemplate:                Bool(false),
 		LicenseTemplate:           String(""),
 		GitignoreTemplate:         String(""),
@@ -1590,7 +1592,7 @@ func TestRepository_String(t *testing.T) {
 		Visibility:                String(""),
 		RoleName:                  String(""),
 	}
-	want := `github.Repository{ID:0, NodeID:"", Owner:github.User{}, Name:"", FullName:"", Description:"", Homepage:"", CodeOfConduct:github.CodeOfConduct{}, DefaultBranch:"", MasterBranch:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PushedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HTMLURL:"", CloneURL:"", GitURL:"", MirrorURL:"", SSHURL:"", SVNURL:"", Language:"", Fork:false, ForksCount:0, NetworkCount:0, OpenIssuesCount:0, OpenIssues:0, StargazersCount:0, SubscribersCount:0, WatchersCount:0, Watchers:0, Size:0, AutoInit:false, Parent:github.Repository{}, Source:github.Repository{}, TemplateRepository:github.Repository{}, Organization:github.Organization{}, AllowRebaseMerge:false, AllowUpdateBranch:false, AllowSquashMerge:false, AllowMergeCommit:false, AllowAutoMerge:false, AllowForking:false, DeleteBranchOnMerge:false, UseSquashPRTitleAsDefault:false, SquashMergeCommitTitle:"", SquashMergeCommitMessage:"", MergeCommitTitle:"", MergeCommitMessage:"", Topics:[""], Archived:false, Disabled:false, License:github.License{}, Private:false, HasIssues:false, HasWiki:false, HasPages:false, HasProjects:false, HasDownloads:false, IsTemplate:false, LicenseTemplate:"", GitignoreTemplate:"", SecurityAndAnalysis:github.SecurityAndAnalysis{}, TeamID:0, URL:"", ArchiveURL:"", AssigneesURL:"", BlobsURL:"", BranchesURL:"", CollaboratorsURL:"", CommentsURL:"", CommitsURL:"", CompareURL:"", ContentsURL:"", ContributorsURL:"", DeploymentsURL:"", DownloadsURL:"", EventsURL:"", ForksURL:"", GitCommitsURL:"", GitRefsURL:"", GitTagsURL:"", HooksURL:"", IssueCommentURL:"", IssueEventsURL:"", IssuesURL:"", KeysURL:"", LabelsURL:"", LanguagesURL:"", MergesURL:"", MilestonesURL:"", NotificationsURL:"", PullsURL:"", ReleasesURL:"", StargazersURL:"", StatusesURL:"", SubscribersURL:"", SubscriptionURL:"", TagsURL:"", TreesURL:"", TeamsURL:"", Visibility:"", RoleName:""}`
+	want := `github.Repository{ID:0, NodeID:"", Owner:github.User{}, Name:"", FullName:"", Description:"", Homepage:"", CodeOfConduct:github.CodeOfConduct{}, DefaultBranch:"", MasterBranch:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PushedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HTMLURL:"", CloneURL:"", GitURL:"", MirrorURL:"", SSHURL:"", SVNURL:"", Language:"", Fork:false, ForksCount:0, NetworkCount:0, OpenIssuesCount:0, OpenIssues:0, StargazersCount:0, SubscribersCount:0, WatchersCount:0, Watchers:0, Size:0, AutoInit:false, Parent:github.Repository{}, Source:github.Repository{}, TemplateRepository:github.Repository{}, Organization:github.Organization{}, AllowRebaseMerge:false, AllowUpdateBranch:false, AllowSquashMerge:false, AllowMergeCommit:false, AllowAutoMerge:false, AllowForking:false, DeleteBranchOnMerge:false, UseSquashPRTitleAsDefault:false, SquashMergeCommitTitle:"", SquashMergeCommitMessage:"", MergeCommitTitle:"", MergeCommitMessage:"", Topics:[""], Archived:false, Disabled:false, License:github.License{}, Private:false, HasIssues:false, HasWiki:false, HasPages:false, HasProjects:false, HasDownloads:false, HasDiscussions:false, IsTemplate:false, LicenseTemplate:"", GitignoreTemplate:"", SecurityAndAnalysis:github.SecurityAndAnalysis{}, TeamID:0, URL:"", ArchiveURL:"", AssigneesURL:"", BlobsURL:"", BranchesURL:"", CollaboratorsURL:"", CommentsURL:"", CommitsURL:"", CompareURL:"", ContentsURL:"", ContributorsURL:"", DeploymentsURL:"", DownloadsURL:"", EventsURL:"", ForksURL:"", GitCommitsURL:"", GitRefsURL:"", GitTagsURL:"", HooksURL:"", IssueCommentURL:"", IssueEventsURL:"", IssuesURL:"", KeysURL:"", LabelsURL:"", LanguagesURL:"", MergesURL:"", MilestonesURL:"", NotificationsURL:"", PullsURL:"", ReleasesURL:"", StargazersURL:"", StatusesURL:"", SubscribersURL:"", SubscriptionURL:"", TagsURL:"", TreesURL:"", TeamsURL:"", Visibility:"", RoleName:""}`
 	if got := v.String(); got != want {
 		t.Errorf("Repository.String = %v, want %v", got, want)
 	}
@@ -1694,6 +1696,7 @@ func TestRepositoryRelease_String(t *testing.T) {
 		Body:                   String(""),
 		Draft:                  Bool(false),
 		Prerelease:             Bool(false),
+		MakeLatest:             String(""),
 		DiscussionCategoryName: String(""),
 		GenerateReleaseNotes:   Bool(false),
 		ID:                     Int64(0),
@@ -1708,7 +1711,7 @@ func TestRepositoryRelease_String(t *testing.T) {
 		Author:                 &User{},
 		NodeID:                 String(""),
 	}
-	want := `github.RepositoryRelease{TagName:"", TargetCommitish:"", Name:"", Body:"", Draft:false, Prerelease:false, DiscussionCategoryName:"", GenerateReleaseNotes:false, ID:0, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PublishedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, URL:"", HTMLURL:"", AssetsURL:"", UploadURL:"", ZipballURL:"", TarballURL:"", Author:github.User{}, NodeID:""}`
+	want := `github.RepositoryRelease{TagName:"", TargetCommitish:"", Name:"", Body:"", Draft:false, Prerelease:false, MakeLatest:"", DiscussionCategoryName:"", GenerateReleaseNotes:false, ID:0, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PublishedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, URL:"", HTMLURL:"", AssetsURL:"", UploadURL:"", ZipballURL:"", TarballURL:"", Author:github.User{}, NodeID:""}`
 	if got := v.String(); got != want {
 		t.Errorf("RepositoryRelease.String = %v, want %v", got, want)
 	}
diff --git a/github/github_test.go b/github/github_test.go
index c4ceb89b..53809399 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -517,6 +517,17 @@ func TestNewRequest(t *testing.T) {
 	if !strings.Contains(userAgent, Version) {
 		t.Errorf("NewRequest() User-Agent should contain %v, found %v", Version, userAgent)
 	}
+
+	apiVersion := req.Header.Get(headerAPIVersion)
+	if got, want := apiVersion, defaultAPIVersion; got != want {
+		t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
+	}
+
+	req, _ = c.NewRequest("GET", inURL, inBody, WithVersion("2022-11-29"))
+	apiVersion = req.Header.Get(headerAPIVersion)
+	if got, want := apiVersion, "2022-11-29"; got != want {
+		t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
+	}
 }
 
 func TestNewRequest_invalidJSON(t *testing.T) {
@@ -626,6 +637,17 @@ func TestNewFormRequest(t *testing.T) {
 	if got, want := req.Header.Get("User-Agent"), c.UserAgent; got != want {
 		t.Errorf("NewFormRequest() User-Agent is %v, want %v", got, want)
 	}
+
+	apiVersion := req.Header.Get(headerAPIVersion)
+	if got, want := apiVersion, defaultAPIVersion; got != want {
+		t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
+	}
+
+	req, _ = c.NewFormRequest(inURL, inBody, WithVersion("2022-11-29"))
+	apiVersion = req.Header.Get(headerAPIVersion)
+	if got, want := apiVersion, "2022-11-29"; got != want {
+		t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
+	}
 }
 
 func TestNewFormRequest_badURL(t *testing.T) {
@@ -680,6 +702,22 @@ func TestNewFormRequest_errorForNoTrailingSlash(t *testing.T) {
 	}
 }
 
+func TestNewUploadRequest_WithVersion(t *testing.T) {
+	c := NewClient(nil)
+	req, _ := c.NewUploadRequest("https://example.com/", nil, 0, "")
+
+	apiVersion := req.Header.Get(headerAPIVersion)
+	if got, want := apiVersion, defaultAPIVersion; got != want {
+		t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
+	}
+
+	req, _ = c.NewUploadRequest("https://example.com/", nil, 0, "", WithVersion("2022-11-29"))
+	apiVersion = req.Header.Get(headerAPIVersion)
+	if got, want := apiVersion, "2022-11-29"; got != want {
+		t.Errorf("NewRequest() %v header is %v, want %v", headerAPIVersion, got, want)
+	}
+}
+
 func TestNewUploadRequest_badURL(t *testing.T) {
 	c := NewClient(nil)
 	_, err := c.NewUploadRequest(":", nil, 0, "")
diff --git a/github/repos_actions_access_test.go b/github/repos_actions_access_test.go
new file mode 100644
index 00000000..7085ac7d
--- /dev/null
+++ b/github/repos_actions_access_test.go
@@ -0,0 +1,98 @@
+// Copyright 2022 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestRepositoriesService_GetActionsAccessLevel(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/permissions/access", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprintf(w, `{"access_level": "none"}`)
+	})
+
+	ctx := context.Background()
+	org, _, err := client.Repositories.GetActionsAccessLevel(ctx, "o", "r")
+	if err != nil {
+		t.Errorf("Repositories.GetActionsAccessLevel returned error: %v", err)
+	}
+	want := &RepositoryActionsAccessLevel{AccessLevel: String("none")}
+	if !cmp.Equal(org, want) {
+		t.Errorf("Repositories.GetActionsAccessLevel returned %+v, want %+v", org, want)
+	}
+
+	const methodName = "GetActionsAccessLevel"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.GetActionsAccessLevel(ctx, "\n", "\n")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.GetActionsAccessLevel(ctx, "o", "r")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_EditActionsAccessLevel(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	input := &RepositoryActionsAccessLevel{AccessLevel: String("organization")}
+
+	mux.HandleFunc("/repos/o/r/actions/permissions/access", func(w http.ResponseWriter, r *http.Request) {
+		v := new(RepositoryActionsAccessLevel)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, "PUT")
+		if !cmp.Equal(v, input) {
+			t.Errorf("Request body = %+v, want %+v", v, input)
+		}
+	})
+
+	ctx := context.Background()
+	_, err := client.Repositories.EditActionsAccessLevel(ctx, "o", "r", *input)
+	if err != nil {
+		t.Errorf("Repositories.EditActionsAccessLevel returned error: %v", err)
+	}
+
+	const methodName = "EditActionsAccessLevel"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Repositories.EditActionsAccessLevel(ctx, "\n", "\n", *input)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		resp, err := client.Repositories.EditActionsAccessLevel(ctx, "o", "r", *input)
+		return resp, err
+	})
+}
+
+func TestRepositoryActionsAccessLevel_Marshal(t *testing.T) {
+	testJSONMarshal(t, &ActionsPermissions{}, "{}")
+
+	u := &RepositoryActionsAccessLevel{
+		AccessLevel: String("enterprise"),
+	}
+
+	want := `{
+		"access_level": "enterprise"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_forks_test.go b/github/repos_forks_test.go
index 07be423b..d1a17f62 100644
--- a/github/repos_forks_test.go
+++ b/github/repos_forks_test.go
@@ -73,7 +73,7 @@ func TestRepositoriesService_CreateFork(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "POST")
-		testFormValues(t, r, values{"organization": "o", "name": "n", "default_branch_only": "true"})
+		testBody(t, r, `{"organization":"o","name":"n","default_branch_only":true}`+"\n")
 		fmt.Fprint(w, `{"id":1}`)
 	})
 
@@ -110,7 +110,7 @@ func TestRepositoriesService_CreateFork_deferred(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "POST")
-		testFormValues(t, r, values{"organization": "o", "name": "n", "default_branch_only": "true"})
+		testBody(t, r, `{"organization":"o","name":"n","default_branch_only":true}`+"\n")
 		// This response indicates the fork will happen asynchronously.
 		w.WriteHeader(http.StatusAccepted)
 		fmt.Fprint(w, `{"id":1}`)
diff --git a/github/repos_releases_test.go b/github/repos_releases_test.go
index 77a20ffe..73a9f3a5 100644
--- a/github/repos_releases_test.go
+++ b/github/repos_releases_test.go
@@ -708,6 +708,7 @@ func TestRepositoryReleaseRequest_Marshal(t *testing.T) {
 		Body:                   String("body"),
 		Draft:                  Bool(false),
 		Prerelease:             Bool(false),
+		MakeLatest:             String("legacy"),
 		DiscussionCategoryName: String("dcn"),
 	}
 
@@ -718,6 +719,7 @@ func TestRepositoryReleaseRequest_Marshal(t *testing.T) {
 		"body": "body",
 		"draft": false,
 		"prerelease": false,
+		"make_latest": "legacy",
 		"discussion_category_name": "dcn"
 	}`
 
@@ -774,6 +776,7 @@ func TestRepositoryRelease_Marshal(t *testing.T) {
 		Body:                   String("body"),
 		Draft:                  Bool(false),
 		Prerelease:             Bool(false),
+		MakeLatest:             String("legacy"),
 		DiscussionCategoryName: String("dcn"),
 		ID:                     Int64(1),
 		CreatedAt:              &Timestamp{referenceTime},
@@ -796,6 +799,7 @@ func TestRepositoryRelease_Marshal(t *testing.T) {
 		"body": "body",
 		"draft": false,
 		"prerelease": false,
+		"make_latest": "legacy",
 		"discussion_category_name": "dcn",
 		"id": 1,
 		"created_at": ` + referenceTimeStr + `,