diff --git a/cmd/review-bot/main.go b/cmd/review-bot/main.go index b37f448..8163ea1 100644 --- a/cmd/review-bot/main.go +++ b/cmd/review-bot/main.go @@ -316,11 +316,35 @@ func main() { // POST new review (first run, or state transition fallthrough) log.Printf("Posting review (event=%s)...", event) - _, err = giteaClient.PostReview(ctx, owner, repoName, prNumber, event, reviewBody, inlineComments) + posted, err := giteaClient.PostReview(ctx, owner, repoName, prNumber, event, reviewBody, inlineComments) if err != nil { log.Fatalf("Failed to post review: %v", err) } - log.Printf("Review posted successfully") + log.Printf("Review posted (id=%d, user=%s)", posted.ID, posted.User.Login) + + // Post-posting escalation: if we just posted APPROVED but a sibling + // from the same user has REQUEST_CHANGES, mark ours as superseded and + // re-post as REQUEST_CHANGES. This handles the first-run case where + // we don't know our login until after posting. + if event == "APPROVED" && *updateExisting && *reviewerName != "" { + reviews, err := giteaClient.ListReviews(ctx, owner, repoName, prNumber) + if err == nil && shouldEscalate(reviews, posted.ID, posted.User.Login, sentinel) { + log.Printf("Post-posting escalation: sibling has REQUEST_CHANGES") + // Mark our just-posted review as superseded + commentID, err := giteaClient.GetTimelineReviewCommentID(ctx, owner, repoName, prNumber, sentinel) + if err == nil { + supersededBody := fmt.Sprintf("~~*This review has been superseded by a newer review below.*~~\n\n%s", sentinel) + giteaClient.EditComment(ctx, owner, repoName, commentID, supersededBody) + } + // Re-post as REQUEST_CHANGES + _, err = giteaClient.PostReview(ctx, owner, repoName, prNumber, "REQUEST_CHANGES", reviewBody, inlineComments) + if err != nil { + log.Printf("Warning: could not re-post as REQUEST_CHANGES: %v", err) + } else { + log.Printf("Review escalated to REQUEST_CHANGES") + } + } + } } // fetchFileContext fetches the full content of modified files from the PR branch. @@ -508,9 +532,7 @@ func reviewUnchanged(reviews []gitea.Review, newBody, newEvent, sentinel string) if !strings.Contains(r.Body, sentinel) { continue } - // Compare state (map APPROVED back from Gitea's representation) - existingEvent := r.State - if existingEvent == r.State && existingEvent == newEvent && r.Body == newBody { + if r.State == newEvent && r.Body == newBody { return true } } diff --git a/gitea/client.go b/gitea/client.go index ee15e9a..9355e29 100644 --- a/gitea/client.go +++ b/gitea/client.go @@ -407,7 +407,10 @@ func (c *Client) EditComment(ctx context.Context, owner, repo string, commentID payload := struct { Body string `json:"body"` }{Body: newBody} - data, _ := json.Marshal(payload) + data, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("marshal edit payload: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodPatch, reqURL, bytes.NewReader(data)) if err != nil {