hive-forge: comments/timeline default to newest, timeline gets a real total
Mara's decision on #4179: the default window for comments/timeline (no flags at all) is now the newest 10, matching an explicit --tail 10 -- agent can still opt into the old oldest-first window via an explicit --limit. Same restructure in both verbs: limit u64 -> Option<u64>, only the explicit-limit branch takes the head path now, the tail-or-default branch covers both --tail N and the no-flags case. timeline.rs also gets the real fix: TimelineListHeaders.x_total_count is a genuine field forgejo-api already returns on every timeline call -- verified against the vendored crate source. The old 'no total exists' premise in the module doc was false, same shape as #3200. Replaced the over-fetch-by-one boolean 'more' with an exact count via a cheap page_size=1 fetch_total, mirroring comments.rs's fetch_tail pagination math for --tail. JSON output reshaped to {events, more_before, more_after, since_more, since_limit_clamped}, matching comments.rs -- a breaking interface change, intentional. Also fixed a real pre-existing bug in comments.rs's truncation_note: the more_before message suggested retrying with --tail, which is nonsensical since more_before only ever fires from a tail-shaped window. Now correctly suggests --limit. Added a regression test. fixes #4179
This commit is contained in:
parent
db0eb8829b
commit
1be4b81f65
2 changed files with 333 additions and 85 deletions
|
|
@ -1,15 +1,15 @@
|
|||
//! `comments <number> [--limit N | --tail N]` — list comments on an
|
||||
//! issue or PR. Replaces the curl fallback.
|
||||
//!
|
||||
//! - `--limit N` (default 10, was 50) returns the first N comments. A
|
||||
//! big default was a silent trap on long threads: a plain `comments`
|
||||
//! call reads as "I've read this thread" when it's really "I've read
|
||||
//! the oldest N of it".
|
||||
//! - `--tail N` returns the *last* N comments, chronological. Reads the
|
||||
//! issue's `comments` count first to compute which page holds the
|
||||
//! tail, then fetches only `ceil(N/50) + 1` pages — bounded by `N`,
|
||||
//! not thread length, so it stays cheap on threads with thousands of
|
||||
//! comments.
|
||||
//! - `--limit N` returns the first N comments (oldest-first) — an
|
||||
//! explicit opt-in: a plain `comments` call used to read as "I've read
|
||||
//! this thread" when it was really "the oldest N of it", so the
|
||||
//! default moved to `--tail`'s window instead (see below).
|
||||
//! - `--tail N` returns the *last* N comments, chronological — and with
|
||||
//! neither flag given, THIS is the default (N = 10): most-recent
|
||||
//! activity. Reads the issue's `comments` count first to compute which
|
||||
//! page holds the tail, then fetches only `ceil(N/50) + 1` pages —
|
||||
//! bounded by `N`, not thread length.
|
||||
//! - **The count of comments outside whatever window is shown is
|
||||
//! always reported** — never a silent truncation.
|
||||
//! - `--since <RFC3339>` filters to comments at or after that
|
||||
|
|
@ -45,14 +45,16 @@ use crate::verbs::{
|
|||
pub struct Args {
|
||||
/// Issue or PR number.
|
||||
pub(crate) number: u64,
|
||||
/// Number of comments from the start of the thread, or (with
|
||||
/// `--since`) the most this call returns — capped at
|
||||
/// [`crate::verbs::MAX_LIMIT`] in the latter case. Mutually
|
||||
/// exclusive with `--tail`.
|
||||
#[arg(long, default_value_t = 10, conflicts_with = "tail")]
|
||||
limit: u64,
|
||||
/// Return the last `N` comments (chronological). Mutually exclusive
|
||||
/// with `--limit`/`--since`.
|
||||
/// Number of comments from the start of the thread (oldest-first),
|
||||
/// or (with `--since`) the most this call returns — capped at
|
||||
/// [`crate::verbs::MAX_LIMIT`] in the latter case. An explicit
|
||||
/// opt-in: with neither this nor `--tail` given, the default is the
|
||||
/// newest 10 (see `--tail`). Mutually exclusive with `--tail`.
|
||||
#[arg(long, conflicts_with = "tail")]
|
||||
limit: Option<u64>,
|
||||
/// Return the last `N` comments (chronological) — the most recent
|
||||
/// activity. This is the default (`N` = 10) when neither `--limit`
|
||||
/// nor `--tail` is given. Mutually exclusive with `--limit`/`--since`.
|
||||
#[arg(long, conflicts_with = "since")]
|
||||
tail: Option<usize>,
|
||||
/// Only show comments at or after this RFC3339 timestamp (same
|
||||
|
|
@ -69,8 +71,8 @@ pub struct Args {
|
|||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo()?;
|
||||
// Truncation info: what sits outside the window we're about to show.
|
||||
// A `--tail` window has nothing after it (it ends at the thread's
|
||||
// current end); a `--limit`/default window has nothing before it (it
|
||||
// A `--tail`/default window has nothing after it (it ends at the
|
||||
// thread's current end); a `--limit` window has nothing before it (it
|
||||
// starts at the thread's beginning); a `--since` window is a boolean
|
||||
// "there's more" (no total exists for a since-filtered query) rather
|
||||
// than an exact count. `more_before`/`more_after` are derived from
|
||||
|
|
@ -80,19 +82,24 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
let (thread, more_before, more_after, since_more, since_clamped) =
|
||||
if let Some(since_str) = &args.since {
|
||||
let since = parse_rfc3339(since_str)?;
|
||||
let (limit, clamped) = clamp_limit(args.limit);
|
||||
let (limit, clamped) = clamp_limit(args.limit.unwrap_or(10));
|
||||
let (thread, more) = fetch_since(client, args.number, since, limit)?;
|
||||
(thread, 0, 0, more, clamped)
|
||||
} else if let Some(n) = args.tail {
|
||||
} else if let Some(limit) = args.limit {
|
||||
let total = fetch_total(client, args.number)?;
|
||||
let thread = fetch_head(client, args.number, limit)?;
|
||||
let more_after = total.saturating_sub(thread.len());
|
||||
(thread, 0, more_after, false, false)
|
||||
} else {
|
||||
// Default (no `--limit`/`--tail`/`--since`): same window as
|
||||
// an explicit `--tail 10` — the newest activity, not the
|
||||
// oldest. `args.tail` still wins if the agent passed a
|
||||
// different count explicitly.
|
||||
let n = args.tail.unwrap_or(10);
|
||||
let total = fetch_total(client, args.number)?;
|
||||
let thread = fetch_tail(client, args.number, n, total)?;
|
||||
let more_before = total.saturating_sub(thread.len());
|
||||
(thread, more_before, 0, false, false)
|
||||
} else {
|
||||
let total = fetch_total(client, args.number)?;
|
||||
let thread = fetch_head(client, args.number, args.limit)?;
|
||||
let more_after = total.saturating_sub(thread.len());
|
||||
(thread, 0, more_after, false, false)
|
||||
};
|
||||
// Merge in PR review bodies (empty for issues — degrades to a
|
||||
// no-op) so review feedback isn't silently dropped.
|
||||
|
|
@ -166,7 +173,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
if since_clamped {
|
||||
println!(
|
||||
"(--limit {} is above the {MAX_LIMIT} cap --since can reliably detect truncation at — clamped)",
|
||||
args.limit
|
||||
args.limit.unwrap_or(10)
|
||||
);
|
||||
}
|
||||
if since_more {
|
||||
|
|
@ -189,7 +196,7 @@ fn truncation_note(more_before: usize, more_after: usize) -> Option<String> {
|
|||
match (more_before > 0, more_after > 0) {
|
||||
(false, false) => None,
|
||||
(true, false) => Some(format!(
|
||||
"({more_before} earlier comment(s) not shown — use --tail to see the start of the thread)"
|
||||
"({more_before} earlier comment(s) not shown — use --limit to see the start of the thread)"
|
||||
)),
|
||||
(false, true) => Some(format!(
|
||||
"({more_after} more comment(s) not shown — use --tail N to see the latest instead)"
|
||||
|
|
@ -554,10 +561,15 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn truncation_note_before_only_mentions_earlier() {
|
||||
fn truncation_note_before_only_points_at_limit() {
|
||||
// Regression: this used to (wrongly) suggest `--tail` — the flag
|
||||
// that produced the very window you're already looking at when
|
||||
// more_before is nonzero. `--limit` is what actually reaches the
|
||||
// start of the thread.
|
||||
let note = truncation_note(12, 0).unwrap();
|
||||
assert!(note.contains("12"), "{note}");
|
||||
assert!(note.contains("earlier"), "{note}");
|
||||
assert!(note.contains("--limit"), "{note}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue