The forge clamps page_size to 50. branches, pr reviews, the review bodies merged into `comments <pr>`, and view's comment list each sent one request, so anything past the first page was dropped silently. labels::repo_labels did loop, but asked for 100/page and stopped on `len < 100`, so a clamped full page of 50 ended the walk. Add verbs::all_pages: requests 50/page until a short page, capped at 50 pages with a stderr warning when the cap is hit. Use it in all of the above; repo-labels now calls labels::repo_labels instead of its own copy of the loop. list --json now prints the same "N of M shown" stderr trailer as the pretty output; its paging and stdout shape are unchanged. Closes #5051
97 lines
2.9 KiB
Rust
97 lines
2.9 KiB
Rust
//! `view <number> [repo]` — dump title + body + all comments as
|
|
//! markdown.
|
|
|
|
use anyhow::Result;
|
|
use clap::Args as ClapArgs;
|
|
use forgejo_api::structs::{IssueGetCommentsQuery, StateType};
|
|
|
|
use crate::client::{Client, index};
|
|
use crate::notify;
|
|
use crate::verbs::{all_pages, attachment_line, issue_reactions, reaction_summary, refuse_json};
|
|
|
|
#[derive(ClapArgs)]
|
|
pub struct Args {
|
|
/// Issue or PR number.
|
|
pub(crate) number: u64,
|
|
}
|
|
|
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
|
refuse_json(
|
|
client.json_mode(),
|
|
"view",
|
|
"use `issue show`/`pr show` for structured fields",
|
|
)?;
|
|
let repo = client.repo()?;
|
|
// Reading the thread clears its unread notification so the
|
|
// read-before-comment guard (in `comment`) lets a reply through.
|
|
notify::mark_read_best_effort(client, repo, args.number);
|
|
let (owner, name) = client.owner_repo()?;
|
|
let issue = client
|
|
.api()
|
|
.issue_get_issue(owner, name, index(args.number)?)
|
|
.send()?;
|
|
let title = issue.title.as_deref().unwrap_or("");
|
|
let body = issue.body.as_deref().unwrap_or("");
|
|
let state = match issue.state {
|
|
Some(StateType::Open) => "open",
|
|
Some(StateType::Closed) => "closed",
|
|
None => "?",
|
|
};
|
|
let user = issue
|
|
.user
|
|
.as_ref()
|
|
.and_then(|u| u.login.as_deref())
|
|
.unwrap_or("?");
|
|
let kind = if issue.pull_request.is_some() {
|
|
"PR"
|
|
} else {
|
|
"issue"
|
|
};
|
|
println!("# {kind} #{} [{state}] by {user}", args.number);
|
|
println!("{title}");
|
|
if !body.is_empty() {
|
|
println!();
|
|
println!("{body}");
|
|
}
|
|
for a in issue.assets.as_deref().unwrap_or_default() {
|
|
if let Some(line) = attachment_line(a) {
|
|
println!("{line}");
|
|
}
|
|
}
|
|
let reactions = issue_reactions(client, owner, name, args.number)?;
|
|
if let Some(summary) = reaction_summary(&reactions) {
|
|
println!("[reactions: {summary}]");
|
|
}
|
|
let idx = index(args.number)?;
|
|
let comments = all_pages(|page, size| {
|
|
let (_, batch) = client
|
|
.api()
|
|
.issue_get_comments(owner, name, idx, IssueGetCommentsQuery::default())
|
|
.page(page)
|
|
.page_size(size)
|
|
.send()?;
|
|
Ok(batch)
|
|
})?;
|
|
if !comments.is_empty() {
|
|
println!();
|
|
println!("---");
|
|
println!("## Comments ({})", comments.len());
|
|
println!();
|
|
for c in &comments {
|
|
let cu = c
|
|
.user
|
|
.as_ref()
|
|
.and_then(|u| u.login.as_deref())
|
|
.unwrap_or("?");
|
|
let cb = c.body.as_deref().unwrap_or("");
|
|
println!("**{cu}**: {cb}");
|
|
for a in c.assets.as_deref().unwrap_or_default() {
|
|
if let Some(line) = attachment_line(a) {
|
|
println!("{line}");
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|