From a97d1590095d913bbc89fd1827ef1761b33b323c Mon Sep 17 00:00:00 2001 From: damocles Date: Thu, 2 Jul 2026 20:28:55 +0200 Subject: [PATCH] =?UTF-8?q?hive-forge:=20pr=20request-review=20verb=20?= =?UTF-8?q?=E2=80=94=20set/withdraw=20requested=20reviewers=20(closes=20#2?= =?UTF-8?q?153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tools/forge.md | 2 ++ hive-forge/src/main.rs | 4 +++ hive-forge/src/verbs/mod.rs | 1 + hive-forge/src/verbs/pr_cmd.rs | 3 ++ hive-forge/src/verbs/pr_request_review.rs | 41 +++++++++++++++++++++++ 5 files changed, 51 insertions(+) create mode 100644 hive-forge/src/verbs/pr_request_review.rs diff --git a/docs/tools/forge.md b/docs/tools/forge.md index 64fd0f79..b6bcaf03 100644 --- a/docs/tools/forge.md +++ b/docs/tools/forge.md @@ -55,6 +55,8 @@ hive-forge pr-reviews 42 # list reviews; inline comments incl hive-forge pr-reviews 42 --approve # submit APPROVED review hive-forge pr-reviews 42 --request-changes -m "msg" # submit REQUEST_CHANGES review hive-forge pr-reviews 42 --comment -m "msg" # submit COMMENT review +hive-forge pr request-review 42 argus # set argus as a requested reviewer on the PR +hive-forge pr request-review 42 argus --remove # withdraw the request hive-forge diff 42 # unified diff (lockfile hunks collapsed by default) hive-forge diff 42 --full # include unfiltered lockfile hunks hive-forge list # open issues/PRs diff --git a/hive-forge/src/main.rs b/hive-forge/src/main.rs index eb617eac..a679822b 100644 --- a/hive-forge/src/main.rs +++ b/hive-forge/src/main.rs @@ -135,6 +135,9 @@ enum Verb { /// `--request-changes` / `--comment` (with `-m` for the body). #[command(hide = true)] PrReviews(verbs::pr_reviews::Args), + /// Request (or withdraw with `--remove`) a review from a user on a PR. + #[command(hide = true)] + PrRequestReview(verbs::pr_request_review::Args), /// List branches, optionally filtered. Branches(verbs::branches::Args), /// Print the tree SHA at a branch or commit. @@ -200,6 +203,7 @@ fn main() -> Result<()> { Verb::Milestone(a) => verbs::milestone::run(&client, a), Verb::PrMerge(a) => verbs::pr_merge::run(&client, a), Verb::PrReviews(a) => verbs::pr_reviews::run(&client, a), + Verb::PrRequestReview(a) => verbs::pr_request_review::run(&client, a), Verb::Branches(a) => verbs::branches::run(&client, a), Verb::TreeSha(a) => verbs::tree_sha::run(&client, a), Verb::Diff(a) => verbs::diff::run(&client, a), diff --git a/hive-forge/src/verbs/mod.rs b/hive-forge/src/verbs/mod.rs index 60b4bb99..bb48cf5f 100644 --- a/hive-forge/src/verbs/mod.rs +++ b/hive-forge/src/verbs/mod.rs @@ -30,6 +30,7 @@ pub mod pr_cmd; pub mod pr_commits; pub mod pr_create; pub mod pr_merge; +pub mod pr_request_review; pub mod pr_reviews; pub mod pr_status; pub mod reopen; diff --git a/hive-forge/src/verbs/pr_cmd.rs b/hive-forge/src/verbs/pr_cmd.rs index 533100f3..d04ccf0a 100644 --- a/hive-forge/src/verbs/pr_cmd.rs +++ b/hive-forge/src/verbs/pr_cmd.rs @@ -32,6 +32,8 @@ enum Cmd { Merge(verbs::pr_merge::Args), /// List a PR's reviews, or submit one. Reviews(verbs::pr_reviews::Args), + /// Request (or withdraw) a review from a user. + RequestReview(verbs::pr_request_review::Args), /// Print the PR's unified diff. Diff(verbs::diff::Args), /// Show title + body + comments. @@ -61,6 +63,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> { Cmd::Status(a) => verbs::pr_status::run(client, a), Cmd::Merge(a) => verbs::pr_merge::run(client, a), Cmd::Reviews(a) => verbs::pr_reviews::run(client, a), + Cmd::RequestReview(a) => verbs::pr_request_review::run(client, a), Cmd::Diff(a) => verbs::diff::run(client, a), // Generics shared with `issue` — verify the number is a PR first. Cmd::View(a) => { diff --git a/hive-forge/src/verbs/pr_request_review.rs b/hive-forge/src/verbs/pr_request_review.rs new file mode 100644 index 00000000..02f53af0 --- /dev/null +++ b/hive-forge/src/verbs/pr_request_review.rs @@ -0,0 +1,41 @@ +//! `pr-request-review [--remove]` — add or remove a +//! requested reviewer on a PR. Unlike `assign` (assignees, mutate + +//! PATCH the issue) Forgejo has a dedicated endpoint pair for review +//! requests: `POST /repos/{repo}/pulls/{index}/requested_reviewers` +//! adds, `DELETE` (same path + body) removes. Requesting a review from +//! a user who is already requested is a Forgejo-side no-op, so the verb +//! is idempotent in both directions. + +use anyhow::Result; +use clap::Args as ClapArgs; +use serde_json::json; + +use crate::client::Client; + +#[derive(ClapArgs)] +pub struct Args { + /// PR number. + pub(crate) number: u64, + /// User login to request a review from (or withdraw with `--remove`). + user: String, + /// Withdraw the review request instead of adding it. + #[arg(long)] + remove: bool, +} + +pub fn run(client: &Client, args: Args) -> Result<()> { + let repo = client.repo(); + let path = format!("/repos/{repo}/pulls/{}/requested_reviewers", args.number); + let body = json!({ "reviewers": [args.user] }); + if args.remove { + client.delete(&path, Some(&body))?; + println!( + "review request withdrawn: {} on #{}", + args.user, args.number + ); + } else { + client.post_json(&path, &body)?; + println!("review requested: {} on #{}", args.user, args.number); + } + Ok(()) +}