From 11fb2ac0fc9dea570ab1cba411eb4a922761c6e3 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 23 Jun 2026 14:54:45 +0200 Subject: [PATCH] hive-forge: add 'reopen' verb (pr reopen / issue reopen) hive-forge had close but no reopen, so reopening required the non-obvious workaround 'issue edit --state open'. Add a reopen verb mirroring close (PATCH state=open), wired into both 'pr reopen' (kind-checked PR) and 'issue reopen' (kind-checked issue). Updates docs/forge.md + the subcommand enumerations. --- docs/tools/forge.md | 4 +++- hive-forge/src/main.rs | 4 ++-- hive-forge/src/verbs/issue_cmd.rs | 8 +++++++- hive-forge/src/verbs/mod.rs | 1 + hive-forge/src/verbs/pr_cmd.rs | 8 +++++++- hive-forge/src/verbs/reopen.rs | 30 ++++++++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 hive-forge/src/verbs/reopen.rs diff --git a/docs/tools/forge.md b/docs/tools/forge.md index 4240360d..2e4fdd53 100644 --- a/docs/tools/forge.md +++ b/docs/tools/forge.md @@ -19,7 +19,7 @@ under `issue` and `pr` parent commands — `hive-forge pr close 42`, refuses an issue number, which the old generic `close` couldn't). Run `hive-forge pr --help` / `hive-forge issue --help` for the full subcommand list (show/create/edit/status/merge/reviews/commits/diff/view/comment/ -comments/close/labels/assign/timeline as applicable). +comments/close/reopen/labels/assign/timeline as applicable). The flat forms below (`close 42`, `pr-create …`, `pr-status …`, …) still work as **hidden back-compat aliases** during the transition and are dropped from @@ -27,7 +27,9 @@ as **hidden back-compat aliases** during the transition and are dropped from ```bash hive-forge pr close 42 # close a PR (kind-validated) +hive-forge pr reopen 42 # reopen a closed PR (kind-validated) hive-forge issue close 42 # close an issue (kind-validated) +hive-forge issue reopen 42 # reopen a closed issue (kind-validated) hive-forge pr status --pr 42 # PR health (mergeable / CI / reviews) hive-forge issue create --title "..." --body "..." # --- flat aliases below remain valid (hidden) --- diff --git a/hive-forge/src/main.rs b/hive-forge/src/main.rs index 6735f718..a733be23 100644 --- a/hive-forge/src/main.rs +++ b/hive-forge/src/main.rs @@ -55,7 +55,7 @@ enum Verb { /// Dump title + body + all comments for an issue or PR. #[command(hide = true)] View(verbs::view::Args), - /// Issue-scoped commands: `issue …`. + /// Issue-scoped commands: `issue …`. Issue(verbs::issue_cmd::Args), /// Create an issue. Prints the issue URL on success. #[command(hide = true)] @@ -63,7 +63,7 @@ enum Verb { /// Edit an issue's title, body, state, or milestone. #[command(hide = true)] IssueEdit(verbs::issue_edit::Args), - /// PR-scoped commands: `pr …`. + /// PR-scoped commands: `pr …`. Pr(verbs::pr_cmd::Args), /// List a PR's commits as JSON (sha, message, author date, author). /// Survives rebase-rewritten shas — message + author date let a diff --git a/hive-forge/src/verbs/issue_cmd.rs b/hive-forge/src/verbs/issue_cmd.rs index 993cbaeb..886f0611 100644 --- a/hive-forge/src/verbs/issue_cmd.rs +++ b/hive-forge/src/verbs/issue_cmd.rs @@ -1,7 +1,7 @@ //! `issue ` — issue-scoped sub-commands. Wraps the per-verb modules //! under an `issue` parent so `hive-forge issue close 42`, `issue create …`, //! etc. read as kind-namespaced commands. The generic verbs that also work on -//! PRs (view/comment/comments/close/labels/assign/timeline) kind-check the +//! PRs (view/comment/comments/close/reopen/labels/assign/timeline) kind-check the //! number is an issue first (`assert_kind`); the issue-only verbs are //! kind-correct by construction. The flat `issue-*` + bare generic verbs stay //! as hidden back-compat aliases (see `main.rs`). @@ -34,6 +34,8 @@ enum Cmd { Comments(verbs::comments::Args), /// Close the issue. Close(verbs::close::Args), + /// Reopen a closed issue. + Reopen(verbs::reopen::Args), /// List / add / remove labels. Labels(verbs::labels::Args), /// Assign or unassign a user. @@ -65,6 +67,10 @@ pub fn run(client: &Client, args: Args) -> Result<()> { assert_kind(client, a.number, Kind::Issue)?; verbs::close::run(client, a) } + Cmd::Reopen(a) => { + assert_kind(client, a.number, Kind::Issue)?; + verbs::reopen::run(client, a) + } Cmd::Labels(a) => { assert_kind(client, a.number, Kind::Issue)?; verbs::labels::run(client, a) diff --git a/hive-forge/src/verbs/mod.rs b/hive-forge/src/verbs/mod.rs index 0e2bc60c..60b4bb99 100644 --- a/hive-forge/src/verbs/mod.rs +++ b/hive-forge/src/verbs/mod.rs @@ -32,6 +32,7 @@ pub mod pr_create; pub mod pr_merge; pub mod pr_reviews; pub mod pr_status; +pub mod reopen; pub mod repo_add_collaborator; pub mod repo_create; pub mod repo_labels; diff --git a/hive-forge/src/verbs/pr_cmd.rs b/hive-forge/src/verbs/pr_cmd.rs index ea380166..533100f3 100644 --- a/hive-forge/src/verbs/pr_cmd.rs +++ b/hive-forge/src/verbs/pr_cmd.rs @@ -1,7 +1,7 @@ //! `pr ` — PR-scoped sub-commands. Wraps the per-verb modules under a //! `pr` parent so `hive-forge pr close 42`, `pr status --pr 42`, etc. read as //! kind-namespaced commands. The generic verbs that also work on issues -//! (view/comment/comments/close/labels/assign/timeline) kind-check the number +//! (view/comment/comments/close/reopen/labels/assign/timeline) kind-check the number //! is a PR first (`assert_kind`); the PR-only verbs hit `/pulls/…` and are //! kind-correct by construction. The flat `pr-*` + bare generic verbs stay as //! hidden back-compat aliases (see `main.rs`). @@ -42,6 +42,8 @@ enum Cmd { Comments(verbs::comments::Args), /// Close the PR. Close(verbs::close::Args), + /// Reopen a closed PR. + Reopen(verbs::reopen::Args), /// List / add / remove labels. Labels(verbs::labels::Args), /// Assign or unassign a user. @@ -77,6 +79,10 @@ pub fn run(client: &Client, args: Args) -> Result<()> { assert_kind(client, a.number, Kind::Pr)?; verbs::close::run(client, a) } + Cmd::Reopen(a) => { + assert_kind(client, a.number, Kind::Pr)?; + verbs::reopen::run(client, a) + } Cmd::Labels(a) => { assert_kind(client, a.number, Kind::Pr)?; verbs::labels::run(client, a) diff --git a/hive-forge/src/verbs/reopen.rs b/hive-forge/src/verbs/reopen.rs new file mode 100644 index 00000000..0fed88f5 --- /dev/null +++ b/hive-forge/src/verbs/reopen.rs @@ -0,0 +1,30 @@ +//! `reopen ` — reopen a closed issue or PR. +//! +//! Mirror of `close`: sends a PATCH setting the issue/PR `state` back to +//! `open`. PRs share the issue number space, so the same `/issues/` +//! endpoint reopens either. + +use anyhow::Result; +use clap::Args as ClapArgs; +use serde_json::json; + +use crate::client::Client; +use crate::verbs::print_json; + +#[derive(ClapArgs)] +pub struct Args { + /// Issue or PR number. + pub(crate) number: u64, +} + +pub fn run(client: &Client, args: Args) -> Result<()> { + let repo = client.repo(); + let resp = client.patch_json( + &format!("/repos/{repo}/issues/{}", args.number), + &json!({ "state": "open" }), + )?; + print_json(&json!({ + "number": resp.get("number"), + "state": resp.get("state"), + })) +}