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