diff --git a/hive-forge/src/verbs/pr_reviews.rs b/hive-forge/src/verbs/pr_reviews.rs index 4533f468..8755f218 100644 --- a/hive-forge/src/verbs/pr_reviews.rs +++ b/hive-forge/src/verbs/pr_reviews.rs @@ -32,6 +32,8 @@ pub struct Args { } pub fn run(client: &Client, args: Args) -> Result<()> { + let repo = client.repo(); + let event = if args.approve { Some("APPROVED") } else if args.request_changes { @@ -43,138 +45,44 @@ pub fn run(client: &Client, args: Args) -> Result<()> { }; if let Some(ev) = event { - submit_review(client, args.number, ev, args.body) + // Submit a review. + let payload = json!({ + "event": ev, + "body": args.body.unwrap_or_default(), + }); + let v = client.post_json( + &format!("/repos/{repo}/pulls/{}/reviews", args.number), + &payload, + )?; + // Print a compact summary rather than the full review blob. + let summary = json!({ + "id": v.get("id"), + "state": v.get("state"), + "user": v.get("user").and_then(|u| u.get("login")), + }); + print_json(&summary) } else { if args.body.is_some() { bail!("--body requires one of --approve / --request-changes / --comment"); } - list_reviews(client, args.number) - } -} - -/// Submit a review event (APPROVED / REQUEST_CHANGES / COMMENT) and print -/// a compact summary of the created review. -fn submit_review( - client: &Client, - number: u64, - event: &str, - body: Option, -) -> Result<()> { - let repo = client.repo(); - let payload = json!({ - "event": event, - "body": body.unwrap_or_default(), - }); - let v = client.post_json( - &format!("/repos/{repo}/pulls/{number}/reviews"), - &payload, - )?; - print_json(&json!({ - "id": v.get("id"), - "state": v.get("state"), - "user": v.get("user").and_then(|u| u.get("login")), - })) -} - -/// Fetch inline diff comments for a single review. Returns an empty vec on -/// any error (missing review, network failure) so callers can degrade -/// gracefully. -fn fetch_inline_comments(client: &Client, repo: &str, pr: u64, review_id: u64) -> Vec { - client - .get_json(&format!("/repos/{repo}/pulls/{pr}/reviews/{review_id}/comments")) - .ok() - .and_then(|v| v.as_array().cloned()) - .unwrap_or_default() -} - -/// List all reviews for a PR, dispatching to the appropriate output mode. -fn list_reviews(client: &Client, number: u64) -> Result<()> { - let repo = client.repo(); - let v = client.get_json(&format!("/repos/{repo}/pulls/{number}/reviews"))?; - let reviews = v.as_array().cloned().unwrap_or_default(); - if client.json_mode() { - list_reviews_json(client, repo, number, &reviews) - } else { - list_reviews_text(client, repo, number, &reviews) - } -} - -/// JSON output: one object per review, with an inline `comments` array. -fn list_reviews_json( - client: &Client, - repo: &str, - number: u64, - reviews: &[Value], -) -> Result<()> { - let trimmed: Vec = reviews - .iter() - .map(|r| { - let id = r.get("id").and_then(Value::as_u64).unwrap_or(0); - let inline: Vec = if id > 0 { - fetch_inline_comments(client, repo, number, id) - .iter() - .map(|c| { + // List mode (original behaviour). + let v = client.get_json(&format!("/repos/{repo}/pulls/{}/reviews", args.number))?; + let trimmed: Vec = v + .as_array() + .map(|a| { + a.iter() + .map(|r| { json!({ - "id": c.get("id"), - "path": c.get("path"), - "line": c.get("line"), - "body": c.get("body"), + "id": r.get("id"), + "state": r.get("state"), + "user": r.get("user").and_then(|u| u.get("login")), + "body": r.get("body"), + "comments_count": r.get("comments_count"), }) }) .collect() - } else { - vec![] - }; - json!({ - "id": r.get("id"), - "state": r.get("state"), - "user": r.get("user").and_then(|u| u.get("login")), - "body": r.get("body"), - "comments_count": r.get("comments_count"), - "comments": inline, }) - }) - .collect(); - print_json(&Value::Array(trimmed)) -} - -/// Human-readable output: Markdown-style heading per review, inline -/// comments as `[path:line] body` (line omitted for PR-level comments). -fn list_reviews_text( - client: &Client, - repo: &str, - number: u64, - reviews: &[Value], -) -> Result<()> { - if reviews.is_empty() { - println!("(no reviews)"); - return Ok(()); + .unwrap_or_default(); + print_json(&Value::Array(trimmed)) } - for r in reviews { - let id = r.get("id").and_then(Value::as_u64).unwrap_or(0); - let user = r - .get("user") - .and_then(|u| u.get("login")) - .and_then(Value::as_str) - .unwrap_or("?"); - let state = r.get("state").and_then(Value::as_str).unwrap_or("?"); - let body = r.get("body").and_then(Value::as_str).unwrap_or("").trim(); - println!("### review by {user} ({state})"); - if !body.is_empty() { - println!("{body}"); - } - if id > 0 { - for c in &fetch_inline_comments(client, repo, number, id) { - let path = c.get("path").and_then(Value::as_str).unwrap_or("?"); - let cbody = c.get("body").and_then(Value::as_str).unwrap_or("").trim(); - // PR-level comments have no line; omit `:line` when absent. - match c.get("line").and_then(Value::as_u64) { - Some(line) => println!(" [{path}:{line}] {cbody}"), - None => println!(" [{path}] {cbody}"), - } - } - } - println!(); - } - Ok(()) }