hive-forge: timeline --since no longer errors on an empty window

This commit is contained in:
damocles 2026-09-12 09:18:19 +02:00 committed by mara
commit 7a3e7af6b8

View file

@ -25,13 +25,14 @@
//! what `forgejo-api` actually returns), so unlike the old `--limit`-only
//! shape this is now an exact count, not a boolean "more" flag.
use anyhow::Result;
use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use forgejo_api::structs::{IssueGetCommentsAndTimelineQuery, TimelineComment};
use serde_json::{Value, json};
use time::format_description::well_known::Rfc3339;
use crate::client::{Client, index};
use crate::verbs::{MAX_LIMIT, PAGE_SIZE, clamp_limit, parse_rfc3339, print_json};
use crate::verbs::{MAX_LIMIT, NullableVec, PAGE_SIZE, clamp_limit, parse_rfc3339, print_json};
#[derive(ClapArgs)]
pub struct Args {
@ -246,6 +247,20 @@ fn fetch_tail(
/// `comments.rs`'s `fetch_since` does: ask for `limit + 1`, and a full
/// extra row means there's more. Returns `(events, more)`, `more` a
/// boolean rather than an exact count.
///
/// Deliberately bypasses the typed `forgejo-api` client
/// (`issue_get_comments_and_timeline`) rather than calling it directly:
/// this endpoint answers an empty `--since` window with a JSON `null`
/// body, and the typed client's generated response type is
/// `Vec<TimelineComment>`, which fails to deserialize `null` at all — the
/// single most common answer to "anything new since my last check?"
/// turns into a hard error instead of an empty list. The sibling
/// `issue_get_comments` endpoint `comments.rs`'s own `fetch_since` calls
/// returns `[]` for the same case and never hits this. [`NullableVec`] is
/// this codebase's existing, tested newtype for exactly this shape of
/// problem — used the same way for reactions and blocking-issue lists —
/// so this reuses it via [`Client::get_api_json`] rather than inventing a
/// second null-tolerant path.
fn fetch_since(
client: &Client,
number: u64,
@ -253,16 +268,18 @@ fn fetch_since(
limit: u64,
) -> Result<(Vec<TimelineComment>, bool)> {
let (owner, name) = client.owner_repo()?;
let query = IssueGetCommentsAndTimelineQuery {
since: Some(since),
before: None,
};
let idx = index(number)?;
let fetch_limit = limit.saturating_add(1);
let (_, events) = client
.api()
.issue_get_comments_and_timeline(owner, name, index(number)?, query)
.page_size(u32::try_from(fetch_limit).unwrap_or(u32::MAX))
.send()?;
let since_str = since
.format(&Rfc3339)
.context("format --since timestamp as RFC 3339")?;
let fetch_limit_str = fetch_limit.to_string();
let path = format!("/repos/{owner}/{name}/issues/{idx}/timeline");
let events: NullableVec<TimelineComment> = client.get_api_json(
&path,
&[("since", since_str.as_str()), ("limit", &fetch_limit_str)],
)?;
let events = events.0;
let limit = usize::try_from(limit).unwrap_or(usize::MAX);
let more = events.len() > limit;
let mut events = events;