package gitea import ( "fmt" "os" "strings" giteaSDK "code.gitea.io/sdk/gitea" ) const prMarker = "" // Client wraps the Gitea API. type Client struct { client *giteaSDK.Client owner string repo string } const commentMarker = "" // NewClient creates a Gitea client from GITEA_TOKEN and GITEA_BASE_URL environment variables. // The repoSlug must be in "owner/repo" format. func NewClient(repoSlug string) (*Client, error) { token := os.Getenv("GITEA_TOKEN") if token == "" { return nil, fmt.Errorf("GITEA_TOKEN environment variable is not set") } baseURL := os.Getenv("GITEA_BASE_URL") if baseURL == "" { return nil, fmt.Errorf("GITEA_BASE_URL 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) } client, err := giteaSDK.NewClient(baseURL, giteaSDK.SetToken(token)) if err != nil { return nil, fmt.Errorf("creating Gitea client: %w", err) } return &Client{client: client, owner: parts[0], repo: parts[1]}, nil } // CreateRelease creates a Gitea release for the given tag. func (c *Client) CreateRelease(tag, name, body string, draft, prerelease bool) (int64, error) { rel, _, err := c.client.CreateRelease(c.owner, c.repo, giteaSDK.CreateReleaseOption{ TagName: tag, Title: name, Note: body, IsDraft: draft, IsPrerelease: prerelease, }) if err != nil { return 0, fmt.Errorf("creating Gitea release %s: %w", tag, err) } return rel.ID, nil } // UploadAsset uploads a file to an existing Gitea release. func (c *Client) UploadAsset(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.CreateReleaseAttachment(c.owner, c.repo, releaseID, f, fileName) if err != nil { return fmt.Errorf("uploading %s to release %d: %w", fileName, releaseID, err) } return nil } // UpsertPR creates or updates a stamp release PR on the given base branch. // It identifies the existing PR by scanning open PRs for the hidden marker in the body. func (c *Client) UpsertPR(head, base, title, body string) (int64, string, error) { markedBody := prMarker + "\n" + body // Search open PRs for an existing stamp release PR (Gitea SDK does not support // filtering by head branch, so we filter client-side). prs, _, err := c.client.ListRepoPullRequests(c.owner, c.repo, giteaSDK.ListPullRequestsOptions{ State: giteaSDK.StateOpen, }) if err != nil { return 0, "", fmt.Errorf("listing pull requests: %w", err) } for _, pr := range prs { if pr.Head != nil && pr.Head.Name != head { continue } if strings.Contains(pr.Body, prMarker) { // Update existing PR. updated, _, err := c.client.EditPullRequest(c.owner, c.repo, pr.Index, giteaSDK.EditPullRequestOption{ Title: title, Body: &markedBody, }) if err != nil { return 0, "", fmt.Errorf("updating pull request #%d: %w", pr.Index, err) } return updated.Index, updated.HTMLURL, nil } } // Create a new PR. pr, _, err := c.client.CreatePullRequest(c.owner, c.repo, giteaSDK.CreatePullRequestOption{ Head: head, Base: base, Title: title, Body: markedBody, }) if err != nil { return 0, "", fmt.Errorf("creating pull request: %w", err) } return pr.Index, pr.HTMLURL, nil } // UpsertPRComment posts or updates a stamped PR comment. func (c *Client) UpsertPRComment(prNumber int, body string) error { markedBody := commentMarker + "\n" + body comments, _, err := c.client.ListIssueComments(c.owner, c.repo, int64(prNumber), giteaSDK.ListIssueCommentOptions{}) if err != nil { return fmt.Errorf("listing PR comments: %w", err) } for _, comment := range comments { if strings.Contains(comment.Body, commentMarker) { _, _, err = c.client.EditIssueComment(c.owner, c.repo, comment.ID, giteaSDK.EditIssueCommentOption{Body: markedBody}) return err } } _, _, err = c.client.CreateIssueComment(c.owner, c.repo, int64(prNumber), giteaSDK.CreateIssueCommentOption{Body: markedBody}) return err }