From 3ff4dc7be00bcd55a50f9e61ab3666fd8a094c17 Mon Sep 17 00:00:00 2001 From: damocles Date: Thu, 5 Nov 2026 07:52:03 +0100 Subject: [PATCH] hive-forge: review-reply adds the comment to the answered comment's review, as the web UI does --- hive-forge/src/verbs/pr_review_reply.rs | 47 +++++++++++-------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/hive-forge/src/verbs/pr_review_reply.rs b/hive-forge/src/verbs/pr_review_reply.rs index 96782f45..d1d3882f 100644 --- a/hive-forge/src/verbs/pr_review_reply.rs +++ b/hive-forge/src/verbs/pr_review_reply.rs @@ -1,12 +1,12 @@ //! `pr review-reply ` - answer an inline review comment. //! -//! Forgejo's API has no reply endpoint, so the reply is a one-comment -//! `COMMENT` review anchored at the same path and line as the comment it -//! answers; the forge shows comments with one anchor as one conversation. +//! Forgejo's API has no reply endpoint. The web UI stores a reply in the same +//! review as the comment it answers, so this adds a comment to that review at +//! the same path and line. A new review at the same anchor is not enough: the +//! forge shows it as a conversation of its own. use anyhow::{Result, bail}; use clap::Args as ClapArgs; -use forgejo_api::structs::{CreatePullReviewComment, CreatePullReviewOptions}; use serde_json::{Value, json}; use crate::body; @@ -66,8 +66,9 @@ fn anchor_of(comment: &Value) -> Result { }) } -/// Find the inline comment with `id` among the PR's reviews. -fn find_comment(client: &Client, number: u64, id: i64) -> Result { +/// Find the inline comment with `id` among the PR's reviews, with the id of the +/// review it belongs to. +fn find_comment(client: &Client, number: u64, id: i64) -> Result<(i64, Value)> { let (owner, name) = client.owner_repo()?; let reviews = all_pages(|page, size| { let (_, batch) = client @@ -86,7 +87,7 @@ fn find_comment(client: &Client, number: u64, id: i64) -> Result { .into_iter() .find(|c| c.get("id").and_then(Value::as_i64) == Some(id)); if let Some(c) = found { - return Ok(c); + return Ok((review_id, c)); } } bail!("no inline review comment #{id} on PR #{number}; `pr reviews {number}` lists their ids") @@ -102,34 +103,28 @@ pub fn run(client: &Client, args: Args) -> Result<()> { args.body_file.as_deref(), "pr review-reply", )?; - let target = find_comment(client, args.number, args.comment_id)?; + let (review_id, target) = find_comment(client, args.number, args.comment_id)?; let anchor = anchor_of(&target)?; let (owner, name) = client.owner_repo()?; - let review = client + let comment = client .api() - .repo_create_pull_review( + .repo_create_pull_review_comment( owner, name, index(args.number)?, - CreatePullReviewOptions { - body: Some(String::new()), - comments: Some(vec![CreatePullReviewComment { - body: Some(text), - extra_lines_count: Some(anchor.extra_lines), - new_position: Some(anchor.line), - old_position: Some(0), - path: Some(anchor.path), - }]), - commit_id: target - .get("commit_id") - .and_then(Value::as_str) - .map(str::to_owned), - event: Some("COMMENT".to_owned()), - }, + review_id, + json!({ + "body": text, + "extra_lines_count": anchor.extra_lines, + "new_position": anchor.line, + "old_position": 0, + "path": anchor.path, + }), ) .send()?; print_json(&json!({ - "review": review.id, + "comment": comment.id, + "review": review_id, "in_reply_to": args.comment_id, "at": comment_location(&target), }))