// Copyright 2013 The go-github AUTHORS. All rights reserved.//// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.//go:generate go run gen-accessors.go//go:generate go run gen-stringify-test.go//go:generate ../script/metadata.sh update-gopackage githubimport ()const (Version = "v66.0.0" defaultAPIVersion = "2022-11-28" defaultBaseURL = "https://api.github.com/" defaultUserAgent = "go-github" + "/" + Version uploadBaseURL = "https://uploads.github.com/" headerAPIVersion = "X-GitHub-Api-Version" headerRateLimit = "X-RateLimit-Limit" headerRateRemaining = "X-RateLimit-Remaining" headerRateReset = "X-RateLimit-Reset" headerOTP = "X-GitHub-OTP" headerRetryAfter = "Retry-After" headerTokenExpiration = "GitHub-Authentication-Token-Expiration" mediaTypeV3 = "application/vnd.github.v3+json" defaultMediaType = "application/octet-stream" mediaTypeV3SHA = "application/vnd.github.v3.sha" mediaTypeV3Diff = "application/vnd.github.v3.diff" mediaTypeV3Patch = "application/vnd.github.v3.patch" mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json" mediaTypeIssueImportAPI = "application/vnd.github.golden-comet-preview+json"// Media Type values to access preview APIs // These media types will be added to the API request as headers // and used to enable particular features on GitHub API that are still in preview. // After some time, specific media types will be promoted (to a "stable" state). // From then on, the preview headers are not required anymore to activate the additional // feature on GitHub.com's API. However, this API header might still be needed for users // to run a GitHub Enterprise Server on-premise. // It's not uncommon for GitHub Enterprise Server customers to run older versions which // would probably rely on the preview headers for some time. // While the header promotion is going out for GitHub.com, it may be some time before it // even arrives in GitHub Enterprise Server. // We keep those preview headers around to avoid breaking older GitHub Enterprise Server // versions. Additionally, non-functional (preview) headers don't create any side effects // on GitHub Cloud version. // // See https://github.com/google/go-github/pull/2125 for full context.// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/ mediaTypeStarringPreview = "application/vnd.github.v3.star+json"// https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/ mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json"// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/ mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/ mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"// https://developer.github.com/changes/2016-05-12-reactions-api-preview/ mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview"// https://developer.github.com/changes/2016-05-23-timeline-preview-api/ mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"// https://developer.github.com/changes/2016-09-14-projects-api/ mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"// https://developer.github.com/changes/2017-01-05-commit-search-api/ mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json"// https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/ mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json"// https://developer.github.com/changes/2017-05-23-coc-api/ mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json"// https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/ mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json"// https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/ mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json"// https://developer.github.com/changes/2018-05-07-new-checks-api-public-beta/ mediaTypeCheckRunsPreview = "application/vnd.github.antiope-preview+json"// https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/ mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview"// https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/ mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json"// https://developer.github.com/changes/2018-09-05-project-card-events/ mediaTypeProjectCardDetailsPreview = "application/vnd.github.starfox-preview+json"// https://developer.github.com/changes/2018-12-18-interactions-preview/ mediaTypeInteractionRestrictionsPreview = "application/vnd.github.sombra-preview+json"// https://developer.github.com/changes/2019-03-14-enabling-disabling-pages/ mediaTypeEnablePagesAPIPreview = "application/vnd.github.switcheroo-preview+json"// https://developer.github.com/changes/2019-04-24-vulnerability-alerts/ mediaTypeRequiredVulnerabilityAlertsPreview = "application/vnd.github.dorian-preview+json"// https://developer.github.com/changes/2019-05-29-update-branch-api/ mediaTypeUpdatePullRequestBranchPreview = "application/vnd.github.lydian-preview+json"// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/ mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"// https://docs.github.com/rest/previews/#repository-creation-permissions mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"// https://docs.github.com/rest/previews/#create-and-use-repository-templates mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"// https://developer.github.com/changes/2019-10-03-multi-line-comments/ mediaTypeMultiLineCommentsPreview = "application/vnd.github.comfort-fade-preview+json"// https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/ mediaTypeOAuthAppPreview = "application/vnd.github.doctor-strange-preview+json"// https://developer.github.com/changes/2019-12-03-internal-visibility-changes/ mediaTypeRepositoryVisibilityPreview = "application/vnd.github.nebula-preview+json"// https://developer.github.com/changes/2018-12-10-content-attachments-api/ mediaTypeContentAttachmentsPreview = "application/vnd.github.corsair-preview+json")var errNonNilContext = errors.New("context must be non-nil")// A Client manages communication with the GitHub API.typeClientstruct { clientMu sync.Mutex// clientMu protects the client during calls that modify the CheckRedirect func. client *http.Client// HTTP client used to communicate with the API.// Base URL for API requests. Defaults to the public GitHub API, but can be // set to a domain endpoint to use with GitHub Enterprise. BaseURL should // always be specified with a trailing slash. BaseURL *url.URL// Base URL for uploading files. UploadURL *url.URL// User agent used when communicating with the GitHub API. UserAgent string rateMu sync.Mutex rateLimits [Categories]Rate// Rate limits for the client as determined by the most recent API calls. secondaryRateLimitReset time.Time// Secondary rate limit reset for the client as determined by the most recent API calls. common service// Reuse a single struct instead of allocating one for each service on the heap.// Services used for talking to different parts of the GitHub API. Actions *ActionsService Activity *ActivityService Admin *AdminService Apps *AppsService Authorizations *AuthorizationsService Billing *BillingService Checks *ChecksService CodeScanning *CodeScanningService CodesOfConduct *CodesOfConductService Codespaces *CodespacesService Copilot *CopilotService Dependabot *DependabotService DependencyGraph *DependencyGraphService Emojis *EmojisService Enterprise *EnterpriseService Gists *GistsService Git *GitService Gitignores *GitignoresService Interactions *InteractionsService IssueImport *IssueImportService Issues *IssuesService Licenses *LicensesService Markdown *MarkdownService Marketplace *MarketplaceService Meta *MetaService Migrations *MigrationService Organizations *OrganizationsService Projects *ProjectsService PullRequests *PullRequestsService RateLimit *RateLimitService Reactions *ReactionsService Repositories *RepositoriesService SCIM *SCIMService Search *SearchService SecretScanning *SecretScanningService SecurityAdvisories *SecurityAdvisoriesService Teams *TeamsService Users *UsersService}type service struct { client *Client}// Client returns the http.Client used by this GitHub client.// This should only be used for requests to the GitHub API because// request headers will contain an authorization token.func ( *Client) () *http.Client { .clientMu.Lock()defer .clientMu.Unlock() := *.clientreturn &}// ListOptions specifies the optional parameters to various List methods that// support offset pagination.typeListOptionsstruct {// For paginated result sets, page of results to retrieve. Page int`url:"page,omitempty"`// For paginated result sets, the number of results to include per page. PerPage int`url:"per_page,omitempty"`}// ListCursorOptions specifies the optional parameters to various List methods that// support cursor pagination.typeListCursorOptionsstruct {// For paginated result sets, page of results to retrieve. Page string`url:"page,omitempty"`// For paginated result sets, the number of results to include per page. PerPage int`url:"per_page,omitempty"`// For paginated result sets, the number of results per page (max 100), starting from the first matching result. // This parameter must not be used in combination with last. First int`url:"first,omitempty"`// For paginated result sets, the number of results per page (max 100), starting from the last matching result. // This parameter must not be used in combination with first. Last int`url:"last,omitempty"`// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor. After string`url:"after,omitempty"`// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor. Before string`url:"before,omitempty"`// A cursor, as given in the Link header. If specified, the query continues the search using this cursor. Cursor string`url:"cursor,omitempty"`}// UploadOptions specifies the parameters to methods that support uploads.typeUploadOptionsstruct { Name string`url:"name,omitempty"` Label string`url:"label,omitempty"` MediaType string`url:"-"`}// RawType represents type of raw format of a request instead of JSON.typeRawTypeuint8const (// Diff format.DiffRawType = 1 + iota// Patch format.Patch)// RawOptions specifies parameters when user wants to get raw format of// a response instead of JSON.typeRawOptionsstruct { Type RawType}// addOptions adds the parameters in opts as URL query parameters to s. opts// must be a struct whose fields may contain "url" tags.func addOptions( string, interface{}) (string, error) { := reflect.ValueOf()if .Kind() == reflect.Ptr && .IsNil() {return , nil } , := url.Parse()if != nil {return , } , := query.Values()if != nil {return , } .RawQuery = .Encode()return .String(), nil}// NewClient returns a new GitHub API client. If a nil httpClient is// provided, a new http.Client will be used. To use API methods which require// authentication, either use Client.WithAuthToken or provide NewClient with// an http.Client that will perform the authentication for you (such as that// provided by the golang.org/x/oauth2 library).func ( *http.Client) *Client {if == nil { = &http.Client{} } := * := &Client{client: &} .initialize()return}// WithAuthToken returns a copy of the client configured to use the provided token for the Authorization header.func ( *Client) ( string) *Client { := .copy()defer .initialize() := .client.Transportif == nil { = http.DefaultTransport } .client.Transport = roundTripperFunc(func( *http.Request) (*http.Response, error) { = .Clone(.Context()) .Header.Set("Authorization", fmt.Sprintf("Bearer %s", ))return .RoundTrip() }, )return}// WithEnterpriseURLs returns a copy of the client configured to use the provided base and// upload URLs. If the base URL does not have the suffix "/api/v3/", it will be added// automatically. If the upload URL does not have the suffix "/api/uploads", it will be// added automatically.//// Note that WithEnterpriseURLs is a convenience helper only;// its behavior is equivalent to setting the BaseURL and UploadURL fields.//// Another important thing is that by default, the GitHub Enterprise URL format// should be http(s)://[hostname]/api/v3/ or you will always receive the 406 status code.// The upload URL format should be http(s)://[hostname]/api/uploads/.func ( *Client) (, string) (*Client, error) { := .copy()defer .initialize()varerror .BaseURL, = url.Parse()if != nil {returnnil, }if !strings.HasSuffix(.BaseURL.Path, "/") { .BaseURL.Path += "/" }if !strings.HasSuffix(.BaseURL.Path, "/api/v3/") && !strings.HasPrefix(.BaseURL.Host, "api.") && !strings.Contains(.BaseURL.Host, ".api.") { .BaseURL.Path += "api/v3/" } .UploadURL, = url.Parse()if != nil {returnnil, }if !strings.HasSuffix(.UploadURL.Path, "/") { .UploadURL.Path += "/" }if !strings.HasSuffix(.UploadURL.Path, "/api/uploads/") && !strings.HasPrefix(.UploadURL.Host, "api.") && !strings.Contains(.UploadURL.Host, ".api.") { .UploadURL.Path += "api/uploads/" }return , nil}// initialize sets default values and initializes services.func ( *Client) () {if .client == nil { .client = &http.Client{} }if .BaseURL == nil { .BaseURL, _ = url.Parse(defaultBaseURL) }if .UploadURL == nil { .UploadURL, _ = url.Parse(uploadBaseURL) }if .UserAgent == "" { .UserAgent = defaultUserAgent } .common.client = .Actions = (*ActionsService)(&.common) .Activity = (*ActivityService)(&.common) .Admin = (*AdminService)(&.common) .Apps = (*AppsService)(&.common) .Authorizations = (*AuthorizationsService)(&.common) .Billing = (*BillingService)(&.common) .Checks = (*ChecksService)(&.common) .CodeScanning = (*CodeScanningService)(&.common) .Codespaces = (*CodespacesService)(&.common) .CodesOfConduct = (*CodesOfConductService)(&.common) .Copilot = (*CopilotService)(&.common) .Dependabot = (*DependabotService)(&.common) .DependencyGraph = (*DependencyGraphService)(&.common) .Emojis = (*EmojisService)(&.common) .Enterprise = (*EnterpriseService)(&.common) .Gists = (*GistsService)(&.common) .Git = (*GitService)(&.common) .Gitignores = (*GitignoresService)(&.common) .Interactions = (*InteractionsService)(&.common) .IssueImport = (*IssueImportService)(&.common) .Issues = (*IssuesService)(&.common) .Licenses = (*LicensesService)(&.common) .Markdown = (*MarkdownService)(&.common) .Marketplace = &MarketplaceService{client: } .Meta = (*MetaService)(&.common) .Migrations = (*MigrationService)(&.common) .Organizations = (*OrganizationsService)(&.common) .Projects = (*ProjectsService)(&.common) .PullRequests = (*PullRequestsService)(&.common) .RateLimit = (*RateLimitService)(&.common) .Reactions = (*ReactionsService)(&.common) .Repositories = (*RepositoriesService)(&.common) .SCIM = (*SCIMService)(&.common) .Search = (*SearchService)(&.common) .SecretScanning = (*SecretScanningService)(&.common) .SecurityAdvisories = (*SecurityAdvisoriesService)(&.common) .Teams = (*TeamsService)(&.common) .Users = (*UsersService)(&.common)}// copy returns a copy of the current client. It must be initialized before use.func ( *Client) () *Client { .clientMu.Lock()// can't use *c here because that would copy mutexes by value. := Client{client: &http.Client{},UserAgent: .UserAgent,BaseURL: .BaseURL,UploadURL: .UploadURL,secondaryRateLimitReset: .secondaryRateLimitReset, } .clientMu.Unlock()if .client != nil { .client.Transport = .client.Transport .client.CheckRedirect = .client.CheckRedirect .client.Jar = .client.Jar .client.Timeout = .client.Timeout } .rateMu.Lock()copy(.rateLimits[:], .rateLimits[:]) .rateMu.Unlock()return &}// NewClientWithEnvProxy enhances NewClient with the HttpProxy env.func () *Client {returnNewClient(&http.Client{Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}})}// NewTokenClient returns a new GitHub API client authenticated with the provided token.// Deprecated: Use NewClient(nil).WithAuthToken(token) instead.func ( context.Context, string) *Client {// This always returns a nil error.returnNewClient(nil).WithAuthToken()}// NewEnterpriseClient returns a new GitHub API client with provided// base URL and upload URL (often is your GitHub Enterprise hostname).//// Deprecated: Use NewClient(httpClient).WithEnterpriseURLs(baseURL, uploadURL) instead.func (, string, *http.Client) (*Client, error) {returnNewClient().WithEnterpriseURLs(, )}// RequestOption represents an option that can modify an http.Request.typeRequestOptionfunc(req *http.Request)// WithVersion overrides the GitHub v3 API version for this individual request.// For more information, see:// https://github.blog/2022-11-28-to-infinity-and-beyond-enabling-the-future-of-githubs-rest-api-with-api-versioning/func ( string) RequestOption {returnfunc( *http.Request) { .Header.Set(headerAPIVersion, ) }}// NewRequest creates an API request. A relative URL can be provided in urlStr,// in which case it is resolved relative to the BaseURL of the Client.// Relative URLs should always be specified without a preceding slash. If// specified, the value pointed to by body is JSON encoded and included as the// request body.func ( *Client) (, string, interface{}, ...RequestOption) (*http.Request, error) {if !strings.HasSuffix(.BaseURL.Path, "/") {returnnil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", .BaseURL) } , := .BaseURL.Parse()if != nil {returnnil, }vario.ReadWriterif != nil { = &bytes.Buffer{} := json.NewEncoder() .SetEscapeHTML(false) := .Encode()if != nil {returnnil, } } , := http.NewRequest(, .String(), )if != nil {returnnil, }if != nil { .Header.Set("Content-Type", "application/json") } .Header.Set("Accept", mediaTypeV3)if .UserAgent != "" { .Header.Set("User-Agent", .UserAgent) } .Header.Set(headerAPIVersion, defaultAPIVersion)for , := range { () }return , nil}// NewFormRequest creates an API request. A relative URL can be provided in urlStr,// in which case it is resolved relative to the BaseURL of the Client.// Relative URLs should always be specified without a preceding slash.// Body is sent with Content-Type: application/x-www-form-urlencoded.func ( *Client) ( string, io.Reader, ...RequestOption) (*http.Request, error) {if !strings.HasSuffix(.BaseURL.Path, "/") {returnnil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", .BaseURL) } , := .BaseURL.Parse()if != nil {returnnil, } , := http.NewRequest(http.MethodPost, .String(), )if != nil {returnnil, } .Header.Set("Content-Type", "application/x-www-form-urlencoded") .Header.Set("Accept", mediaTypeV3)if .UserAgent != "" { .Header.Set("User-Agent", .UserAgent) } .Header.Set(headerAPIVersion, defaultAPIVersion)for , := range { () }return , nil}// NewUploadRequest creates an upload request. A relative URL can be provided in// urlStr, in which case it is resolved relative to the UploadURL of the Client.// Relative URLs should always be specified without a preceding slash.func ( *Client) ( string, io.Reader, int64, string, ...RequestOption) (*http.Request, error) {if !strings.HasSuffix(.UploadURL.Path, "/") {returnnil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", .UploadURL) } , := .UploadURL.Parse()if != nil {returnnil, } , := http.NewRequest("POST", .String(), )if != nil {returnnil, } .ContentLength = if == "" { = defaultMediaType } .Header.Set("Content-Type", ) .Header.Set("Accept", mediaTypeV3) .Header.Set("User-Agent", .UserAgent) .Header.Set(headerAPIVersion, defaultAPIVersion)for , := range { () }return , nil}// Response is a GitHub API response. This wraps the standard http.Response// returned from GitHub and provides convenient access to things like// pagination links.typeResponsestruct { *http.Response// These fields provide the page values for paginating through a set of // results. Any or all of these may be set to the zero value for // responses that are not part of a paginated set, or for which there // are no additional pages. // // These fields support what is called "offset pagination" and should // be used with the ListOptions struct. NextPage int PrevPage int FirstPage int LastPage int// Additionally, some APIs support "cursor pagination" instead of offset. // This means that a token points directly to the next record which // can lead to O(1) performance compared to O(n) performance provided // by offset pagination. // // For APIs that support cursor pagination (such as // TeamsService.ListIDPGroupsInOrganization), the following field // will be populated to point to the next page. // // To use this token, set ListCursorOptions.Page to this value before // calling the endpoint again. NextPageToken string// For APIs that support cursor pagination, such as RepositoriesService.ListHookDeliveries, // the following field will be populated to point to the next page. // Set ListCursorOptions.Cursor to this value when calling the endpoint again. Cursor string// For APIs that support before/after pagination, such as OrganizationsService.AuditLog. Before string After string// Explicitly specify the Rate type so Rate's String() receiver doesn't // propagate to Response. Rate Rate// token's expiration date. Timestamp is 0001-01-01 when token doesn't expire. // So it is valid for TokenExpiration.Equal(Timestamp{}) or TokenExpiration.Time.After(time.Now()) TokenExpiration Timestamp}// newResponse creates a new Response for the provided http.Response.// r must not be nil.func newResponse( *http.Response) *Response { := &Response{Response: } .populatePageValues() .Rate = parseRate() .TokenExpiration = parseTokenExpiration()return}// populatePageValues parses the HTTP Link response headers and populates the// various pagination link values in the Response.func ( *Response) () {if , := .Response.Header["Link"]; && len() > 0 {for , := rangestrings.Split([0], ",") { := strings.Split(strings.TrimSpace(), ";")// link must at least have href and reliflen() < 2 {continue }// ensure href is properly formattedif !strings.HasPrefix([0], "<") || !strings.HasSuffix([0], ">") {continue }// try to pull out page parameter , := url.Parse([0][1 : len([0])-1])if != nil {continue } := .Query()if := .Get("cursor"); != "" {for , := range [1:] {switchstrings.TrimSpace() {case`rel="next"`: .Cursor = } }continue } := .Get("page") := .Get("since") := .Get("before") := .Get("after")if == "" && == "" && == "" && == "" {continue }if != "" && == "" { = }for , := range [1:] {switchstrings.TrimSpace() {case`rel="next"`:if .NextPage, = strconv.Atoi(); != nil { .NextPageToken = } .After = case`rel="prev"`: .PrevPage, _ = strconv.Atoi() .Before = case`rel="first"`: .FirstPage, _ = strconv.Atoi()case`rel="last"`: .LastPage, _ = strconv.Atoi() } } } }}// parseRate parses the rate related headers.func parseRate( *http.Response) Rate {varRateif := .Header.Get(headerRateLimit); != "" { .Limit, _ = strconv.Atoi() }if := .Header.Get(headerRateRemaining); != "" { .Remaining, _ = strconv.Atoi() }if := .Header.Get(headerRateReset); != "" {if , := strconv.ParseInt(, 10, 64); != 0 { .Reset = Timestamp{time.Unix(, 0)} } }return}// parseSecondaryRate parses the secondary rate related headers,// and returns the time to retry after.func parseSecondaryRate( *http.Response) *time.Duration {// According to GitHub support, the "Retry-After" header value will be // an integer which represents the number of seconds that one should // wait before resuming making requests.if := .Header.Get(headerRetryAfter); != "" { , := strconv.ParseInt(, 10, 64) // Error handling is noop. := time.Duration() * time.Secondreturn & }// According to GitHub support, endpoints might return x-ratelimit-reset instead, // as an integer which represents the number of seconds since epoch UTC, // representing the time to resume making requests.if := .Header.Get(headerRateReset); != "" { , := strconv.ParseInt(, 10, 64) // Error handling is noop. := time.Until(time.Unix(, 0))return & }returnnil}// parseTokenExpiration parses the TokenExpiration related headers.// Returns 0001-01-01 if the header is not defined or could not be parsed.func parseTokenExpiration( *http.Response) Timestamp {if := .Header.Get(headerTokenExpiration); != "" {if , := time.Parse("2006-01-02 15:04:05 MST", ); == nil {returnTimestamp{.Local()} }// Some tokens include the timezone offset instead of the timezone. // https://github.com/google/go-github/issues/2649if , := time.Parse("2006-01-02 15:04:05 -0700", ); == nil {returnTimestamp{.Local()} } }returnTimestamp{} // 0001-01-01 00:00:00}type requestContext uint8const ( bypassRateLimitCheck requestContext = iotaSleepUntilPrimaryRateLimitResetWhenRateLimited)// BareDo sends an API request and lets you handle the api response. If an error// or API Error occurs, the error will contain more information. Otherwise you// are supposed to read and close the response's Body. If rate limit is exceeded// and reset time is in the future, BareDo returns *RateLimitError immediately// without making a network API call.//// The provided ctx must be non-nil, if it is nil an error is returned. If it is// canceled or times out, ctx.Err() will be returned.func ( *Client) ( context.Context, *http.Request) (*Response, error) {if == nil {returnnil, errNonNilContext } = withContext(, ) := GetRateLimitCategory(.Method, .URL.Path)if := .Value(bypassRateLimitCheck); == nil {// If we've hit rate limit, don't make further requests before Reset time.if := .checkRateLimitBeforeDo(, ); != nil {return &Response{Response: .Response,Rate: .Rate, }, }// If we've hit a secondary rate limit, don't make further requests before Retry After.if := .checkSecondaryRateLimitBeforeDo(); != nil {return &Response{Response: .Response, }, } } , := .client.Do()if != nil {// If we got an error, and the context has been canceled, // the context's error is probably more useful.select {case<-.Done():returnnil, .Err()default: }// If the error type is *url.Error, sanitize its URL before returning.if , := .(*url.Error); {if , := url.Parse(.URL); == nil { .URL = sanitizeURL().String()returnnil, } }returnnil, } := newResponse()// Don't update the rate limits if this was a cached response. // X-From-Cache is set by https://github.com/gregjones/httpcacheif .Header.Get("X-From-Cache") == "" { .rateMu.Lock() .rateLimits[] = .Rate .rateMu.Unlock() } = CheckResponse()if != nil {defer .Body.Close()// Special case for AcceptedErrors. If an AcceptedError // has been encountered, the response's payload will be // added to the AcceptedError and returned. // // Issue #1022 , := .(*AcceptedError)if { , := io.ReadAll(.Body)if != nil {return , } .Raw = = } , := .(*RateLimitError)if && .Context().Value(SleepUntilPrimaryRateLimitResetWhenRateLimited) != nil {if := sleepUntilResetWithBuffer(.Context(), .Rate.Reset.Time); != nil {return , }// retry the request once when the rate limit has resetreturn .(context.WithValue(.Context(), SleepUntilPrimaryRateLimitResetWhenRateLimited, nil), ) }// Update the secondary rate limit if we hit it. , := .(*AbuseRateLimitError)if && .RetryAfter != nil { .rateMu.Lock() .secondaryRateLimitReset = time.Now().Add(*.RetryAfter) .rateMu.Unlock() } }return , }// Do sends an API request and returns the API response. The API response is// JSON decoded and stored in the value pointed to by v, or returned as an// error if an API error has occurred. If v implements the io.Writer interface,// the raw response body will be written to v, without attempting to first// decode it. If v is nil, and no error happens, the response is returned as is.// If rate limit is exceeded and reset time is in the future, Do returns// *RateLimitError immediately without making a network API call.//// The provided ctx must be non-nil, if it is nil an error is returned. If it// is canceled or times out, ctx.Err() will be returned.func ( *Client) ( context.Context, *http.Request, interface{}) (*Response, error) { , := .BareDo(, )if != nil {return , }defer .Body.Close()switch v := .(type) {casenil:caseio.Writer: _, = io.Copy(, .Body)default: := json.NewDecoder(.Body).Decode()if == io.EOF { = nil// ignore EOF errors caused by empty response body }if != nil { = } }return , }// checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from// current client state in order to quickly check if *RateLimitError can be immediately returned// from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.// Otherwise it returns nil, and Client.Do should proceed normally.func ( *Client) ( *http.Request, RateLimitCategory) *RateLimitError { .rateMu.Lock() := .rateLimits[] .rateMu.Unlock()if !.Reset.Time.IsZero() && .Remaining == 0 && time.Now().Before(.Reset.Time) {// Create a fake response. := &http.Response{Status: http.StatusText(http.StatusForbidden),StatusCode: http.StatusForbidden,Request: ,Header: make(http.Header),Body: io.NopCloser(strings.NewReader("")), }if .Context().Value(SleepUntilPrimaryRateLimitResetWhenRateLimited) != nil {if := sleepUntilResetWithBuffer(.Context(), .Reset.Time); == nil {returnnil }return &RateLimitError{Rate: ,Response: ,Message: fmt.Sprintf("Context cancelled while waiting for rate limit to reset until %v, not making remote request.", .Reset.Time), } }return &RateLimitError{Rate: ,Response: ,Message: fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", .Limit, .Reset.Time), } }returnnil}// checkSecondaryRateLimitBeforeDo does not make any network calls, but uses existing knowledge from// current client state in order to quickly check if *AbuseRateLimitError can be immediately returned// from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily.// Otherwise it returns nil, and Client.Do should proceed normally.func ( *Client) ( *http.Request) *AbuseRateLimitError { .rateMu.Lock() := .secondaryRateLimitReset .rateMu.Unlock()if !.IsZero() && time.Now().Before() {// Create a fake response. := &http.Response{Status: http.StatusText(http.StatusForbidden),StatusCode: http.StatusForbidden,Request: ,Header: make(http.Header),Body: io.NopCloser(strings.NewReader("")), } := time.Until()return &AbuseRateLimitError{Response: ,Message: fmt.Sprintf("API secondary rate limit exceeded until %v, not making remote request.", ),RetryAfter: &, } }returnnil}// compareHTTPResponse returns whether two http.Response objects are equal or not.// Currently, only StatusCode is checked. This function is used when implementing the// Is(error) bool interface for the custom error types in this package.func compareHTTPResponse(, *http.Response) bool {if == nil && == nil {returntrue }if != nil && != nil {return .StatusCode == .StatusCode }returnfalse}/*An ErrorResponse reports one or more errors caused by an API request.GitHub API docs: https://docs.github.com/rest/#client-errors*/typeErrorResponsestruct { Response *http.Response`json:"-"`// HTTP response that caused this error Message string`json:"message"`// error message Errors []Error`json:"errors"`// more detail on individual errors// Block is only populated on certain types of errors such as code 451. Block *ErrorBlock`json:"block,omitempty"`// Most errors will also include a documentation_url field pointing // to some content that might help you resolve the error, see // https://docs.github.com/rest/#client-errors DocumentationURL string`json:"documentation_url,omitempty"`}// ErrorBlock contains a further explanation for the reason of an error.// See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/// for more information.typeErrorBlockstruct { Reason string`json:"reason,omitempty"` CreatedAt *Timestamp`json:"created_at,omitempty"`}func ( *ErrorResponse) () string {if .Response != nil && .Response.Request != nil {returnfmt.Sprintf("%v %v: %d %v %+v", .Response.Request.Method, sanitizeURL(.Response.Request.URL), .Response.StatusCode, .Message, .Errors) }if .Response != nil {returnfmt.Sprintf("%d %v %+v", .Response.StatusCode, .Message, .Errors) }returnfmt.Sprintf("%v %+v", .Message, .Errors)}// Is returns whether the provided error equals this error.func ( *ErrorResponse) ( error) bool { , := .(*ErrorResponse)if ! {returnfalse }if .Message != .Message || (.DocumentationURL != .DocumentationURL) || !compareHTTPResponse(.Response, .Response) {returnfalse }// Compare Errors.iflen(.Errors) != len(.Errors) {returnfalse }for := range .Errors {if .Errors[] != .Errors[] {returnfalse } }// Compare Block.if (.Block != nil && .Block == nil) || (.Block == nil && .Block != nil) {returnfalse }if .Block != nil && .Block != nil {if .Block.Reason != .Block.Reason {returnfalse }if (.Block.CreatedAt != nil && .Block.CreatedAt == nil) || (.Block.CreatedAt ==nil && .Block.CreatedAt != nil) {returnfalse }if .Block.CreatedAt != nil && .Block.CreatedAt != nil {if *(.Block.CreatedAt) != *(.Block.CreatedAt) {returnfalse } } }returntrue}// TwoFactorAuthError occurs when using HTTP Basic Authentication for a user// that has two-factor authentication enabled. The request can be reattempted// by providing a one-time password in the request.typeTwoFactorAuthErrorErrorResponsefunc ( *TwoFactorAuthError) () string { return (*ErrorResponse)().Error() }// RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit// remaining value of 0.typeRateLimitErrorstruct { Rate Rate// Rate specifies last known rate limit for the client Response *http.Response// HTTP response that caused this error Message string`json:"message"`// error message}func ( *RateLimitError) () string {returnfmt.Sprintf("%v %v: %d %v %v", .Response.Request.Method, sanitizeURL(.Response.Request.URL), .Response.StatusCode, .Message, formatRateReset(time.Until(.Rate.Reset.Time)))}// Is returns whether the provided error equals this error.func ( *RateLimitError) ( error) bool { , := .(*RateLimitError)if ! {returnfalse }return .Rate == .Rate && .Message == .Message &&compareHTTPResponse(.Response, .Response)}// AcceptedError occurs when GitHub returns 202 Accepted response with an// empty body, which means a job was scheduled on the GitHub side to process// the information needed and cache it.// Technically, 202 Accepted is not a real error, it's just used to// indicate that results are not ready yet, but should be available soon.// The request can be repeated after some time.typeAcceptedErrorstruct {// Raw contains the response body. Raw []byte}func (*AcceptedError) () string {return"job scheduled on GitHub side; try again later"}// Is returns whether the provided error equals this error.func ( *AcceptedError) ( error) bool { , := .(*AcceptedError)if ! {returnfalse }returnbytes.Equal(.Raw, .Raw)}// AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the// "documentation_url" field value equal to "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits".typeAbuseRateLimitErrorstruct { Response *http.Response// HTTP response that caused this error Message string`json:"message"`// error message// RetryAfter is provided with some abuse rate limit errors. If present, // it is the amount of time that the client should wait before retrying. // Otherwise, the client should try again later (after an unspecified amount of time). RetryAfter *time.Duration}func ( *AbuseRateLimitError) () string {returnfmt.Sprintf("%v %v: %d %v", .Response.Request.Method, sanitizeURL(.Response.Request.URL), .Response.StatusCode, .Message)}// Is returns whether the provided error equals this error.func ( *AbuseRateLimitError) ( error) bool { , := .(*AbuseRateLimitError)if ! {returnfalse }return .Message == .Message && .RetryAfter == .RetryAfter &&compareHTTPResponse(.Response, .Response)}// sanitizeURL redacts the client_secret parameter from the URL which may be// exposed to the user.func sanitizeURL( *url.URL) *url.URL {if == nil {returnnil } := .Query()iflen(.Get("client_secret")) > 0 { .Set("client_secret", "REDACTED") .RawQuery = .Encode() }return}/*An Error reports more details on an individual error in an ErrorResponse.These are the possible validation error codes: missing: resource does not exist missing_field: a required field on a resource has not been set invalid: the formatting of a field is invalid already_exists: another resource has the same valid as this field custom: some resources return this (e.g. github.User.CreateKey()), additional information is set in the Message field of the ErrorGitHub error responses structure are often undocumented and inconsistent.Sometimes error is just a simple string (Issue #540).In such cases, Message represents an error message as a workaround.GitHub API docs: https://docs.github.com/rest/#client-errors*/typeErrorstruct { Resource string`json:"resource"`// resource on which the error occurred Field string`json:"field"`// field on which the error occurred Code string`json:"code"`// validation error code Message string`json:"message"`// Message describing the error. Errors with Code == "custom" will always have this set.}func ( *Error) () string {returnfmt.Sprintf("%v error caused by %v field on %v resource", .Code, .Field, .Resource)}func ( *Error) ( []byte) error {typeError// avoid infinite recursion by using type alias.if := json.Unmarshal(, (*)()); != nil {returnjson.Unmarshal(, &.Message) // data can be json string. }returnnil}// CheckResponse checks the API response for errors, and returns them if// present. A response is considered an error if it has a status code outside// the 200 range or equal to 202 Accepted.// API error responses are expected to have response// body, and a JSON response body that maps to ErrorResponse.//// The error type will be *RateLimitError for rate limit exceeded errors,// *AcceptedError for 202 Accepted status codes,// and *TwoFactorAuthError for two-factor authentication errors.func ( *http.Response) error {if .StatusCode == http.StatusAccepted {return &AcceptedError{} }if := .StatusCode; 200 <= && <= 299 {returnnil } := &ErrorResponse{Response: } , := io.ReadAll(.Body)if == nil && != nil { = json.Unmarshal(, )if != nil {// reset the response as if this never happened = &ErrorResponse{Response: } } }// Re-populate error response body because GitHub error responses are often // undocumented and inconsistent. // Issue #1136, #540. .Body = io.NopCloser(bytes.NewBuffer())switch {case .StatusCode == http.StatusUnauthorized && strings.HasPrefix(.Header.Get(headerOTP), "required"):return (*TwoFactorAuthError)()case .StatusCode == http.StatusForbidden && .Header.Get(headerRateRemaining) == "0":return &RateLimitError{Rate: parseRate(),Response: .Response,Message: .Message, }case .StatusCode == http.StatusForbidden && (strings.HasSuffix(.DocumentationURL, "#abuse-rate-limits") ||strings.HasSuffix(.DocumentationURL, "secondary-rate-limits")): := &AbuseRateLimitError{Response: .Response,Message: .Message, }if := parseSecondaryRate(); != nil { .RetryAfter = }returndefault:return }}// parseBoolResponse determines the boolean result from a GitHub API response.// Several GitHub API methods return boolean responses indicated by the HTTP// status code in the response (true indicated by a 204, false indicated by a// 404). This helper function will determine that result and hide the 404// error if present. Any other error will be returned through as-is.func parseBoolResponse( error) (bool, error) {if == nil {returntrue, nil }if , := .(*ErrorResponse); && .Response.StatusCode == http.StatusNotFound {// Simply false. In this one case, we do not pass the error through.returnfalse, nil }// some other real error occurredreturnfalse, }typeRateLimitCategoryuint8const (CoreCategoryRateLimitCategory = iotaSearchCategoryGraphqlCategoryIntegrationManifestCategorySourceImportCategoryCodeScanningUploadCategoryActionsRunnerRegistrationCategoryScimCategoryDependencySnapshotsCategoryCodeSearchCategoryAuditLogCategoryCategories// An array of this length will be able to contain all rate limit categories.)// GetRateLimitCategory returns the rate limit RateLimitCategory of the endpoint, determined by HTTP method and Request.URL.Path.func (, string) RateLimitCategory {switch {// https://docs.github.com/rest/rate-limit#about-rate-limitsdefault:// NOTE: coreCategory is returned for actionsRunnerRegistrationCategory too, // because no API found for this category.returnCoreCategory// https://docs.github.com/en/rest/search/search#search-codecasestrings.HasPrefix(, "/search/code") && == http.MethodGet:returnCodeSearchCategorycasestrings.HasPrefix(, "/search/"):returnSearchCategorycase == "/graphql":returnGraphqlCategorycasestrings.HasPrefix(, "/app-manifests/") &&strings.HasSuffix(, "/conversions") && == http.MethodPost:returnIntegrationManifestCategory// https://docs.github.com/rest/migrations/source-imports#start-an-importcasestrings.HasPrefix(, "/repos/") &&strings.HasSuffix(, "/import") && == http.MethodPut:returnSourceImportCategory// https://docs.github.com/rest/code-scanning#upload-an-analysis-as-sarif-datacasestrings.HasSuffix(, "/code-scanning/sarifs"):returnCodeScanningUploadCategory// https://docs.github.com/enterprise-cloud@latest/rest/scimcasestrings.HasPrefix(, "/scim/"):returnScimCategory// https://docs.github.com/en/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repositorycasestrings.HasPrefix(, "/repos/") &&strings.HasSuffix(, "/dependency-graph/snapshots") && == http.MethodPost:returnDependencySnapshotsCategory// https://docs.github.com/en/enterprise-cloud@latest/rest/orgs/orgs?apiVersion=2022-11-28#get-the-audit-log-for-an-organizationcasestrings.HasSuffix(, "/audit-log"):returnAuditLogCategory }}// RateLimits returns the rate limits for the current client.//// Deprecated: Use RateLimitService.Get instead.func ( *Client) ( context.Context) (*RateLimits, *Response, error) {return .RateLimit.Get()}func setCredentialsAsHeaders( *http.Request, , string) *http.Request {// To set extra headers, we must make a copy of the Request so // that we don't modify the Request we were given. This is required by the // specification of http.RoundTripper. // // Since we are going to modify only req.Header here, we only need a deep copy // of req.Header. := new(http.Request) * = * .Header = make(http.Header, len(.Header))for , := range .Header { .Header[] = append([]string(nil), ...) } .SetBasicAuth(, )return}/*UnauthenticatedRateLimitedTransport allows you to make unauthenticated callsthat need to use a higher rate limit associated with your OAuth application. t := &github.UnauthenticatedRateLimitedTransport{ ClientID: "your app's client ID", ClientSecret: "your app's client secret", } client := github.NewClient(t.Client())This will add the client id and secret as a base64-encoded string in the formatClientID:ClientSecret and apply it as an "Authorization": "Basic" header.See https://docs.github.com/rest/#unauthenticated-rate-limited-requests formore information.*/typeUnauthenticatedRateLimitedTransportstruct {// ClientID is the GitHub OAuth client ID of the current application, which // can be found by selecting its entry in the list at // https://github.com/settings/applications. ClientID string// ClientSecret is the GitHub OAuth client secret of the current // application. ClientSecret string// Transport is the underlying HTTP transport to use when making requests. // It will default to http.DefaultTransport if nil. Transport http.RoundTripper}// RoundTrip implements the RoundTripper interface.func ( *UnauthenticatedRateLimitedTransport) ( *http.Request) (*http.Response, error) {if .ClientID == "" {returnnil, errors.New("t.ClientID is empty") }if .ClientSecret == "" {returnnil, errors.New("t.ClientSecret is empty") } := setCredentialsAsHeaders(, .ClientID, .ClientSecret)// Make the HTTP request.return .transport().RoundTrip()}// Client returns an *http.Client that makes requests which are subject to the// rate limit of your OAuth application.func ( *UnauthenticatedRateLimitedTransport) () *http.Client {return &http.Client{Transport: }}func ( *UnauthenticatedRateLimitedTransport) () http.RoundTripper {if .Transport != nil {return .Transport }returnhttp.DefaultTransport}// BasicAuthTransport is an http.RoundTripper that authenticates all requests// using HTTP Basic Authentication with the provided username and password. It// additionally supports users who have two-factor authentication enabled on// their GitHub account.typeBasicAuthTransportstruct { Username string// GitHub username Password string// GitHub password OTP string// one-time password for users with two-factor auth enabled// Transport is the underlying HTTP transport to use when making requests. // It will default to http.DefaultTransport if nil. Transport http.RoundTripper}// RoundTrip implements the RoundTripper interface.func ( *BasicAuthTransport) ( *http.Request) (*http.Response, error) { := setCredentialsAsHeaders(, .Username, .Password)if .OTP != "" { .Header.Set(headerOTP, .OTP) }return .transport().RoundTrip()}// Client returns an *http.Client that makes requests that are authenticated// using HTTP Basic Authentication.func ( *BasicAuthTransport) () *http.Client {return &http.Client{Transport: }}func ( *BasicAuthTransport) () http.RoundTripper {if .Transport != nil {return .Transport }returnhttp.DefaultTransport}// formatRateReset formats d to look like "[rate reset in 2s]" or// "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]"// for the negative cases.func formatRateReset( time.Duration) string { := < 0if { *= -1 } := int(0.5 + .Seconds()) := / 60 := - *60varstringif > 0 { = fmt.Sprintf("%dm%02ds", , ) } else { = fmt.Sprintf("%ds", ) }if {returnfmt.Sprintf("[rate limit was reset %v ago]", ) }returnfmt.Sprintf("[rate reset in %v]", )}func sleepUntilResetWithBuffer( context.Context, time.Time) error { := time.Second := time.NewTimer(time.Until() + )select {case<-.Done():if !.Stop() { <-.C }return .Err()case<-.C: }returnnil}// When using roundTripWithOptionalFollowRedirect, note that it// is the responsibility of the caller to close the response body.func ( *Client) ( context.Context, string, int, ...RequestOption) (*http.Response, error) { , := .NewRequest("GET", , nil, ...)if != nil {returnnil, }var *http.Response// Use http.DefaultTransport if no custom Transport is configured = withContext(, )if .client.Transport == nil { , = http.DefaultTransport.RoundTrip() } else { , = .client.Transport.RoundTrip() }if != nil {returnnil, }// If redirect response is returned, follow itif > 0 && .StatusCode == http.StatusMovedPermanently { _ = .Body.Close() = .Header.Get("Location") , = .(, , -1, ...) }return , }// Bool is a helper routine that allocates a new bool value// to store v and returns a pointer to it.func ( bool) *bool { return & }// Int is a helper routine that allocates a new int value// to store v and returns a pointer to it.func ( int) *int { return & }// Int64 is a helper routine that allocates a new int64 value// to store v and returns a pointer to it.func ( int64) *int64 { return & }// String is a helper routine that allocates a new string value// to store v and returns a pointer to it.func ( string) *string { return & }// roundTripperFunc creates a RoundTripper (transport)type roundTripperFunc func(*http.Request) (*http.Response, error)func ( roundTripperFunc) ( *http.Request) (*http.Response, error) {return ()}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.