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