//! `dependency [list|add|remove] [numbers...]` — manage an //! issue/PR's "blocked by" dependency links. Default action: list. //! //! Forgejo's dependency endpoints work on the shared issue/PR index (a //! PR is an issue internally), so this is wired into both `issue ` //! and `pr ` the same way `labels`/`assign`/`timeline` are. //! Setting ``'s dependency on `` means `` is //! blocked by `` — matching the forge web UI's "add dependency" //! action, which is a same-repo-only relationship (there is no //! cross-repo dependency support in this CLI, mirroring the UI). use anyhow::{Result, bail}; use clap::{Args as ClapArgs, Subcommand}; use forgejo_api::structs::IssueMeta; use crate::client::{Client, index}; use crate::verbs::{dependency_summaries, print_json}; #[derive(ClapArgs)] pub struct Args { /// Issue or PR number. pub(crate) number: u64, #[command(subcommand)] action: Option, } #[derive(Subcommand)] enum Action { /// List dependencies (default when no action is given) — the /// issues/PRs this one is blocked by. List, /// Add one or more issues/PRs this one is blocked by. Add { /// Issue/PR numbers to add as dependencies. deps: Vec, }, /// Remove one or more dependency links. Remove { /// Issue/PR numbers to remove as dependencies. deps: Vec, }, } pub fn run(client: &Client, args: Args) -> Result<()> { let (owner, name) = client.owner_repo()?; let idx = index(args.number)?; match args.action.unwrap_or(Action::List) { Action::List => {} Action::Add { deps } => { if deps.is_empty() { bail!("hive-forge dependency add: pass at least one issue/PR number"); } for dep in &deps { client .api() .issue_create_issue_dependencies(owner, name, idx, dep_meta(owner, name, *dep)?) .send()?; } } Action::Remove { deps } => { if deps.is_empty() { bail!("hive-forge dependency remove: pass at least one issue/PR number"); } for dep in &deps { client .api() .issue_remove_issue_dependencies(owner, name, idx, dep_meta(owner, name, *dep)?) .send()?; } } } let deps = dependency_summaries(client, owner, name, args.number)?; print_json(&serde_json::json!(deps)) } /// Build the `IssueMeta` body the create/remove endpoints want. /// /// `owner`/`repo` are filled in with the *same* repo the request URL /// already targets — not left `None`. Forgejo's dependency handler /// resolves the dependency's repo from these body fields rather than /// defaulting an absent value to the URL's own owner/repo, so omitting /// them 404s looking up an empty-string repo (`owner/repo: not found` /// — measured, not guessed: `hive-forge dependency add ` /// failed this way for every same-repo pair before this fix). This CLI /// still only supports same-repo dependencies (matching the forge web /// UI) — the fields are populated, not opened up to cross-repo. fn dep_meta(owner: &str, repo: &str, dep: u64) -> Result { Ok(IssueMeta { index: Some(index(dep)?), owner: Some(owner.to_owned()), repo: Some(repo.to_owned()), }) }