hyperhive/hive-forge/src/verbs/issue_edit.rs
atlas f0e3ed04d3 hive-forge, hivectl, swarmctl: fix clap help passive voice, regen docs
Rewrites every write-good.Passive hit in the hive-forge clap help text
into terse, imperative, active voice (meaning unchanged) and drops
clap-markdown's own fixed footer ('This document was generated
automatically by...') via MarkdownOptions::show_footer(false), since
that string isn't ours to reword and vale flagged it too.

docs/tools/{hivectl,swarmctl,forge}-cli.md are generated from each
crate's clap tree (see hive-forge/src/main.rs's MarkdownDocs verb) —
regenerated here from the fixed source, not hand-edited.

Refs #4549
2026-09-20 13:49:39 +02:00

88 lines
2.9 KiB
Rust

//! `issue edit <number> [--title <t>] [body sources] [--state s]
//! [--milestone id] [repo]` — partial update of an issue. Omitted
//! fields keep their current value. Also backs `pr edit`: Forgejo serves
//! both kinds off the same `/issues/<n>` endpoint, so this is shared
//! as-is — `pr_cmd.rs` wires it in with a `Kind::Pr` check, the same
//! pattern `close`/`reopen`/`labels` already use.
use anyhow::Result;
use clap::{Args as ClapArgs, ValueEnum};
use forgejo_api::structs::EditIssueOption;
use serde_json::json;
use crate::body;
use crate::client::{Client, index};
use crate::verbs::print_json;
#[derive(Copy, Clone, ValueEnum, strum::IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum StateArg {
Open,
Closed,
}
#[derive(ClapArgs)]
pub struct Args {
/// Issue (or PR — shares the same `/issues/<n>` endpoint) number.
pub(crate) number: u64,
/// New title (omit to leave unchanged).
#[arg(long)]
title: Option<String>,
/// Inline body text (omit to leave unchanged).
#[arg(long, conflicts_with = "body_file")]
body: Option<String>,
/// Read body from a file. `-` means stdin.
#[arg(long = "body-file")]
body_file: Option<String>,
/// New state.
#[arg(long, value_enum)]
state: Option<StateArg>,
/// Milestone id (0 to unset).
#[arg(long)]
milestone: Option<u64>,
}
/// # Errors
///
/// Propagates any I/O error from the body input (`--body-file`,
/// stdin), any transport error from the Forgejo REST call (network
/// unreachable, 4xx/5xx response, token missing/invalid), and any
/// I/O error from writing the response to stdout.
pub fn run(client: &Client, args: Args) -> Result<()> {
// Body is partial: only update the body field if a source was
// actually given. Piped stdin without --body/--body-file leaves
// body alone (the partial-update contract). Absent fields ride as
// JSON `null`, which Forgejo's partial-update binding treats as
// "leave unchanged".
let body_explicit = args.body.is_some() || args.body_file.is_some();
let body = if body_explicit {
body::resolve(args.body.as_deref(), args.body_file.as_deref())?
} else {
None
};
let payload = EditIssueOption {
assignee: None,
assignees: None,
body,
due_date: None,
milestone: args.milestone.map(index).transpose()?,
r#ref: None,
state: args.state.map(|s| <&str>::from(s).to_owned()),
title: args.title,
unset_due_date: None,
updated_at: None,
};
let (owner, name) = client.owner_repo()?;
let resp = client
.api()
.issue_edit_issue(owner, name, index(args.number)?, payload)
.send()?;
print_json(&json!({
"number": resp.number,
"title": resp.title,
"state": resp.state,
"milestone": resp.milestone.as_ref().and_then(|m| m.title.as_deref()),
}))
}