Watch
0
0
Fork
You've already forked hyperhive
0

hive-forge: pr status lists only pending review requests

This commit is contained in:
damocles 2026-10-26 17:50:48 +01:00 • committed by mara
commit 1ff4a0057e
2 changed files with 43 additions and 2 deletions

View file

@ -12,6 +12,7 @@
use anyhow::{Context, Result, bail};
use clap::Args as ClapArgs;
use forgejo_api::structs::IssueGetCommentsQuery;
use hive_types::review::{ReviewInfo, standing};
use serde_json::Value;
use crate::client::{Client, index};
@ -100,6 +101,16 @@ fn sha_status(client: &Client, sha: &str) -> Result<()> {
}
}
/// The forge keeps a reviewer who has already answered in `requested_reviewers`,
/// so a login only counts as a pending request when it has no review that still
/// stands. A stale or dismissed review needs a fresh one, so that reviewer stays.
fn pending_requests(listed: Vec<String>, reviews: &[ReviewInfo]) -> Vec<String> {
listed
.into_iter()
.filter(|login| standing(reviews, login).is_none_or(|r| r.state == "REQUEST_REVIEW"))
.collect()
}
/// Full PR health view. Exit code is a merge-readiness verdict.
fn pr_status(client: &Client, repo: &str, pr: u64) -> Result<()> {
let (owner, name) = client.owner_repo()?;
@ -122,7 +133,7 @@ fn pr_status(client: &Client, repo: &str, pr: u64) -> Result<()> {
.and_then(|h| h.sha.clone())
.with_context(|| format!("pr status: PR #{pr} has no head.sha"))?;
let requested: Vec<String> = pull
let listed: Vec<String> = pull
.requested_reviewers
.as_deref()
.unwrap_or_default()
@ -132,6 +143,7 @@ fn pr_status(client: &Client, repo: &str, pr: u64) -> Result<()> {
let (ci_state, ci_statuses) = fetch_combined(client, &sha)?;
let reviews = super::latest_reviews(client, repo, pr)?;
let requested = pending_requests(listed, &reviews);
let last = last_comment(client, pr)?;
if client.json_mode() {
@ -402,6 +414,33 @@ mod tests {
use super::*;
use clap::Parser;
fn info(login: &str, verdict: &str, old_head: bool) -> ReviewInfo {
ReviewInfo {
login: login.to_owned(),
state: verdict.to_owned(),
stale: old_head,
dismissed: false,
}
}
/// A reviewer whose verdict still stands has answered; one with no
/// review, a bare re-request, or a verdict on an older head is pending.
#[test]
fn only_reviewers_without_a_standing_verdict_are_pending() {
let reviews = [
info("answered", "APPROVED", false),
info("rerequested", "REQUEST_REVIEW", false),
info("stale", "REQUEST_CHANGES", true),
];
let listed = ["answered", "rerequested", "stale", "never-reviewed"]
.map(String::from)
.to_vec();
assert_eq!(
pending_requests(listed, &reviews),
["rerequested", "stale", "never-reviewed"]
);
}
/// `Args` is a flattened arg group, not a `Parser`, so parsing it
/// standalone needs a wrapper. Without one these cases could only be
/// written against the whole binary's clap tree.