Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/commands/list/collect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1987,8 +1987,6 @@ pub fn collect(
item.refresh_status_symbols(primary_target);
}

// Count errors for summary
let error_count = errors.len();
let timed_out_count = errors.iter().filter(|e| e.is_timeout()).count();

let table_render = render_table.then(|| TableRenderPlan {
Expand All @@ -2002,7 +2000,6 @@ pub fn collect(
&all_items,
show_branches || show_remotes,
layout.hidden_column_count,
error_count,
timed_out_count,
),
});
Expand Down
52 changes: 19 additions & 33 deletions src/commands/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,17 @@ impl SummaryMetrics {
}
}

/// Format a summary message for the given items (used by both collect/mod.rs and mod.rs)
/// Format the summary line that closes the table.
///
/// The footer is the only home a timed-out task has: it leaves an empty cell
/// and no message to print, so the count here is all that reports it. Tasks
/// that failed instead get a named entry in the warning that follows the
/// table, and that warning carries its own count — repeating it here would
/// print the same number twice on adjacent lines.
pub(crate) fn format_summary_message(
items: &[ListItem],
show_branches: bool,
hidden_column_count: usize,
error_count: usize,
timed_out_count: usize,
) -> String {
let metrics = SummaryMetrics::from_items(items);
Expand All @@ -379,25 +384,11 @@ pub(crate) fn format_summary_message(
.summary_parts(show_branches, hidden_column_count)
.join(", ");

if error_count > 0 {
// "failed" and "timed out" are disjoint here, matching the warning
// that details the non-timeout failures after the table.
let failed_count = error_count - timed_out_count;
let failure_msg = match (failed_count, timed_out_count) {
(0, t) => {
let plural = if t == 1 { "" } else { "s" };
format!("{t} task{plural} timed out")
}
(f, 0) => {
let plural = if f == 1 { "" } else { "s" };
format!("{f} task{plural} failed")
}
(f, t) => {
let plural = if f == 1 { "" } else { "s" };
format!("{f} task{plural} failed, {t} timed out")
}
};
format!("{INFO_SYMBOL} {dim}Showing {summary}; {failure_msg}{dim:#}")
if timed_out_count > 0 {
let plural = if timed_out_count == 1 { "" } else { "s" };
format!(
"{INFO_SYMBOL} {dim}Showing {summary}; {timed_out_count} task{plural} timed out{dim:#}"
)
} else {
format!("{INFO_SYMBOL} {dim}Showing {summary}{dim:#}")
}
Expand Down Expand Up @@ -538,20 +529,15 @@ mod tests {
}

#[test]
fn test_format_summary_message_error_variants() {
fn test_format_summary_message_timeout_variants() {
use insta::assert_snapshot;

// No errors
assert_snapshot!(format_summary_message(&[], false, 0, 0, 0), @"○ Showing 0 worktrees");
// All timeouts
assert_snapshot!(format_summary_message(&[], false, 0, 3, 3), @"○ Showing 0 worktrees; 3 tasks timed out");
// Mixed errors and timeouts
assert_snapshot!(format_summary_message(&[], false, 0, 5, 3), @"○ Showing 0 worktrees; 2 tasks failed, 3 timed out");
// Only failures, no timeouts
assert_snapshot!(format_summary_message(&[], false, 0, 2, 0), @"○ Showing 0 worktrees; 2 tasks failed");
// Single error
assert_snapshot!(format_summary_message(&[], false, 0, 1, 0), @"○ Showing 0 worktrees; 1 task failed");
// Nothing timed out. Failures alone leave the footer untouched — the
// warning after the table names them and carries their count.
assert_snapshot!(format_summary_message(&[], false, 0, 0), @"○ Showing 0 worktrees");
// Single timeout
assert_snapshot!(format_summary_message(&[], false, 0, 1, 1), @"○ Showing 0 worktrees; 1 task timed out");
assert_snapshot!(format_summary_message(&[], false, 0, 1), @"○ Showing 0 worktrees; 1 task timed out");
// Several timeouts
assert_snapshot!(format_summary_message(&[], false, 0, 3), @"○ Showing 0 worktrees; 3 tasks timed out");
}
}
8 changes: 5 additions & 3 deletions src/commands/list/model/status_symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,11 @@
//!
//! 1. Each position's last-known state is displayed: resolved positions show
//! their symbol; unresolved positions show `·`.
//! 2. The diagnostic footer already lists which tasks did not finish per
//! item — this continues unchanged and gives the user the mapping from
//! "`·` in position X" back to "`TaskKind::Foo` timed out."
//! 2. The `·` is the whole signal — budget truncation is deliberate, so it
//! goes unreported, and nothing maps a `·` in position X back to the
//! `TaskKind` that didn't finish. (A task whose own git command times out
//! is a separate path: it reaches the summary footer as one of "N tasks
//! timed out", a count that likewise doesn't name the task.)
//! 3. JSON output omits fields that correspond to unresolved gates
//! (`working_tree`, `main_state`, `operation_state`, `upstream_divergence`,
//! etc.) so machine consumers can distinguish "loading / timeout" from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ exit_code: 0
----- stderr -----
▲ Failed to batch-fetch commit details: fatal: bad object 0000000000000000000000000000000000000001

○ Showing 5 worktrees, 3 ahead; 10 tasks failed
○ Showing 5 worktrees, 3 ahead
▲ 10 tasks failed:
  main: upstream status (fatal: missing object 0000000000000000000000000000000000000001 for refs/heads/feature)
  feature: ahead/behind counts (git merge-base failed for 05a4a45d0b981dad5c27db59dca482836d59f89e 0000000000000000000000000000000000000001: fatal: Not a valid commit name 0000000000000000000000000000000000000001)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ exit_code: 0
----- stderr -----
▲ Failed to batch-fetch commit details: fatal: bad object 0000000000000000000000000000000000000001

○ Showing 5 worktrees, 3 ahead; 6 tasks failed
○ Showing 5 worktrees, 3 ahead
▲ 6 tasks failed:
  (detached): ahead/behind counts (git merge-base failed for 05a4a45d0b981dad5c27db59dca482836d59f89e 0000000000000000000000000000000000000001: fatal: Not a valid commit name 0000000000000000000000000000000000000001)
  (detached): trees-match check (fatal: Needed a single revision)
Expand Down
Loading