cli/pkg/forge/prs.go
Snider 0a3c0204d1 fix(agentci): use log.E() error pattern, add Charm SSH TODOs
Replace fmt.Errorf() with structured log.E() errors in agentci, forge,
jobrunner packages. Update PipelineSignal comment to reflect dispatch
fields. Add TODO markers for charmbracelet/ssh migration across all
exec ssh call sites.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-09 11:15:11 +00:00

109 lines
3.3 KiB
Go

package forge
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
"github.com/host-uk/core/pkg/log"
)
// MergePullRequest merges a pull request with the given method ("squash", "rebase", "merge").
func (c *Client) MergePullRequest(owner, repo string, index int64, method string) error {
style := forgejo.MergeStyleMerge
switch method {
case "squash":
style = forgejo.MergeStyleSquash
case "rebase":
style = forgejo.MergeStyleRebase
}
merged, _, err := c.api.MergePullRequest(owner, repo, index, forgejo.MergePullRequestOption{
Style: style,
DeleteBranchAfterMerge: true,
})
if err != nil {
return log.E("forge.MergePullRequest", "failed to merge pull request", err)
}
if !merged {
return log.E("forge.MergePullRequest", fmt.Sprintf("merge returned false for %s/%s#%d", owner, repo, index), nil)
}
return nil
}
// SetPRDraft sets or clears the draft status on a pull request.
// The Forgejo SDK v2.2.0 doesn't expose the draft field on EditPullRequestOption,
// so we use a raw HTTP PATCH request.
func (c *Client) SetPRDraft(owner, repo string, index int64, draft bool) error {
payload := map[string]bool{"draft": draft}
body, err := json.Marshal(payload)
if err != nil {
return log.E("forge.SetPRDraft", "marshal payload", err)
}
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d", c.url, owner, repo, index)
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(body))
if err != nil {
return log.E("forge.SetPRDraft", "create request", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "token "+c.token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return log.E("forge.SetPRDraft", "failed to update draft status", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return log.E("forge.SetPRDraft", fmt.Sprintf("unexpected status %d", resp.StatusCode), nil)
}
return nil
}
// ListPRReviews returns all reviews for a pull request.
func (c *Client) ListPRReviews(owner, repo string, index int64) ([]*forgejo.PullReview, error) {
var all []*forgejo.PullReview
page := 1
for {
reviews, resp, err := c.api.ListPullReviews(owner, repo, index, forgejo.ListPullReviewsOptions{
ListOptions: forgejo.ListOptions{Page: page, PageSize: 50},
})
if err != nil {
return nil, log.E("forge.ListPRReviews", "failed to list reviews", err)
}
all = append(all, reviews...)
if resp == nil || page >= resp.LastPage {
break
}
page++
}
return all, nil
}
// GetCombinedStatus returns the combined commit status for a ref (SHA or branch).
func (c *Client) GetCombinedStatus(owner, repo string, ref string) (*forgejo.CombinedStatus, error) {
status, _, err := c.api.GetCombinedStatus(owner, repo, ref)
if err != nil {
return nil, log.E("forge.GetCombinedStatus", "failed to get combined status", err)
}
return status, nil
}
// DismissReview dismisses a pull request review by ID.
func (c *Client) DismissReview(owner, repo string, index, reviewID int64, message string) error {
_, err := c.api.DismissPullReview(owner, repo, index, reviewID, forgejo.DismissPullReviewOptions{
Message: message,
})
if err != nil {
return log.E("forge.DismissReview", "failed to dismiss review", err)
}
return nil
}