From 7a3e7af6b8192bb25b4cd7ded5a31cca87b03b5c Mon Sep 17 00:00:00 2001 From: damocles Date: Sat, 12 Sep 2026 09:18:19 +0200 Subject: [PATCH] hive-forge: timeline --since no longer errors on an empty window --- hive-forge/src/verbs/timeline.rs | 39 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/hive-forge/src/verbs/timeline.rs b/hive-forge/src/verbs/timeline.rs index 350f7e55..2e9acad2 100644 --- a/hive-forge/src/verbs/timeline.rs +++ b/hive-forge/src/verbs/timeline.rs @@ -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`, 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, 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 = 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;