143 lines
4.1 KiB
Go
143 lines
4.1 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
gh "github.com/google/go-github/v70/github"
|
|
)
|
|
|
|
// Client wraps the GitHub REST API.
|
|
type Client struct {
|
|
client *gh.Client
|
|
owner string
|
|
repo string
|
|
}
|
|
|
|
// NewClient creates a GitHub client from the GITHUB_TOKEN environment variable.
|
|
// The repoSlug must be in "owner/repo" format.
|
|
func NewClient(repoSlug string) (*Client, error) {
|
|
token := os.Getenv("GITHUB_TOKEN")
|
|
if token == "" {
|
|
return nil, fmt.Errorf("GITHUB_TOKEN environment variable is not set")
|
|
}
|
|
parts := strings.SplitN(repoSlug, "/", 2)
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid repo slug %q: expected owner/repo", repoSlug)
|
|
}
|
|
return &Client{
|
|
client: gh.NewClient(nil).WithAuthToken(token),
|
|
owner: parts[0],
|
|
repo: parts[1],
|
|
}, nil
|
|
}
|
|
|
|
// CreateRelease creates a GitHub release for the given tag.
|
|
func (c *Client) CreateRelease(ctx context.Context, tag, name, body string, draft, prerelease bool) (int64, error) {
|
|
rel, _, err := c.client.Repositories.CreateRelease(ctx, c.owner, c.repo, &gh.RepositoryRelease{
|
|
TagName: gh.Ptr(tag),
|
|
Name: gh.Ptr(name),
|
|
Body: gh.Ptr(body),
|
|
Draft: gh.Ptr(draft),
|
|
Prerelease: gh.Ptr(prerelease),
|
|
})
|
|
if err != nil {
|
|
return 0, fmt.Errorf("creating GitHub release %s: %w", tag, err)
|
|
}
|
|
return rel.GetID(), nil
|
|
}
|
|
|
|
// UploadAsset uploads a file to an existing release.
|
|
func (c *Client) UploadAsset(ctx context.Context, releaseID int64, filePath string) error {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("opening %s: %w", filePath, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
name := strings.Split(filePath, "/")
|
|
fileName := name[len(name)-1]
|
|
|
|
_, _, err = c.client.Repositories.UploadReleaseAsset(ctx, c.owner, c.repo, releaseID,
|
|
&gh.UploadOptions{Name: fileName}, f)
|
|
if err != nil {
|
|
return fmt.Errorf("uploading %s to release %d: %w", fileName, releaseID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const commentMarker = "<!-- stamp-pr-comment -->"
|
|
|
|
// UpsertPRComment posts or updates a stamped PR comment (identified by a hidden marker).
|
|
func (c *Client) UpsertPRComment(ctx context.Context, prNumber int, body string) error {
|
|
markedBody := commentMarker + "\n" + body
|
|
|
|
// List existing comments to find ours.
|
|
comments, _, err := c.client.Issues.ListComments(ctx, c.owner, c.repo, prNumber, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("listing PR comments: %w", err)
|
|
}
|
|
|
|
for _, comment := range comments {
|
|
if strings.Contains(comment.GetBody(), commentMarker) {
|
|
// Update existing comment.
|
|
_, _, err = c.client.Issues.EditComment(ctx, c.owner, c.repo, comment.GetID(),
|
|
&gh.IssueComment{Body: gh.Ptr(markedBody)})
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create new comment.
|
|
_, _, err = c.client.Issues.CreateComment(ctx, c.owner, c.repo, prNumber,
|
|
&gh.IssueComment{Body: gh.Ptr(markedBody)})
|
|
return err
|
|
}
|
|
|
|
const prMarker = "<!-- stamp-release-pr -->"
|
|
|
|
// UpsertPR creates or updates a release PR from head into base.
|
|
// It identifies a previously-created PR by the hidden marker in the body.
|
|
// Returns the PR number and the URL.
|
|
func (c *Client) UpsertPR(ctx context.Context, head, base, title, body string) (int, string, error) {
|
|
markedBody := prMarker + "\n" + body
|
|
|
|
// Search open PRs from this head branch.
|
|
prs, _, err := c.client.PullRequests.List(ctx, c.owner, c.repo, &gh.PullRequestListOptions{
|
|
State: "open",
|
|
Head: c.owner + ":" + head,
|
|
Base: base,
|
|
})
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("listing pull requests: %w", err)
|
|
}
|
|
|
|
for _, pr := range prs {
|
|
if strings.Contains(pr.GetBody(), prMarker) {
|
|
// Update existing PR.
|
|
updated, _, err := c.client.PullRequests.Edit(ctx, c.owner, c.repo, pr.GetNumber(),
|
|
&gh.PullRequest{
|
|
Title: gh.Ptr(title),
|
|
Body: gh.Ptr(markedBody),
|
|
})
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("updating pull request: %w", err)
|
|
}
|
|
return updated.GetNumber(), updated.GetHTMLURL(), nil
|
|
}
|
|
}
|
|
|
|
// Create new PR.
|
|
pr, _, err := c.client.PullRequests.Create(ctx, c.owner, c.repo, &gh.NewPullRequest{
|
|
Title: gh.Ptr(title),
|
|
Head: gh.Ptr(head),
|
|
Base: gh.Ptr(base),
|
|
Body: gh.Ptr(markedBody),
|
|
})
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("creating pull request: %w", err)
|
|
}
|
|
return pr.GetNumber(), pr.GetHTMLURL(), nil
|
|
}
|