xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/releaser/github.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1// Copyright 2021 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"bytes"
19	"context"
20	"errors"
21	"fmt"
22	"os"
23	"strings"
24
25	"github.com/google/go-github/v36/github"
26)
27
28type githubClient struct {
29	*github.Client
30}
31
32func (gh *githubClient) listTags(ctx context.Context, org, repo string) (_ []*github.RepositoryTag, err error) {
33	defer func() {
34		if err != nil {
35			err = fmt.Errorf("listing tags in github.com/%s/%s: %w", org, repo, err)
36		}
37	}()
38
39	var allTags []*github.RepositoryTag
40	err = gh.listPages(func(opts *github.ListOptions) (*github.Response, error) {
41		tags, resp, err := gh.Repositories.ListTags(ctx, org, repo, opts)
42		if err != nil {
43			return nil, err
44		}
45		allTags = append(allTags, tags...)
46		return resp, nil
47	})
48	if err != nil {
49		return nil, err
50	}
51	return allTags, nil
52}
53
54func (gh *githubClient) listReleases(ctx context.Context, org, repo string) (_ []*github.RepositoryRelease, err error) {
55	defer func() {
56		if err != nil {
57			err = fmt.Errorf("listing releases in github.com/%s/%s: %w", org, repo, err)
58		}
59	}()
60
61	var allReleases []*github.RepositoryRelease
62	err = gh.listPages(func(opts *github.ListOptions) (*github.Response, error) {
63		releases, resp, err := gh.Repositories.ListReleases(ctx, org, repo, opts)
64		if err != nil {
65			return nil, err
66		}
67		allReleases = append(allReleases, releases...)
68		return resp, nil
69	})
70	if err != nil {
71		return nil, err
72	}
73	return allReleases, nil
74}
75
76// getReleaseByTagIncludingDraft is like
77// github.RepositoriesService.GetReleaseByTag, but it also considers draft
78// releases that aren't tagged yet.
79func (gh *githubClient) getReleaseByTagIncludingDraft(ctx context.Context, org, repo, tag string) (*github.RepositoryRelease, error) {
80	releases, err := gh.listReleases(ctx, org, repo)
81	if err != nil {
82		return nil, err
83	}
84	for _, release := range releases {
85		if release.GetTagName() == tag {
86			return release, nil
87		}
88	}
89	return nil, errReleaseNotFound
90}
91
92var errReleaseNotFound = errors.New("release not found")
93
94// githubListPages calls fn repeatedly to get all pages of a large result.
95// This is useful for fetching all tags or all comments or something similar.
96func (gh *githubClient) listPages(fn func(opt *github.ListOptions) (*github.Response, error)) error {
97	opt := &github.ListOptions{PerPage: 50}
98	for {
99		resp, err := fn(opt)
100		if err != nil {
101			return err
102		}
103		if resp.NextPage == 0 {
104			return nil
105		}
106		opt.Page = resp.NextPage
107	}
108}
109
110// githubTokenFlag is used to find a GitHub personal access token on the
111// command line. It accepts a raw token or a path to a file containing a token.
112type githubTokenFlag string
113
114func (f *githubTokenFlag) Set(v string) error {
115	if strings.HasPrefix(v, "ghp_") {
116		*(*string)(f) = v
117		return nil
118	}
119	data, err := os.ReadFile(v)
120	if err != nil {
121		return fmt.Errorf("reading GitHub token: %w", err)
122	}
123	*(*string)(f) = string(bytes.TrimSpace(data))
124	return nil
125}
126
127func (f *githubTokenFlag) String() string {
128	if f == nil {
129		return ""
130	}
131	return string(*f)
132}
133