Watch
0
0
Fork
You've already forked hyperhive
0

hive-forge: add --since cursor paging to comments and timeline

This commit is contained in:
damocles 2026-08-03 20:39:39 +02:00 • committed by mara
commit 4cf647aa70
3 changed files with 159 additions and 70 deletions

View file

@ -18,10 +18,14 @@
//! against — so this over-fetches by one (`limit + 1`) and reports
//! "there's more" without saying how much.
//!
//! `--limit` is capped at [`MAX_LIMIT`] (`PAGE_SIZE - 1`): the
//! `--limit` is capped at [`crate::verbs::MAX_LIMIT`]: the
//! over-fetch-by-one trick needs `limit + 1` to fit inside Forgejo's
//! hard 50-per-page cap, or the response silently clamps to 50 and the
//! hard per-page cap, or the response silently clamps and the
//! truncation check can never fire even when there genuinely is more.
//!
//! `--since <RFC3339>` filters to events at or after that timestamp —
//! a cursor: feed the last-seen row's own `created_at` back in next
//! time instead of re-running with a higher `--limit`.
use anyhow::Result;
use clap::Args as ClapArgs;
@ -29,17 +33,7 @@ use forgejo_api::structs::IssueGetCommentsAndTimelineQuery;
use serde_json::{Value, json};
use crate::client::{Client, index};
use crate::verbs::print_json;
/// Forgejo's per-page cap on the timeline endpoint — same hard ceiling
/// `comments.rs`'s `PAGE_SIZE` documents for `/comments`.
const PAGE_SIZE: u64 = 50;
/// Largest `--limit` the over-fetch-by-one trick can still detect
/// truncation at: `limit + 1` must stay within [`PAGE_SIZE`], or the
/// response silently clamps to `PAGE_SIZE` and `more` reads `false` even
/// when there's genuinely more.
const MAX_LIMIT: u64 = PAGE_SIZE - 1;
use crate::verbs::{MAX_LIMIT, clamp_limit, parse_rfc3339, print_json};
#[derive(ClapArgs)]
pub struct Args {
@ -49,14 +43,11 @@ pub struct Args {
/// comment for why). Default kept small on purpose.
#[arg(long, default_value_t = 10)]
limit: u64,
}
/// Effective `--limit` plus whether the request was clamped to
/// [`MAX_LIMIT`]. Pure so the boundary math is unit-testable without a
/// network call.
fn clamp_limit(requested: u64) -> (u64, bool) {
let limit = requested.min(MAX_LIMIT);
(limit, limit < requested)
/// Only show events at or after this RFC3339 timestamp (same format
/// this verb's own output prints) — pass back the last-seen row's
/// `created_at` to fetch only what's new.
#[arg(long)]
since: Option<String>,
}
pub fn run(client: &Client, args: Args) -> Result<()> {
@ -65,17 +56,17 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
// stay within Forgejo's per-page cap or the truncation check goes
// silently blind right at the boundary — see MAX_LIMIT's doc comment.
let (limit, clamped) = clamp_limit(args.limit);
let since = args.since.as_deref().map(parse_rfc3339).transpose()?;
let query = IssueGetCommentsAndTimelineQuery {
since,
before: None,
};
// Over-fetch by one to detect truncation without an exact total —
// see the module doc comment for why there's no count query here.
let fetch_limit = limit.saturating_add(1);
let (_, events) = client
.api()
.issue_get_comments_and_timeline(
owner,
name,
index(args.number)?,
IssueGetCommentsAndTimelineQuery::default(),
)
.issue_get_comments_and_timeline(owner, name, index(args.number)?, query)
.page_size(u32::try_from(fetch_limit).unwrap_or(u32::MAX))
.send()?;
let limit = usize::try_from(limit).unwrap_or(usize::MAX);
@ -288,20 +279,6 @@ mod tests {
//! duplicating the logic" — addressed.
use super::*;
#[test]
fn clamp_limit_passes_small_requests_through() {
assert_eq!(clamp_limit(10), (10, false));
assert_eq!(clamp_limit(MAX_LIMIT), (MAX_LIMIT, false));
}
#[test]
fn clamp_limit_caps_requests_above_the_boundary() {
// Regression: `limit + 1` must never exceed Forgejo's PAGE_SIZE,
// or the over-fetch-by-one truncation check goes silently blind.
assert_eq!(clamp_limit(PAGE_SIZE), (MAX_LIMIT, true));
assert_eq!(clamp_limit(1000), (MAX_LIMIT, true));
}
#[test]
fn comment_renders_body_inline() {
let ev = serde_json::json!({