diff --git a/docs/tools/forge.md b/docs/tools/forge.md index 18444951..f3dead35 100644 --- a/docs/tools/forge.md +++ b/docs/tools/forge.md @@ -37,6 +37,7 @@ hive-forge pr-reviews 42 --comment -m "msg" # submit COMMENT review 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 +hive-forge list --kind pr --state all --all # every PR across all pages (repo-wide sweep; --all follows pagination) hive-forge milestone # list milestones hive-forge branches deployed/ # filter branches by pattern hive-forge tree-sha main # git tree SHA for a ref diff --git a/hive-forge/src/verbs/list.rs b/hive-forge/src/verbs/list.rs index 00c82870..9cac82df 100644 --- a/hive-forge/src/verbs/list.rs +++ b/hive-forge/src/verbs/list.rs @@ -1,7 +1,8 @@ //! `list [--kind issue|pr|both] [--state open|closed|all] [--assignee -//! ] [--author ] [--label ] [--limit N]` — list -//! issues / PRs with filters. Pretty `#NNN [author] title` output by -//! default; `--json` for piping. +//! ] [--author ] [--label ] [--limit N] [--all]` — +//! list issues / PRs with filters. Pretty `#NNN [author] title` output by +//! default; `--json` for piping. `--all` drains every page (for repo-wide +//! analysis) instead of a single `--limit` page. //! //! Mirrors Forgejo's `GET /repos/{owner}/{repo}/issues` query-string //! filters one-for-one so the mental model carries over. Closes the @@ -86,15 +87,26 @@ pub struct Args { /// Max items to return (default: 30; forge's per-page cap applies). #[arg(long, default_value_t = 30)] limit: u64, + /// Drain ALL matching items across every page, not just the first + /// `--limit` (which the forge caps at its per-page max, ~50). Follows + /// pagination up to a safety cap. Use for repo-wide analysis — e.g. + /// `--kind pr --state all --all` to sweep every PR. Overrides + /// `--limit` (each page is fetched at the per-page max). + #[arg(long)] + all: bool, } pub fn run(client: &Client, args: Args) -> Result<()> { let repo = client.repo(); + // With `--all` each page is fetched at the forge's per-page max so we + // make the fewest round-trips; `get_json_all` then follows the + // `Link: rel="next"` chain. Otherwise honour `--limit` as a single page. + let per_page = if args.all { 50 } else { args.limit }; let mut path = format!( "/repos/{repo}/issues?type={}&state={}&limit={}", args.kind.api_value(), args.state.api_value(), - args.limit + per_page ); if let Some(u) = args.assignee.as_deref() && !u.is_empty() @@ -118,6 +130,20 @@ pub fn run(client: &Client, args: Args) -> Result<()> { let encoded: Vec = args.labels.iter().map(|l| super::pct_encode(l)).collect(); write!(path, "&labels={}", encoded.join(",")).unwrap(); } + if args.all { + // Safety cap: 200 pages * 50/page = 10k items, well past any + // realistic repo while still bounding a runaway loop. + const MAX_PAGES: u32 = 200; + let items = client.get_json_all(&path, MAX_PAGES)?; + if client.json_mode() { + return print_json(&Value::Array(items)); + } + for item in &items { + print_row(item); + } + return Ok(()); + } + let resp = client.get_json(&path)?; if client.json_mode() { return print_json(&resp);