// Copyright 2021 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "context" "errors" "fmt" "os" "strings" "github.com/google/go-github/v36/github" ) type githubClient struct { *github.Client } func (gh *githubClient) listTags(ctx context.Context, org, repo string) (_ []*github.RepositoryTag, err error) { defer func() { if err != nil { err = fmt.Errorf("listing tags in github.com/%s/%s: %w", org, repo, err) } }() var allTags []*github.RepositoryTag err = gh.listPages(func(opts *github.ListOptions) (*github.Response, error) { tags, resp, err := gh.Repositories.ListTags(ctx, org, repo, opts) if err != nil { return nil, err } allTags = append(allTags, tags...) return resp, nil }) if err != nil { return nil, err } return allTags, nil } func (gh *githubClient) listReleases(ctx context.Context, org, repo string) (_ []*github.RepositoryRelease, err error) { defer func() { if err != nil { err = fmt.Errorf("listing releases in github.com/%s/%s: %w", org, repo, err) } }() var allReleases []*github.RepositoryRelease err = gh.listPages(func(opts *github.ListOptions) (*github.Response, error) { releases, resp, err := gh.Repositories.ListReleases(ctx, org, repo, opts) if err != nil { return nil, err } allReleases = append(allReleases, releases...) return resp, nil }) if err != nil { return nil, err } return allReleases, nil } // getReleaseByTagIncludingDraft is like // github.RepositoriesService.GetReleaseByTag, but it also considers draft // releases that aren't tagged yet. func (gh *githubClient) getReleaseByTagIncludingDraft(ctx context.Context, org, repo, tag string) (*github.RepositoryRelease, error) { releases, err := gh.listReleases(ctx, org, repo) if err != nil { return nil, err } for _, release := range releases { if release.GetTagName() == tag { return release, nil } } return nil, errReleaseNotFound } var errReleaseNotFound = errors.New("release not found") // githubListPages calls fn repeatedly to get all pages of a large result. // This is useful for fetching all tags or all comments or something similar. func (gh *githubClient) listPages(fn func(opt *github.ListOptions) (*github.Response, error)) error { opt := &github.ListOptions{PerPage: 50} for { resp, err := fn(opt) if err != nil { return err } if resp.NextPage == 0 { return nil } opt.Page = resp.NextPage } } // githubTokenFlag is used to find a GitHub personal access token on the // command line. It accepts a raw token or a path to a file containing a token. type githubTokenFlag string func (f *githubTokenFlag) Set(v string) error { if strings.HasPrefix(v, "ghp_") { *(*string)(f) = v return nil } data, err := os.ReadFile(v) if err != nil { return fmt.Errorf("reading GitHub token: %w", err) } *(*string)(f) = string(bytes.TrimSpace(data)) return nil } func (f *githubTokenFlag) String() string { if f == nil { return "" } return string(*f) }