github.com/google/go-github/v53@v53.2.0/github/github.go (about)

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  //go:generate go run gen-accessors.go
     7  //go:generate go run gen-stringify-test.go
     8  
     9  package github
    10  
    11  import (
    12  	"bytes"
    13  	"context"
    14  	"encoding/json"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"net/url"
    20  	"reflect"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/google/go-querystring/query"
    27  	"golang.org/x/oauth2"
    28  )
    29  
    30  const (
    31  	Version = "v53.2.0"
    32  
    33  	defaultAPIVersion = "2022-11-28"
    34  	defaultBaseURL    = "https://api.github.com/"
    35  	defaultUserAgent  = "go-github" + "/" + Version
    36  	uploadBaseURL     = "https://uploads.github.com/"
    37  
    38  	headerAPIVersion    = "X-GitHub-Api-Version"
    39  	headerRateLimit     = "X-RateLimit-Limit"
    40  	headerRateRemaining = "X-RateLimit-Remaining"
    41  	headerRateReset     = "X-RateLimit-Reset"
    42  	headerOTP           = "X-GitHub-OTP"
    43  	headerRetryAfter    = "Retry-After"
    44  
    45  	headerTokenExpiration = "GitHub-Authentication-Token-Expiration"
    46  
    47  	mediaTypeV3                = "application/vnd.github.v3+json"
    48  	defaultMediaType           = "application/octet-stream"
    49  	mediaTypeV3SHA             = "application/vnd.github.v3.sha"
    50  	mediaTypeV3Diff            = "application/vnd.github.v3.diff"
    51  	mediaTypeV3Patch           = "application/vnd.github.v3.patch"
    52  	mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json"
    53  	mediaTypeIssueImportAPI    = "application/vnd.github.golden-comet-preview+json"
    54  
    55  	// Media Type values to access preview APIs
    56  	// These media types will be added to the API request as headers
    57  	// and used to enable particular features on GitHub API that are still in preview.
    58  	// After some time, specific media types will be promoted (to a "stable" state).
    59  	// From then on, the preview headers are not required anymore to activate the additional
    60  	// feature on GitHub.com's API. However, this API header might still be needed for users
    61  	// to run a GitHub Enterprise Server on-premise.
    62  	// It's not uncommon for GitHub Enterprise Server customers to run older versions which
    63  	// would probably rely on the preview headers for some time.
    64  	// While the header promotion is going out for GitHub.com, it may be some time before it
    65  	// even arrives in GitHub Enterprise Server.
    66  	// We keep those preview headers around to avoid breaking older GitHub Enterprise Server
    67  	// versions. Additionally, non-functional (preview) headers don't create any side effects
    68  	// on GitHub Cloud version.
    69  	//
    70  	// See https://github.com/google/go-github/pull/2125 for full context.
    71  
    72  	// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
    73  	mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
    74  
    75  	// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/
    76  	mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"
    77  
    78  	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
    79  	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
    80  
    81  	// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/
    82  	mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"
    83  
    84  	// https://developer.github.com/changes/2016-05-12-reactions-api-preview/
    85  	mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"
    86  
    87  	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
    88  	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
    89  
    90  	// https://developer.github.com/changes/2016-09-14-projects-api/
    91  	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
    92  
    93  	// https://developer.github.com/changes/2017-01-05-commit-search-api/
    94  	mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"
    95  
    96  	// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/
    97  	mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"
    98  
    99  	// https://developer.github.com/changes/2017-05-23-coc-api/
   100  	mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"
   101  
   102  	// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/
   103  	mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"
   104  
   105  	// https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/
   106  	mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json"
   107  
   108  	// https://developer.github.com/changes/2018-05-07-new-checks-api-public-beta/
   109  	mediaTypeCheckRunsPreview = "application/vnd.github.antiope-preview+json"
   110  
   111  	// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/
   112  	mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"
   113  
   114  	// https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/
   115  	mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json"
   116  
   117  	// https://developer.github.com/changes/2018-09-05-project-card-events/
   118  	mediaTypeProjectCardDetailsPreview = "application/vnd.github.starfox-preview+json"
   119  
   120  	// https://developer.github.com/changes/2018-12-18-interactions-preview/
   121  	mediaTypeInteractionRestrictionsPreview = "application/vnd.github.sombra-preview+json"
   122  
   123  	// https://developer.github.com/changes/2019-03-14-enabling-disabling-pages/
   124  	mediaTypeEnablePagesAPIPreview = "application/vnd.github.switcheroo-preview+json"
   125  
   126  	// https://developer.github.com/changes/2019-04-24-vulnerability-alerts/
   127  	mediaTypeRequiredVulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json"
   128  
   129  	// https://developer.github.com/changes/2019-06-04-automated-security-fixes/
   130  	mediaTypeRequiredAutomatedSecurityFixesPreview = "application/vnd.github.london-preview+json"
   131  
   132  	// https://developer.github.com/changes/2019-05-29-update-branch-api/
   133  	mediaTypeUpdatePullRequestBranchPreview = "application/vnd.github.lydian-preview+json"
   134  
   135  	// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
   136  	mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"
   137  
   138  	// https://docs.github.com/en/rest/previews/#repository-creation-permissions
   139  	mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
   140  
   141  	// https://docs.github.com/en/rest/previews/#create-and-use-repository-templates
   142  	mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"
   143  
   144  	// https://developer.github.com/changes/2019-10-03-multi-line-comments/
   145  	mediaTypeMultiLineCommentsPreview = "application/vnd.github.comfort-fade-preview+json"
   146  
   147  	// https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/
   148  	mediaTypeOAuthAppPreview = "application/vnd.github.doctor-strange-preview+json"
   149  
   150  	// https://developer.github.com/changes/2019-12-03-internal-visibility-changes/
   151  	mediaTypeRepositoryVisibilityPreview = "application/vnd.github.nebula-preview+json"
   152  
   153  	// https://developer.github.com/changes/2018-12-10-content-attachments-api/
   154  	mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json"
   155  )
   156  
   157  var errNonNilContext = errors.New("context must be non-nil")
   158  
   159  // A Client manages communication with the GitHub API.
   160  type Client struct {
   161  	clientMu sync.Mutex   // clientMu protects the client during calls that modify the CheckRedirect func.
   162  	client   *http.Client // HTTP client used to communicate with the API.
   163  
   164  	// Base URL for API requests. Defaults to the public GitHub API, but can be
   165  	// set to a domain endpoint to use with GitHub Enterprise. BaseURL should
   166  	// always be specified with a trailing slash.
   167  	BaseURL *url.URL
   168  
   169  	// Base URL for uploading files.
   170  	UploadURL *url.URL
   171  
   172  	// User agent used when communicating with the GitHub API.
   173  	UserAgent string
   174  
   175  	rateMu                  sync.Mutex
   176  	rateLimits              [categories]Rate // Rate limits for the client as determined by the most recent API calls.
   177  	secondaryRateLimitReset time.Time        // Secondary rate limit reset for the client as determined by the most recent API calls.
   178  
   179  	common service // Reuse a single struct instead of allocating one for each service on the heap.
   180  
   181  	// Services used for talking to different parts of the GitHub API.
   182  	Actions        *ActionsService
   183  	Activity       *ActivityService
   184  	Admin          *AdminService
   185  	Apps           *AppsService
   186  	Authorizations *AuthorizationsService
   187  	Billing        *BillingService
   188  	Checks         *ChecksService
   189  	CodeScanning   *CodeScanningService
   190  	Codespaces     *CodespacesService
   191  	Dependabot     *DependabotService
   192  	Enterprise     *EnterpriseService
   193  	Gists          *GistsService
   194  	Git            *GitService
   195  	Gitignores     *GitignoresService
   196  	Interactions   *InteractionsService
   197  	IssueImport    *IssueImportService
   198  	Issues         *IssuesService
   199  	Licenses       *LicensesService
   200  	Marketplace    *MarketplaceService
   201  	Migrations     *MigrationService
   202  	Organizations  *OrganizationsService
   203  	Projects       *ProjectsService
   204  	PullRequests   *PullRequestsService
   205  	Reactions      *ReactionsService
   206  	Repositories   *RepositoriesService
   207  	SCIM           *SCIMService
   208  	Search         *SearchService
   209  	SecretScanning *SecretScanningService
   210  	Teams          *TeamsService
   211  	Users          *UsersService
   212  }
   213  
   214  type service struct {
   215  	client *Client
   216  }
   217  
   218  // Client returns the http.Client used by this GitHub client.
   219  func (c *Client) Client() *http.Client {
   220  	c.clientMu.Lock()
   221  	defer c.clientMu.Unlock()
   222  	clientCopy := *c.client
   223  	return &clientCopy
   224  }
   225  
   226  // ListOptions specifies the optional parameters to various List methods that
   227  // support offset pagination.
   228  type ListOptions struct {
   229  	// For paginated result sets, page of results to retrieve.
   230  	Page int `url:"page,omitempty"`
   231  
   232  	// For paginated result sets, the number of results to include per page.
   233  	PerPage int `url:"per_page,omitempty"`
   234  }
   235  
   236  // ListCursorOptions specifies the optional parameters to various List methods that
   237  // support cursor pagination.
   238  type ListCursorOptions struct {
   239  	// For paginated result sets, page of results to retrieve.
   240  	Page string `url:"page,omitempty"`
   241  
   242  	// For paginated result sets, the number of results to include per page.
   243  	PerPage int `url:"per_page,omitempty"`
   244  
   245  	// For paginated result sets, the number of results per page (max 100), starting from the first matching result.
   246  	// This parameter must not be used in combination with last.
   247  	First int `url:"first,omitempty"`
   248  
   249  	// For paginated result sets, the number of results per page (max 100), starting from the last matching result.
   250  	// This parameter must not be used in combination with first.
   251  	Last int `url:"last,omitempty"`
   252  
   253  	// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
   254  	After string `url:"after,omitempty"`
   255  
   256  	// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
   257  	Before string `url:"before,omitempty"`
   258  
   259  	// A cursor, as given in the Link header. If specified, the query continues the search using this cursor.
   260  	Cursor string `url:"cursor,omitempty"`
   261  }
   262  
   263  // UploadOptions specifies the parameters to methods that support uploads.
   264  type UploadOptions struct {
   265  	Name      string `url:"name,omitempty"`
   266  	Label     string `url:"label,omitempty"`
   267  	MediaType string `url:"-"`
   268  }
   269  
   270  // RawType represents type of raw format of a request instead of JSON.
   271  type RawType uint8
   272  
   273  const (
   274  	// Diff format.
   275  	Diff RawType = 1 + iota
   276  	// Patch format.
   277  	Patch
   278  )
   279  
   280  // RawOptions specifies parameters when user wants to get raw format of
   281  // a response instead of JSON.
   282  type RawOptions struct {
   283  	Type RawType
   284  }
   285  
   286  // addOptions adds the parameters in opts as URL query parameters to s. opts
   287  // must be a struct whose fields may contain "url" tags.
   288  func addOptions(s string, opts interface{}) (string, error) {
   289  	v := reflect.ValueOf(opts)
   290  	if v.Kind() == reflect.Ptr && v.IsNil() {
   291  		return s, nil
   292  	}
   293  
   294  	u, err := url.Parse(s)
   295  	if err != nil {
   296  		return s, err
   297  	}
   298  
   299  	qs, err := query.Values(opts)
   300  	if err != nil {
   301  		return s, err
   302  	}
   303  
   304  	u.RawQuery = qs.Encode()
   305  	return u.String(), nil
   306  }
   307  
   308  // NewClient returns a new GitHub API client. If a nil httpClient is
   309  // provided, a new http.Client will be used. To use API methods which require
   310  // authentication, provide an http.Client that will perform the authentication
   311  // for you (such as that provided by the golang.org/x/oauth2 library).
   312  func NewClient(httpClient *http.Client) *Client {
   313  	if httpClient == nil {
   314  		httpClient = &http.Client{}
   315  	}
   316  	baseURL, _ := url.Parse(defaultBaseURL)
   317  	uploadURL, _ := url.Parse(uploadBaseURL)
   318  
   319  	c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: defaultUserAgent, UploadURL: uploadURL}
   320  	c.common.client = c
   321  	c.Actions = (*ActionsService)(&c.common)
   322  	c.Activity = (*ActivityService)(&c.common)
   323  	c.Admin = (*AdminService)(&c.common)
   324  	c.Apps = (*AppsService)(&c.common)
   325  	c.Authorizations = (*AuthorizationsService)(&c.common)
   326  	c.Billing = (*BillingService)(&c.common)
   327  	c.Checks = (*ChecksService)(&c.common)
   328  	c.CodeScanning = (*CodeScanningService)(&c.common)
   329  	c.Codespaces = (*CodespacesService)(&c.common)
   330  	c.Dependabot = (*DependabotService)(&c.common)
   331  	c.Enterprise = (*EnterpriseService)(&c.common)
   332  	c.Gists = (*GistsService)(&c.common)
   333  	c.Git = (*GitService)(&c.common)
   334  	c.Gitignores = (*GitignoresService)(&c.common)
   335  	c.Interactions = (*InteractionsService)(&c.common)
   336  	c.IssueImport = (*IssueImportService)(&c.common)
   337  	c.Issues = (*IssuesService)(&c.common)
   338  	c.Licenses = (*LicensesService)(&c.common)
   339  	c.Marketplace = &MarketplaceService{client: c}
   340  	c.Migrations = (*MigrationService)(&c.common)
   341  	c.Organizations = (*OrganizationsService)(&c.common)
   342  	c.Projects = (*ProjectsService)(&c.common)
   343  	c.PullRequests = (*PullRequestsService)(&c.common)
   344  	c.Reactions = (*ReactionsService)(&c.common)
   345  	c.Repositories = (*RepositoriesService)(&c.common)
   346  	c.SCIM = (*SCIMService)(&c.common)
   347  	c.Search = (*SearchService)(&c.common)
   348  	c.SecretScanning = (*SecretScanningService)(&c.common)
   349  	c.Teams = (*TeamsService)(&c.common)
   350  	c.Users = (*UsersService)(&c.common)
   351  	return c
   352  }
   353  
   354  // NewClientWithEnvProxy enhances NewClient with the HttpProxy env.
   355  func NewClientWithEnvProxy() *Client {
   356  	return NewClient(&http.Client{Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}})
   357  }
   358  
   359  // NewTokenClient returns a new GitHub API client authenticated with the provided token.
   360  func NewTokenClient(ctx context.Context, token string) *Client {
   361  	return NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})))
   362  }
   363  
   364  // NewEnterpriseClient returns a new GitHub API client with provided
   365  // base URL and upload URL (often is your GitHub Enterprise hostname).
   366  // If the base URL does not have the suffix "/api/v3/", it will be added automatically.
   367  // If the upload URL does not have the suffix "/api/uploads", it will be added automatically.
   368  // If a nil httpClient is provided, a new http.Client will be used.
   369  //
   370  // Note that NewEnterpriseClient is a convenience helper only;
   371  // its behavior is equivalent to using NewClient, followed by setting
   372  // the BaseURL and UploadURL fields.
   373  //
   374  // Another important thing is that by default, the GitHub Enterprise URL format
   375  // should be http(s)://[hostname]/api/v3/ or you will always receive the 406 status code.
   376  // The upload URL format should be http(s)://[hostname]/api/uploads/.
   377  func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) {
   378  	baseEndpoint, err := url.Parse(baseURL)
   379  	if err != nil {
   380  		return nil, err
   381  	}
   382  
   383  	if !strings.HasSuffix(baseEndpoint.Path, "/") {
   384  		baseEndpoint.Path += "/"
   385  	}
   386  	if !strings.HasSuffix(baseEndpoint.Path, "/api/v3/") &&
   387  		!strings.HasPrefix(baseEndpoint.Host, "api.") &&
   388  		!strings.Contains(baseEndpoint.Host, ".api.") {
   389  		baseEndpoint.Path += "api/v3/"
   390  	}
   391  
   392  	uploadEndpoint, err := url.Parse(uploadURL)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  
   397  	if !strings.HasSuffix(uploadEndpoint.Path, "/") {
   398  		uploadEndpoint.Path += "/"
   399  	}
   400  	if !strings.HasSuffix(uploadEndpoint.Path, "/api/uploads/") &&
   401  		!strings.HasPrefix(uploadEndpoint.Host, "api.") &&
   402  		!strings.Contains(uploadEndpoint.Host, ".api.") {
   403  		uploadEndpoint.Path += "api/uploads/"
   404  	}
   405  
   406  	c := NewClient(httpClient)
   407  	c.BaseURL = baseEndpoint
   408  	c.UploadURL = uploadEndpoint
   409  	return c, nil
   410  }
   411  
   412  // RequestOption represents an option that can modify an http.Request.
   413  type RequestOption func(req *http.Request)
   414  
   415  // WithVersion overrides the GitHub v3 API version for this individual request.
   416  // For more information, see:
   417  // https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/
   418  func WithVersion(version string) RequestOption {
   419  	return func(req *http.Request) {
   420  		req.Header.Set(headerAPIVersion, version)
   421  	}
   422  }
   423  
   424  // NewRequest creates an API request. A relative URL can be provided in urlStr,
   425  // in which case it is resolved relative to the BaseURL of the Client.
   426  // Relative URLs should always be specified without a preceding slash. If
   427  // specified, the value pointed to by body is JSON encoded and included as the
   428  // request body.
   429  func (c *Client) NewRequest(method, urlStr string, body interface{}, opts ...RequestOption) (*http.Request, error) {
   430  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   431  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   432  	}
   433  
   434  	u, err := c.BaseURL.Parse(urlStr)
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  
   439  	var buf io.ReadWriter
   440  	if body != nil {
   441  		buf = &bytes.Buffer{}
   442  		enc := json.NewEncoder(buf)
   443  		enc.SetEscapeHTML(false)
   444  		err := enc.Encode(body)
   445  		if err != nil {
   446  			return nil, err
   447  		}
   448  	}
   449  
   450  	req, err := http.NewRequest(method, u.String(), buf)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  
   455  	if body != nil {
   456  		req.Header.Set("Content-Type", "application/json")
   457  	}
   458  	req.Header.Set("Accept", mediaTypeV3)
   459  	if c.UserAgent != "" {
   460  		req.Header.Set("User-Agent", c.UserAgent)
   461  	}
   462  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   463  
   464  	for _, opt := range opts {
   465  		opt(req)
   466  	}
   467  
   468  	return req, nil
   469  }
   470  
   471  // NewFormRequest creates an API request. A relative URL can be provided in urlStr,
   472  // in which case it is resolved relative to the BaseURL of the Client.
   473  // Relative URLs should always be specified without a preceding slash.
   474  // Body is sent with Content-Type: application/x-www-form-urlencoded.
   475  func (c *Client) NewFormRequest(urlStr string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
   476  	if !strings.HasSuffix(c.BaseURL.Path, "/") {
   477  		return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL)
   478  	}
   479  
   480  	u, err := c.BaseURL.Parse(urlStr)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  
   485  	req, err := http.NewRequest(http.MethodPost, u.String(), body)
   486  	if err != nil {
   487  		return nil, err
   488  	}
   489  
   490  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   491  	req.Header.Set("Accept", mediaTypeV3)
   492  	if c.UserAgent != "" {
   493  		req.Header.Set("User-Agent", c.UserAgent)
   494  	}
   495  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   496  
   497  	for _, opt := range opts {
   498  		opt(req)
   499  	}
   500  
   501  	return req, nil
   502  }
   503  
   504  // NewUploadRequest creates an upload request. A relative URL can be provided in
   505  // urlStr, in which case it is resolved relative to the UploadURL of the Client.
   506  // Relative URLs should always be specified without a preceding slash.
   507  func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string, opts ...RequestOption) (*http.Request, error) {
   508  	if !strings.HasSuffix(c.UploadURL.Path, "/") {
   509  		return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL)
   510  	}
   511  	u, err := c.UploadURL.Parse(urlStr)
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  
   516  	req, err := http.NewRequest("POST", u.String(), reader)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	req.ContentLength = size
   522  
   523  	if mediaType == "" {
   524  		mediaType = defaultMediaType
   525  	}
   526  	req.Header.Set("Content-Type", mediaType)
   527  	req.Header.Set("Accept", mediaTypeV3)
   528  	req.Header.Set("User-Agent", c.UserAgent)
   529  	req.Header.Set(headerAPIVersion, defaultAPIVersion)
   530  
   531  	for _, opt := range opts {
   532  		opt(req)
   533  	}
   534  
   535  	return req, nil
   536  }
   537  
   538  // Response is a GitHub API response. This wraps the standard http.Response
   539  // returned from GitHub and provides convenient access to things like
   540  // pagination links.
   541  type Response struct {
   542  	*http.Response
   543  
   544  	// These fields provide the page values for paginating through a set of
   545  	// results. Any or all of these may be set to the zero value for
   546  	// responses that are not part of a paginated set, or for which there
   547  	// are no additional pages.
   548  	//
   549  	// These fields support what is called "offset pagination" and should
   550  	// be used with the ListOptions struct.
   551  	NextPage  int
   552  	PrevPage  int
   553  	FirstPage int
   554  	LastPage  int
   555  
   556  	// Additionally, some APIs support "cursor pagination" instead of offset.
   557  	// This means that a token points directly to the next record which
   558  	// can lead to O(1) performance compared to O(n) performance provided
   559  	// by offset pagination.
   560  	//
   561  	// For APIs that support cursor pagination (such as
   562  	// TeamsService.ListIDPGroupsInOrganization), the following field
   563  	// will be populated to point to the next page.
   564  	//
   565  	// To use this token, set ListCursorOptions.Page to this value before
   566  	// calling the endpoint again.
   567  	NextPageToken string
   568  
   569  	// For APIs that support cursor pagination, such as RepositoriesService.ListHookDeliveries,
   570  	// the following field will be populated to point to the next page.
   571  	// Set ListCursorOptions.Cursor to this value when calling the endpoint again.
   572  	Cursor string
   573  
   574  	// For APIs that support before/after pagination, such as OrganizationsService.AuditLog.
   575  	Before string
   576  	After  string
   577  
   578  	// Explicitly specify the Rate type so Rate's String() receiver doesn't
   579  	// propagate to Response.
   580  	Rate Rate
   581  
   582  	// token's expiration date. Timestamp is 0001-01-01 when token doesn't expire.
   583  	// So it is valid for TokenExpiration.Equal(Timestamp{}) or TokenExpiration.Time.After(time.Now())
   584  	TokenExpiration Timestamp
   585  }
   586  
   587  // newResponse creates a new Response for the provided http.Response.
   588  // r must not be nil.
   589  func newResponse(r *http.Response) *Response {
   590  	response := &Response{Response: r}
   591  	response.populatePageValues()
   592  	response.Rate = parseRate(r)
   593  	response.TokenExpiration = parseTokenExpiration(r)
   594  	return response
   595  }
   596  
   597  // populatePageValues parses the HTTP Link response headers and populates the
   598  // various pagination link values in the Response.
   599  func (r *Response) populatePageValues() {
   600  	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
   601  		for _, link := range strings.Split(links[0], ",") {
   602  			segments := strings.Split(strings.TrimSpace(link), ";")
   603  
   604  			// link must at least have href and rel
   605  			if len(segments) < 2 {
   606  				continue
   607  			}
   608  
   609  			// ensure href is properly formatted
   610  			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
   611  				continue
   612  			}
   613  
   614  			// try to pull out page parameter
   615  			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
   616  			if err != nil {
   617  				continue
   618  			}
   619  
   620  			q := url.Query()
   621  
   622  			if cursor := q.Get("cursor"); cursor != "" {
   623  				for _, segment := range segments[1:] {
   624  					switch strings.TrimSpace(segment) {
   625  					case `rel="next"`:
   626  						r.Cursor = cursor
   627  					}
   628  				}
   629  
   630  				continue
   631  			}
   632  
   633  			page := q.Get("page")
   634  			since := q.Get("since")
   635  			before := q.Get("before")
   636  			after := q.Get("after")
   637  
   638  			if page == "" && before == "" && after == "" && since == "" {
   639  				continue
   640  			}
   641  
   642  			if since != "" && page == "" {
   643  				page = since
   644  			}
   645  
   646  			for _, segment := range segments[1:] {
   647  				switch strings.TrimSpace(segment) {
   648  				case `rel="next"`:
   649  					if r.NextPage, err = strconv.Atoi(page); err != nil {
   650  						r.NextPageToken = page
   651  					}
   652  					r.After = after
   653  				case `rel="prev"`:
   654  					r.PrevPage, _ = strconv.Atoi(page)
   655  					r.Before = before
   656  				case `rel="first"`:
   657  					r.FirstPage, _ = strconv.Atoi(page)
   658  				case `rel="last"`:
   659  					r.LastPage, _ = strconv.Atoi(page)
   660  				}
   661  			}
   662  		}
   663  	}
   664  }
   665  
   666  // parseRate parses the rate related headers.
   667  func parseRate(r *http.Response) Rate {
   668  	var rate Rate
   669  	if limit := r.Header.Get(headerRateLimit); limit != "" {
   670  		rate.Limit, _ = strconv.Atoi(limit)
   671  	}
   672  	if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
   673  		rate.Remaining, _ = strconv.Atoi(remaining)
   674  	}
   675  	if reset := r.Header.Get(headerRateReset); reset != "" {
   676  		if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
   677  			rate.Reset = Timestamp{time.Unix(v, 0)}
   678  		}
   679  	}
   680  	return rate
   681  }
   682  
   683  // parseSecondaryRate parses the secondary rate related headers,
   684  // and returns the time to retry after.
   685  func parseSecondaryRate(r *http.Response) *time.Duration {
   686  	// According to GitHub support, the "Retry-After" header value will be
   687  	// an integer which represents the number of seconds that one should
   688  	// wait before resuming making requests.
   689  	if v := r.Header.Get(headerRetryAfter); v != "" {
   690  		retryAfterSeconds, _ := strconv.ParseInt(v, 10, 64) // Error handling is noop.
   691  		retryAfter := time.Duration(retryAfterSeconds) * time.Second
   692  		return &retryAfter
   693  	}
   694  
   695  	// According to GitHub support, endpoints might return x-ratelimit-reset instead,
   696  	// as an integer which represents the number of seconds since epoch UTC,
   697  	// represting the time to resume making requests.
   698  	if v := r.Header.Get(headerRateReset); v != "" {
   699  		secondsSinceEpoch, _ := strconv.ParseInt(v, 10, 64) // Error handling is noop.
   700  		retryAfter := time.Until(time.Unix(secondsSinceEpoch, 0))
   701  		return &retryAfter
   702  	}
   703  
   704  	return nil
   705  }
   706  
   707  // parseTokenExpiration parses the TokenExpiration related headers.
   708  // Returns 0001-01-01 if the header is not defined or could not be parsed.
   709  func parseTokenExpiration(r *http.Response) Timestamp {
   710  	if v := r.Header.Get(headerTokenExpiration); v != "" {
   711  		if t, err := time.Parse("2006-01-02 15:04:05 MST", v); err == nil {
   712  			return Timestamp{t.Local()}
   713  		}
   714  		// Some tokens include the timezone offset instead of the timezone.
   715  		// https://github.com/google/go-github/issues/2649
   716  		if t, err := time.Parse("2006-01-02 15:04:05 -0700", v); err == nil {
   717  			return Timestamp{t.Local()}
   718  		}
   719  	}
   720  	return Timestamp{} // 0001-01-01 00:00:00
   721  }
   722  
   723  type requestContext uint8
   724  
   725  const (
   726  	bypassRateLimitCheck requestContext = iota
   727  )
   728  
   729  // BareDo sends an API request and lets you handle the api response. If an error
   730  // or API Error occurs, the error will contain more information. Otherwise you
   731  // are supposed to read and close the response's Body. If rate limit is exceeded
   732  // and reset time is in the future, BareDo returns *RateLimitError immediately
   733  // without making a network API call.
   734  //
   735  // The provided ctx must be non-nil, if it is nil an error is returned. If it is
   736  // canceled or times out, ctx.Err() will be returned.
   737  func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, error) {
   738  	if ctx == nil {
   739  		return nil, errNonNilContext
   740  	}
   741  
   742  	req = withContext(ctx, req)
   743  
   744  	rateLimitCategory := category(req.Method, req.URL.Path)
   745  
   746  	if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil {
   747  		// If we've hit rate limit, don't make further requests before Reset time.
   748  		if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil {
   749  			return &Response{
   750  				Response: err.Response,
   751  				Rate:     err.Rate,
   752  			}, err
   753  		}
   754  		// If we've hit a secondary rate limit, don't make further requests before Retry After.
   755  		if err := c.checkSecondaryRateLimitBeforeDo(ctx, req); err != nil {
   756  			return &Response{
   757  				Response: err.Response,
   758  			}, err
   759  		}
   760  	}
   761  
   762  	resp, err := c.client.Do(req)
   763  	if err != nil {
   764  		// If we got an error, and the context has been canceled,
   765  		// the context's error is probably more useful.
   766  		select {
   767  		case <-ctx.Done():
   768  			return nil, ctx.Err()
   769  		default:
   770  		}
   771  
   772  		// If the error type is *url.Error, sanitize its URL before returning.
   773  		if e, ok := err.(*url.Error); ok {
   774  			if url, err := url.Parse(e.URL); err == nil {
   775  				e.URL = sanitizeURL(url).String()
   776  				return nil, e
   777  			}
   778  		}
   779  
   780  		return nil, err
   781  	}
   782  
   783  	response := newResponse(resp)
   784  
   785  	// Don't update the rate limits if this was a cached response.
   786  	// X-From-Cache is set by https://github.com/gregjones/httpcache
   787  	if response.Header.Get("X-From-Cache") == "" {
   788  		c.rateMu.Lock()
   789  		c.rateLimits[rateLimitCategory] = response.Rate
   790  		c.rateMu.Unlock()
   791  	}
   792  
   793  	err = CheckResponse(resp)
   794  	if err != nil {
   795  		defer resp.Body.Close()
   796  		// Special case for AcceptedErrors. If an AcceptedError
   797  		// has been encountered, the response's payload will be
   798  		// added to the AcceptedError and returned.
   799  		//
   800  		// Issue #1022
   801  		aerr, ok := err.(*AcceptedError)
   802  		if ok {
   803  			b, readErr := io.ReadAll(resp.Body)
   804  			if readErr != nil {
   805  				return response, readErr
   806  			}
   807  
   808  			aerr.Raw = b
   809  			err = aerr
   810  		}
   811  
   812  		// Update the secondary rate limit if we hit it.
   813  		rerr, ok := err.(*AbuseRateLimitError)
   814  		if ok && rerr.RetryAfter != nil {
   815  			c.rateMu.Lock()
   816  			c.secondaryRateLimitReset = time.Now().Add(*rerr.RetryAfter)
   817  			c.rateMu.Unlock()
   818  		}
   819  	}
   820  	return response, err
   821  }
   822  
   823  // Do sends an API request and returns the API response. The API response is
   824  // JSON decoded and stored in the value pointed to by v, or returned as an
   825  // error if an API error has occurred. If v implements the io.Writer interface,
   826  // the raw response body will be written to v, without attempting to first
   827  // decode it. If v is nil, and no error hapens, the response is returned as is.
   828  // If rate limit is exceeded and reset time is in the future, Do returns
   829  // *RateLimitError immediately without making a network API call.
   830  //
   831  // The provided ctx must be non-nil, if it is nil an error is returned. If it
   832  // is canceled or times out, ctx.Err() will be returned.
   833  func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
   834  	resp, err := c.BareDo(ctx, req)
   835  	if err != nil {
   836  		return resp, err
   837  	}
   838  	defer resp.Body.Close()
   839  
   840  	switch v := v.(type) {
   841  	case nil:
   842  	case io.Writer:
   843  		_, err = io.Copy(v, resp.Body)
   844  	default:
   845  		decErr := json.NewDecoder(resp.Body).Decode(v)
   846  		if decErr == io.EOF {
   847  			decErr = nil // ignore EOF errors caused by empty response body
   848  		}
   849  		if decErr != nil {
   850  			err = decErr
   851  		}
   852  	}
   853  	return resp, err
   854  }
   855  
   856  // checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   857  // current client state in order to quickly check if *RateLimitError can be immediately returned
   858  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   859  // Otherwise it returns nil, and Client.Do should proceed normally.
   860  func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError {
   861  	c.rateMu.Lock()
   862  	rate := c.rateLimits[rateLimitCategory]
   863  	c.rateMu.Unlock()
   864  	if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) {
   865  		// Create a fake response.
   866  		resp := &http.Response{
   867  			Status:     http.StatusText(http.StatusForbidden),
   868  			StatusCode: http.StatusForbidden,
   869  			Request:    req,
   870  			Header:     make(http.Header),
   871  			Body:       io.NopCloser(strings.NewReader("")),
   872  		}
   873  		return &RateLimitError{
   874  			Rate:     rate,
   875  			Response: resp,
   876  			Message:  fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time),
   877  		}
   878  	}
   879  
   880  	return nil
   881  }
   882  
   883  // checkSecondaryRateLimitBeforeDo does not make any network calls, but uses existing knowledge from
   884  // current client state in order to quickly check if *AbuseRateLimitError can be immediately returned
   885  // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.
   886  // Otherwise it returns nil, and Client.Do should proceed normally.
   887  func (c *Client) checkSecondaryRateLimitBeforeDo(ctx context.Context, req *http.Request) *AbuseRateLimitError {
   888  	c.rateMu.Lock()
   889  	secondary := c.secondaryRateLimitReset
   890  	c.rateMu.Unlock()
   891  	if !secondary.IsZero() && time.Now().Before(secondary) {
   892  		// Create a fake response.
   893  		resp := &http.Response{
   894  			Status:     http.StatusText(http.StatusForbidden),
   895  			StatusCode: http.StatusForbidden,
   896  			Request:    req,
   897  			Header:     make(http.Header),
   898  			Body:       io.NopCloser(strings.NewReader("")),
   899  		}
   900  
   901  		retryAfter := time.Until(secondary)
   902  		return &AbuseRateLimitError{
   903  			Response:   resp,
   904  			Message:    fmt.Sprintf("API secondary rate limit exceeded until %v, not making remote request.", secondary),
   905  			RetryAfter: &retryAfter,
   906  		}
   907  	}
   908  
   909  	return nil
   910  }
   911  
   912  // compareHTTPResponse returns whether two http.Response objects are equal or not.
   913  // Currently, only StatusCode is checked. This function is used when implementing the
   914  // Is(error) bool interface for the custom error types in this package.
   915  func compareHTTPResponse(r1, r2 *http.Response) bool {
   916  	if r1 == nil && r2 == nil {
   917  		return true
   918  	}
   919  
   920  	if r1 != nil && r2 != nil {
   921  		return r1.StatusCode == r2.StatusCode
   922  	}
   923  	return false
   924  }
   925  
   926  /*
   927  An ErrorResponse reports one or more errors caused by an API request.
   928  
   929  GitHub API docs: https://docs.github.com/en/rest/#client-errors
   930  */
   931  type ErrorResponse struct {
   932  	Response *http.Response `json:"-"`       // HTTP response that caused this error
   933  	Message  string         `json:"message"` // error message
   934  	Errors   []Error        `json:"errors"`  // more detail on individual errors
   935  	// Block is only populated on certain types of errors such as code 451.
   936  	Block *ErrorBlock `json:"block,omitempty"`
   937  	// Most errors will also include a documentation_url field pointing
   938  	// to some content that might help you resolve the error, see
   939  	// https://docs.github.com/en/rest/#client-errors
   940  	DocumentationURL string `json:"documentation_url,omitempty"`
   941  }
   942  
   943  // ErrorBlock contains a further explanation for the reason of an error.
   944  // See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/
   945  // for more information.
   946  type ErrorBlock struct {
   947  	Reason    string     `json:"reason,omitempty"`
   948  	CreatedAt *Timestamp `json:"created_at,omitempty"`
   949  }
   950  
   951  func (r *ErrorResponse) Error() string {
   952  	return fmt.Sprintf("%v %v: %d %v %+v",
   953  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
   954  		r.Response.StatusCode, r.Message, r.Errors)
   955  }
   956  
   957  // Is returns whether the provided error equals this error.
   958  func (r *ErrorResponse) Is(target error) bool {
   959  	v, ok := target.(*ErrorResponse)
   960  	if !ok {
   961  		return false
   962  	}
   963  
   964  	if r.Message != v.Message || (r.DocumentationURL != v.DocumentationURL) ||
   965  		!compareHTTPResponse(r.Response, v.Response) {
   966  		return false
   967  	}
   968  
   969  	// Compare Errors.
   970  	if len(r.Errors) != len(v.Errors) {
   971  		return false
   972  	}
   973  	for idx := range r.Errors {
   974  		if r.Errors[idx] != v.Errors[idx] {
   975  			return false
   976  		}
   977  	}
   978  
   979  	// Compare Block.
   980  	if (r.Block != nil && v.Block == nil) || (r.Block == nil && v.Block != nil) {
   981  		return false
   982  	}
   983  	if r.Block != nil && v.Block != nil {
   984  		if r.Block.Reason != v.Block.Reason {
   985  			return false
   986  		}
   987  		if (r.Block.CreatedAt != nil && v.Block.CreatedAt == nil) || (r.Block.CreatedAt ==
   988  			nil && v.Block.CreatedAt != nil) {
   989  			return false
   990  		}
   991  		if r.Block.CreatedAt != nil && v.Block.CreatedAt != nil {
   992  			if *(r.Block.CreatedAt) != *(v.Block.CreatedAt) {
   993  				return false
   994  			}
   995  		}
   996  	}
   997  
   998  	return true
   999  }
  1000  
  1001  // TwoFactorAuthError occurs when using HTTP Basic Authentication for a user
  1002  // that has two-factor authentication enabled. The request can be reattempted
  1003  // by providing a one-time password in the request.
  1004  type TwoFactorAuthError ErrorResponse
  1005  
  1006  func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
  1007  
  1008  // RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
  1009  // remaining value of 0.
  1010  type RateLimitError struct {
  1011  	Rate     Rate           // Rate specifies last known rate limit for the client
  1012  	Response *http.Response // HTTP response that caused this error
  1013  	Message  string         `json:"message"` // error message
  1014  }
  1015  
  1016  func (r *RateLimitError) Error() string {
  1017  	return fmt.Sprintf("%v %v: %d %v %v",
  1018  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
  1019  		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
  1020  }
  1021  
  1022  // Is returns whether the provided error equals this error.
  1023  func (r *RateLimitError) Is(target error) bool {
  1024  	v, ok := target.(*RateLimitError)
  1025  	if !ok {
  1026  		return false
  1027  	}
  1028  
  1029  	return r.Rate == v.Rate &&
  1030  		r.Message == v.Message &&
  1031  		compareHTTPResponse(r.Response, v.Response)
  1032  }
  1033  
  1034  // AcceptedError occurs when GitHub returns 202 Accepted response with an
  1035  // empty body, which means a job was scheduled on the GitHub side to process
  1036  // the information needed and cache it.
  1037  // Technically, 202 Accepted is not a real error, it's just used to
  1038  // indicate that results are not ready yet, but should be available soon.
  1039  // The request can be repeated after some time.
  1040  type AcceptedError struct {
  1041  	// Raw contains the response body.
  1042  	Raw []byte
  1043  }
  1044  
  1045  func (*AcceptedError) Error() string {
  1046  	return "job scheduled on GitHub side; try again later"
  1047  }
  1048  
  1049  // Is returns whether the provided error equals this error.
  1050  func (ae *AcceptedError) Is(target error) bool {
  1051  	v, ok := target.(*AcceptedError)
  1052  	if !ok {
  1053  		return false
  1054  	}
  1055  	return bytes.Compare(ae.Raw, v.Raw) == 0
  1056  }
  1057  
  1058  // AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the
  1059  // "documentation_url" field value equal to "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits".
  1060  type AbuseRateLimitError struct {
  1061  	Response *http.Response // HTTP response that caused this error
  1062  	Message  string         `json:"message"` // error message
  1063  
  1064  	// RetryAfter is provided with some abuse rate limit errors. If present,
  1065  	// it is the amount of time that the client should wait before retrying.
  1066  	// Otherwise, the client should try again later (after an unspecified amount of time).
  1067  	RetryAfter *time.Duration
  1068  }
  1069  
  1070  func (r *AbuseRateLimitError) Error() string {
  1071  	return fmt.Sprintf("%v %v: %d %v",
  1072  		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
  1073  		r.Response.StatusCode, r.Message)
  1074  }
  1075  
  1076  // Is returns whether the provided error equals this error.
  1077  func (r *AbuseRateLimitError) Is(target error) bool {
  1078  	v, ok := target.(*AbuseRateLimitError)
  1079  	if !ok {
  1080  		return false
  1081  	}
  1082  
  1083  	return r.Message == v.Message &&
  1084  		r.RetryAfter == v.RetryAfter &&
  1085  		compareHTTPResponse(r.Response, v.Response)
  1086  }
  1087  
  1088  // sanitizeURL redacts the client_secret parameter from the URL which may be
  1089  // exposed to the user.
  1090  func sanitizeURL(uri *url.URL) *url.URL {
  1091  	if uri == nil {
  1092  		return nil
  1093  	}
  1094  	params := uri.Query()
  1095  	if len(params.Get("client_secret")) > 0 {
  1096  		params.Set("client_secret", "REDACTED")
  1097  		uri.RawQuery = params.Encode()
  1098  	}
  1099  	return uri
  1100  }
  1101  
  1102  /*
  1103  An Error reports more details on an individual error in an ErrorResponse.
  1104  These are the possible validation error codes:
  1105  
  1106  	missing:
  1107  	    resource does not exist
  1108  	missing_field:
  1109  	    a required field on a resource has not been set
  1110  	invalid:
  1111  	    the formatting of a field is invalid
  1112  	already_exists:
  1113  	    another resource has the same valid as this field
  1114  	custom:
  1115  	    some resources return this (e.g. github.User.CreateKey()), additional
  1116  	    information is set in the Message field of the Error
  1117  
  1118  GitHub error responses structure are often undocumented and inconsistent.
  1119  Sometimes error is just a simple string (Issue #540).
  1120  In such cases, Message represents an error message as a workaround.
  1121  
  1122  GitHub API docs: https://docs.github.com/en/rest/#client-errors
  1123  */
  1124  type Error struct {
  1125  	Resource string `json:"resource"` // resource on which the error occurred
  1126  	Field    string `json:"field"`    // field on which the error occurred
  1127  	Code     string `json:"code"`     // validation error code
  1128  	Message  string `json:"message"`  // Message describing the error. Errors with Code == "custom" will always have this set.
  1129  }
  1130  
  1131  func (e *Error) Error() string {
  1132  	return fmt.Sprintf("%v error caused by %v field on %v resource",
  1133  		e.Code, e.Field, e.Resource)
  1134  }
  1135  
  1136  func (e *Error) UnmarshalJSON(data []byte) error {
  1137  	type aliasError Error // avoid infinite recursion by using type alias.
  1138  	if err := json.Unmarshal(data, (*aliasError)(e)); err != nil {
  1139  		return json.Unmarshal(data, &e.Message) // data can be json string.
  1140  	}
  1141  	return nil
  1142  }
  1143  
  1144  // CheckResponse checks the API response for errors, and returns them if
  1145  // present. A response is considered an error if it has a status code outside
  1146  // the 200 range or equal to 202 Accepted.
  1147  // API error responses are expected to have response
  1148  // body, and a JSON response body that maps to ErrorResponse.
  1149  //
  1150  // The error type will be *RateLimitError for rate limit exceeded errors,
  1151  // *AcceptedError for 202 Accepted status codes,
  1152  // and *TwoFactorAuthError for two-factor authentication errors.
  1153  func CheckResponse(r *http.Response) error {
  1154  	if r.StatusCode == http.StatusAccepted {
  1155  		return &AcceptedError{}
  1156  	}
  1157  	if c := r.StatusCode; 200 <= c && c <= 299 {
  1158  		return nil
  1159  	}
  1160  
  1161  	errorResponse := &ErrorResponse{Response: r}
  1162  	data, err := io.ReadAll(r.Body)
  1163  	if err == nil && data != nil {
  1164  		json.Unmarshal(data, errorResponse)
  1165  	}
  1166  	// Re-populate error response body because GitHub error responses are often
  1167  	// undocumented and inconsistent.
  1168  	// Issue #1136, #540.
  1169  	r.Body = io.NopCloser(bytes.NewBuffer(data))
  1170  	switch {
  1171  	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
  1172  		return (*TwoFactorAuthError)(errorResponse)
  1173  	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
  1174  		return &RateLimitError{
  1175  			Rate:     parseRate(r),
  1176  			Response: errorResponse.Response,
  1177  			Message:  errorResponse.Message,
  1178  		}
  1179  	case r.StatusCode == http.StatusForbidden &&
  1180  		(strings.HasSuffix(errorResponse.DocumentationURL, "#abuse-rate-limits") ||
  1181  			strings.HasSuffix(errorResponse.DocumentationURL, "#secondary-rate-limits")):
  1182  		abuseRateLimitError := &AbuseRateLimitError{
  1183  			Response: errorResponse.Response,
  1184  			Message:  errorResponse.Message,
  1185  		}
  1186  		if retryAfter := parseSecondaryRate(r); retryAfter != nil {
  1187  			abuseRateLimitError.RetryAfter = retryAfter
  1188  		}
  1189  		return abuseRateLimitError
  1190  	default:
  1191  		return errorResponse
  1192  	}
  1193  }
  1194  
  1195  // parseBoolResponse determines the boolean result from a GitHub API response.
  1196  // Several GitHub API methods return boolean responses indicated by the HTTP
  1197  // status code in the response (true indicated by a 204, false indicated by a
  1198  // 404). This helper function will determine that result and hide the 404
  1199  // error if present. Any other error will be returned through as-is.
  1200  func parseBoolResponse(err error) (bool, error) {
  1201  	if err == nil {
  1202  		return true, nil
  1203  	}
  1204  
  1205  	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
  1206  		// Simply false. In this one case, we do not pass the error through.
  1207  		return false, nil
  1208  	}
  1209  
  1210  	// some other real error occurred
  1211  	return false, err
  1212  }
  1213  
  1214  // Rate represents the rate limit for the current client.
  1215  type Rate struct {
  1216  	// The number of requests per hour the client is currently limited to.
  1217  	Limit int `json:"limit"`
  1218  
  1219  	// The number of remaining requests the client can make this hour.
  1220  	Remaining int `json:"remaining"`
  1221  
  1222  	// The time at which the current rate limit will reset.
  1223  	Reset Timestamp `json:"reset"`
  1224  }
  1225  
  1226  func (r Rate) String() string {
  1227  	return Stringify(r)
  1228  }
  1229  
  1230  // RateLimits represents the rate limits for the current client.
  1231  type RateLimits struct {
  1232  	// The rate limit for non-search API requests. Unauthenticated
  1233  	// requests are limited to 60 per hour. Authenticated requests are
  1234  	// limited to 5,000 per hour.
  1235  	//
  1236  	// GitHub API docs: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting
  1237  	Core *Rate `json:"core"`
  1238  
  1239  	// The rate limit for search API requests. Unauthenticated requests
  1240  	// are limited to 10 requests per minutes. Authenticated requests are
  1241  	// limited to 30 per minute.
  1242  	//
  1243  	// GitHub API docs: https://docs.github.com/en/rest/search#rate-limit
  1244  	Search *Rate `json:"search"`
  1245  
  1246  	// GitHub API docs: https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit
  1247  	GraphQL *Rate `json:"graphql"`
  1248  
  1249  	// GitHub API dos: https://docs.github.com/en/rest/rate-limit
  1250  	IntegrationManifest *Rate `json:"integration_manifest"`
  1251  
  1252  	SourceImport              *Rate `json:"source_import"`
  1253  	CodeScanningUpload        *Rate `json:"code_scanning_upload"`
  1254  	ActionsRunnerRegistration *Rate `json:"actions_runner_registration"`
  1255  	SCIM                      *Rate `json:"scim"`
  1256  }
  1257  
  1258  func (r RateLimits) String() string {
  1259  	return Stringify(r)
  1260  }
  1261  
  1262  type rateLimitCategory uint8
  1263  
  1264  const (
  1265  	coreCategory rateLimitCategory = iota
  1266  	searchCategory
  1267  	graphqlCategory
  1268  	integrationManifestCategory
  1269  	sourceImportCategory
  1270  	codeScanningUploadCategory
  1271  	actionsRunnerRegistrationCategory
  1272  	scimCategory
  1273  
  1274  	categories // An array of this length will be able to contain all rate limit categories.
  1275  )
  1276  
  1277  // category returns the rate limit category of the endpoint, determined by HTTP method and Request.URL.Path.
  1278  func category(method, path string) rateLimitCategory {
  1279  	switch {
  1280  	// https://docs.github.com/en/rest/rate-limit#about-rate-limits
  1281  	default:
  1282  		// NOTE: coreCategory is returned for actionsRunnerRegistrationCategory too,
  1283  		// because no API found for this category.
  1284  		return coreCategory
  1285  	case strings.HasPrefix(path, "/search/"):
  1286  		return searchCategory
  1287  	case path == "/graphql":
  1288  		return graphqlCategory
  1289  	case strings.HasPrefix(path, "/app-manifests/") &&
  1290  		strings.HasSuffix(path, "/conversions") &&
  1291  		method == http.MethodPost:
  1292  		return integrationManifestCategory
  1293  
  1294  	// https://docs.github.com/en/rest/migrations/source-imports#start-an-import
  1295  	case strings.HasPrefix(path, "/repos/") &&
  1296  		strings.HasSuffix(path, "/import") &&
  1297  		method == http.MethodPut:
  1298  		return sourceImportCategory
  1299  
  1300  	// https://docs.github.com/en/rest/code-scanning#upload-an-analysis-as-sarif-data
  1301  	case strings.HasSuffix(path, "/code-scanning/sarifs"):
  1302  		return codeScanningUploadCategory
  1303  
  1304  	// https://docs.github.com/en/enterprise-cloud@latest/rest/scim
  1305  	case strings.HasPrefix(path, "/scim/"):
  1306  		return scimCategory
  1307  	}
  1308  }
  1309  
  1310  // RateLimits returns the rate limits for the current client.
  1311  func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) {
  1312  	req, err := c.NewRequest("GET", "rate_limit", nil)
  1313  	if err != nil {
  1314  		return nil, nil, err
  1315  	}
  1316  
  1317  	response := new(struct {
  1318  		Resources *RateLimits `json:"resources"`
  1319  	})
  1320  
  1321  	// This resource is not subject to rate limits.
  1322  	ctx = context.WithValue(ctx, bypassRateLimitCheck, true)
  1323  	resp, err := c.Do(ctx, req, response)
  1324  	if err != nil {
  1325  		return nil, resp, err
  1326  	}
  1327  
  1328  	if response.Resources != nil {
  1329  		c.rateMu.Lock()
  1330  		if response.Resources.Core != nil {
  1331  			c.rateLimits[coreCategory] = *response.Resources.Core
  1332  		}
  1333  		if response.Resources.Search != nil {
  1334  			c.rateLimits[searchCategory] = *response.Resources.Search
  1335  		}
  1336  		if response.Resources.GraphQL != nil {
  1337  			c.rateLimits[graphqlCategory] = *response.Resources.GraphQL
  1338  		}
  1339  		if response.Resources.IntegrationManifest != nil {
  1340  			c.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest
  1341  		}
  1342  		if response.Resources.SourceImport != nil {
  1343  			c.rateLimits[sourceImportCategory] = *response.Resources.SourceImport
  1344  		}
  1345  		if response.Resources.CodeScanningUpload != nil {
  1346  			c.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload
  1347  		}
  1348  		if response.Resources.ActionsRunnerRegistration != nil {
  1349  			c.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration
  1350  		}
  1351  		if response.Resources.SCIM != nil {
  1352  			c.rateLimits[scimCategory] = *response.Resources.SCIM
  1353  		}
  1354  		c.rateMu.Unlock()
  1355  	}
  1356  
  1357  	return response.Resources, resp, nil
  1358  }
  1359  
  1360  func setCredentialsAsHeaders(req *http.Request, id, secret string) *http.Request {
  1361  	// To set extra headers, we must make a copy of the Request so
  1362  	// that we don't modify the Request we were given. This is required by the
  1363  	// specification of http.RoundTripper.
  1364  	//
  1365  	// Since we are going to modify only req.Header here, we only need a deep copy
  1366  	// of req.Header.
  1367  	convertedRequest := new(http.Request)
  1368  	*convertedRequest = *req
  1369  	convertedRequest.Header = make(http.Header, len(req.Header))
  1370  
  1371  	for k, s := range req.Header {
  1372  		convertedRequest.Header[k] = append([]string(nil), s...)
  1373  	}
  1374  	convertedRequest.SetBasicAuth(id, secret)
  1375  	return convertedRequest
  1376  }
  1377  
  1378  /*
  1379  UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls
  1380  that need to use a higher rate limit associated with your OAuth application.
  1381  
  1382  	t := &github.UnauthenticatedRateLimitedTransport{
  1383  		ClientID:     "your app's client ID",
  1384  		ClientSecret: "your app's client secret",
  1385  	}
  1386  	client := github.NewClient(t.Client())
  1387  
  1388  This will add the client id and secret as a base64-encoded string in the format
  1389  ClientID:ClientSecret and apply it as an "Authorization": "Basic" header.
  1390  
  1391  See https://docs.github.com/en/rest/#unauthenticated-rate-limited-requests for
  1392  more information.
  1393  */
  1394  type UnauthenticatedRateLimitedTransport struct {
  1395  	// ClientID is the GitHub OAuth client ID of the current application, which
  1396  	// can be found by selecting its entry in the list at
  1397  	// https://github.com/settings/applications.
  1398  	ClientID string
  1399  
  1400  	// ClientSecret is the GitHub OAuth client secret of the current
  1401  	// application.
  1402  	ClientSecret string
  1403  
  1404  	// Transport is the underlying HTTP transport to use when making requests.
  1405  	// It will default to http.DefaultTransport if nil.
  1406  	Transport http.RoundTripper
  1407  }
  1408  
  1409  // RoundTrip implements the RoundTripper interface.
  1410  func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1411  	if t.ClientID == "" {
  1412  		return nil, errors.New("t.ClientID is empty")
  1413  	}
  1414  	if t.ClientSecret == "" {
  1415  		return nil, errors.New("t.ClientSecret is empty")
  1416  	}
  1417  
  1418  	req2 := setCredentialsAsHeaders(req, t.ClientID, t.ClientSecret)
  1419  	// Make the HTTP request.
  1420  	return t.transport().RoundTrip(req2)
  1421  }
  1422  
  1423  // Client returns an *http.Client that makes requests which are subject to the
  1424  // rate limit of your OAuth application.
  1425  func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client {
  1426  	return &http.Client{Transport: t}
  1427  }
  1428  
  1429  func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper {
  1430  	if t.Transport != nil {
  1431  		return t.Transport
  1432  	}
  1433  	return http.DefaultTransport
  1434  }
  1435  
  1436  // BasicAuthTransport is an http.RoundTripper that authenticates all requests
  1437  // using HTTP Basic Authentication with the provided username and password. It
  1438  // additionally supports users who have two-factor authentication enabled on
  1439  // their GitHub account.
  1440  type BasicAuthTransport struct {
  1441  	Username string // GitHub username
  1442  	Password string // GitHub password
  1443  	OTP      string // one-time password for users with two-factor auth enabled
  1444  
  1445  	// Transport is the underlying HTTP transport to use when making requests.
  1446  	// It will default to http.DefaultTransport if nil.
  1447  	Transport http.RoundTripper
  1448  }
  1449  
  1450  // RoundTrip implements the RoundTripper interface.
  1451  func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  1452  	req2 := setCredentialsAsHeaders(req, t.Username, t.Password)
  1453  	if t.OTP != "" {
  1454  		req2.Header.Set(headerOTP, t.OTP)
  1455  	}
  1456  	return t.transport().RoundTrip(req2)
  1457  }
  1458  
  1459  // Client returns an *http.Client that makes requests that are authenticated
  1460  // using HTTP Basic Authentication.
  1461  func (t *BasicAuthTransport) Client() *http.Client {
  1462  	return &http.Client{Transport: t}
  1463  }
  1464  
  1465  func (t *BasicAuthTransport) transport() http.RoundTripper {
  1466  	if t.Transport != nil {
  1467  		return t.Transport
  1468  	}
  1469  	return http.DefaultTransport
  1470  }
  1471  
  1472  // formatRateReset formats d to look like "[rate reset in 2s]" or
  1473  // "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"
  1474  // for the negative cases.
  1475  func formatRateReset(d time.Duration) string {
  1476  	isNegative := d < 0
  1477  	if isNegative {
  1478  		d *= -1
  1479  	}
  1480  	secondsTotal := int(0.5 + d.Seconds())
  1481  	minutes := secondsTotal / 60
  1482  	seconds := secondsTotal - minutes*60
  1483  
  1484  	var timeString string
  1485  	if minutes > 0 {
  1486  		timeString = fmt.Sprintf("%dm%02ds", minutes, seconds)
  1487  	} else {
  1488  		timeString = fmt.Sprintf("%ds", seconds)
  1489  	}
  1490  
  1491  	if isNegative {
  1492  		return fmt.Sprintf("[rate limit was reset %v ago]", timeString)
  1493  	}
  1494  	return fmt.Sprintf("[rate reset in %v]", timeString)
  1495  }
  1496  
  1497  // When using roundTripWithOptionalFollowRedirect, note that it
  1498  // is the responsibility of the caller to close the response body.
  1499  func (c *Client) roundTripWithOptionalFollowRedirect(ctx context.Context, u string, followRedirects bool, opts ...RequestOption) (*http.Response, error) {
  1500  	req, err := c.NewRequest("GET", u, nil, opts...)
  1501  	if err != nil {
  1502  		return nil, err
  1503  	}
  1504  
  1505  	var resp *http.Response
  1506  	// Use http.DefaultTransport if no custom Transport is configured
  1507  	req = withContext(ctx, req)
  1508  	if c.client.Transport == nil {
  1509  		resp, err = http.DefaultTransport.RoundTrip(req)
  1510  	} else {
  1511  		resp, err = c.client.Transport.RoundTrip(req)
  1512  	}
  1513  	if err != nil {
  1514  		return nil, err
  1515  	}
  1516  
  1517  	// If redirect response is returned, follow it
  1518  	if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
  1519  		resp.Body.Close()
  1520  		u = resp.Header.Get("Location")
  1521  		resp, err = c.roundTripWithOptionalFollowRedirect(ctx, u, false, opts...)
  1522  	}
  1523  	return resp, err
  1524  }
  1525  
  1526  // Bool is a helper routine that allocates a new bool value
  1527  // to store v and returns a pointer to it.
  1528  func Bool(v bool) *bool { return &v }
  1529  
  1530  // Int is a helper routine that allocates a new int value
  1531  // to store v and returns a pointer to it.
  1532  func Int(v int) *int { return &v }
  1533  
  1534  // Int64 is a helper routine that allocates a new int64 value
  1535  // to store v and returns a pointer to it.
  1536  func Int64(v int64) *int64 { return &v }
  1537  
  1538  // String is a helper routine that allocates a new string value
  1539  // to store v and returns a pointer to it.
  1540  func String(v string) *string { return &v }