Skip to content
Open
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
63 changes: 53 additions & 10 deletions src/cmds/git/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,24 +559,27 @@ pub(crate) fn filter_log_output(
if user_format {
let lines: Vec<&str> = output.lines().collect();
let max_lines = if user_set_limit { lines.len() } else { limit };
return lines
let mut result = lines
.iter()
.take(max_lines)
.map(|l| truncate_line(l, truncate_width))
.collect::<Vec<_>>()
.join("\n");
.collect::<Vec<_>>();
if !user_set_limit && lines.len() >= max_lines && !result.is_empty() {
result.push(log_limit_marker(max_lines));
}
return result.join("\n");
}

// RTK injected format: split output into commit blocks separated by ---END---
let commits: Vec<&str> = output.split("---END---").collect();
let commits: Vec<&str> = output
.split("---END---")
.map(str::trim)
.filter(|block| !block.is_empty())
.collect();
let max_commits = if user_set_limit { commits.len() } else { limit };

let mut result = Vec::new();
for block in commits.iter().take(max_commits) {
let block = block.trim();
if block.is_empty() {
continue;
}
let mut lines = block.lines();
// First line is the header: hash subject (date) <author>
let header = match lines.next() {
Expand Down Expand Up @@ -609,9 +612,20 @@ pub(crate) fn filter_log_output(
}
}

if !user_set_limit && commits.len() >= max_commits && !result.is_empty() {
result.push(log_limit_marker(max_commits));
}

result.join("\n").trim().to_string()
}

fn log_limit_marker(limit: usize) -> String {
format!(
"[limited by RTK: showing up to {} commits; use `rtk git log -n <N>` or `rtk proxy git log` for full output]",
limit
)
}

/// Truncate a single line to `width` characters, appending "..." if needed
fn truncate_line(line: &str, width: usize) -> String {
if line.chars().count() > width {
Expand Down Expand Up @@ -2221,7 +2235,29 @@ A added.rs
.collect::<Vec<_>>()
.join("\n");
let result = filter_log_output(&output, 5, false, false);
assert_eq!(result.lines().count(), 5);
assert_eq!(result.lines().filter(|l| l.starts_with("hash")).count(), 5);
assert!(
result.contains("[limited by RTK: showing up to 5 commits"),
"default git log cap must be visible, got:\n{result}"
);
assert!(
result.contains("rtk git log -n <N>"),
"limit marker must include a recovery hint, got:\n{result}"
);
}

#[test]
fn test_filter_log_output_user_format_cap_marks_limit() {
let output = (0..20)
.map(|i| format!("hash{} message {}", i, i))
.collect::<Vec<_>>()
.join("\n");
let result = filter_log_output(&output, 5, false, true);
assert_eq!(result.lines().filter(|l| l.starts_with("hash")).count(), 5);
assert!(
result.contains("[limited by RTK: showing up to 5 commits"),
"user-format git log cap must be visible, got:\n{result}"
);
}

#[test]
Expand Down Expand Up @@ -2461,7 +2497,14 @@ no changes added to commit (use "git add" and/or "git commit -a")

// user_set_limit=false means cap at limit
let result = filter_log_output(oneline_output, 3, false, true);
assert_eq!(result.lines().count(), 3);
assert_eq!(
result
.lines()
.filter(|l| l.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
.count(),
3
);
assert!(result.contains("[limited by RTK: showing up to 3 commits"));
}

/// Regression test: `git branch <name>` must create, not list.
Expand Down
18 changes: 15 additions & 3 deletions src/cmds/go/golangci_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Filters golangci-lint output, grouping issues by rule.

use crate::core::config;
use crate::core::runner;
use crate::core::stream::exec_capture;
use crate::core::truncate::CAP_WARNINGS;
use crate::core::utils::{resolved_command, truncate};
use crate::core::utils::resolved_command;
use crate::parser::truncate_passthrough;
use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
Expand Down Expand Up @@ -271,7 +271,7 @@ pub(crate) fn filter_golangci_json(output: &str, version: u32) -> String {
return format!(
"golangci-lint (JSON parse failed: {})\n{}",
e,
truncate(output, config::limits().passthrough_max_chars)
truncate_passthrough(output)
);
}
};
Expand Down Expand Up @@ -431,6 +431,18 @@ mod tests {
assert!(result.contains("utils.go"));
}

#[test]
fn test_filter_golangci_parse_failure_marks_truncation() {
let output = format!(
"not-json\n{}",
"x".repeat(crate::core::config::limits().passthrough_max_chars + 16)
);
let result = filter_golangci_json(&output, 1);

assert!(result.contains("JSON parse failed"));
assert!(result.contains("[RTK:PASSTHROUGH] Output truncated"));
}

#[test]
fn test_compact_path() {
assert_eq!(
Expand Down
30 changes: 27 additions & 3 deletions src/cmds/js/lint_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Filters ESLint and Biome linter output, grouping violations by rule.

use crate::core::config;
use crate::core::stream::exec_capture;
use crate::core::tracking;
use crate::core::truncate::{CAP_ERRORS, CAP_WARNINGS};
use crate::core::utils::{package_manager_exec, resolved_command, truncate};
use crate::mypy_cmd;
use crate::parser::truncate_passthrough;
use crate::ruff_cmd;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -230,7 +230,7 @@ fn filter_eslint_json(output: &str) -> String {
return format!(
"ESLint output (JSON parse failed: {})\n{}",
e,
truncate(output, config::limits().passthrough_max_chars)
truncate_passthrough(output)
);
}
};
Expand Down Expand Up @@ -329,7 +329,7 @@ fn filter_pylint_json(output: &str) -> String {
return format!(
"Pylint output (JSON parse failed: {})\n{}",
e,
truncate(output, config::limits().passthrough_max_chars)
truncate_passthrough(output)
);
}
};
Expand Down Expand Up @@ -555,6 +555,18 @@ mod tests {
assert!(result.contains("src/utils.ts"));
}

#[test]
fn test_filter_eslint_json_parse_failure_marks_truncation() {
let output = format!(
"not-json\n{}",
"x".repeat(crate::core::config::limits().passthrough_max_chars + 16)
);
let result = filter_eslint_json(&output);

assert!(result.contains("JSON parse failed"));
assert!(result.contains("[RTK:PASSTHROUGH] Output truncated"));
}

#[test]
fn test_compact_path() {
assert_eq!(
Expand Down Expand Up @@ -624,6 +636,18 @@ mod tests {
assert!(result.contains("utils.py"));
}

#[test]
fn test_filter_pylint_json_parse_failure_marks_truncation() {
let output = format!(
"not-json\n{}",
"x".repeat(crate::core::config::limits().passthrough_max_chars + 16)
);
let result = filter_pylint_json(&output);

assert!(result.contains("JSON parse failed"));
assert!(result.contains("[RTK:PASSTHROUGH] Output truncated"));
}

#[test]
fn test_strip_pm_prefix_npx() {
let args: Vec<String> = vec!["npx".into(), "eslint".into(), "src/".into()];
Expand Down
16 changes: 14 additions & 2 deletions src/cmds/python/ruff_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Filters Ruff linter and formatter output.

use crate::core::config;
use crate::core::runner;
use crate::core::truncate::CAP_WARNINGS;
use crate::core::utils::{resolved_command, truncate};
use crate::parser::truncate_passthrough;
use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
Expand Down Expand Up @@ -101,7 +101,7 @@ pub fn filter_ruff_check_json(output: &str) -> String {
return format!(
"Ruff check (JSON parse failed: {})\n{}",
e,
truncate(output, config::limits().passthrough_max_chars)
truncate_passthrough(output)
);
}
};
Expand Down Expand Up @@ -387,6 +387,18 @@ mod tests {
assert!(result.contains("1:8"), "line:col location missing");
}

#[test]
fn test_filter_ruff_check_parse_failure_marks_truncation() {
let output = format!(
"not-json\n{}",
"x".repeat(crate::core::config::limits().passthrough_max_chars + 16)
);
let result = filter_ruff_check_json(&output);

assert!(result.contains("JSON parse failed"));
assert!(result.contains("[RTK:PASSTHROUGH] Output truncated"));
}

#[test]
fn test_filter_ruff_format_all_formatted() {
let output = "5 files left unchanged";
Expand Down
Loading