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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ rtk read file.rs -l aggressive # Signatures only (strips bodies)
rtk smart file.rs # 2-line heuristic code summary
rtk find "*.rs" . # Compact find results
rtk grep "pattern" . # Grouped search results
rtk diff file1 file2 # Condensed diff
rtk diff file1 file2 # Condensed diff (exit 1 if files differ)
```

### Git
Expand Down
4 changes: 3 additions & 1 deletion scripts/test-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,9 @@ assert_ok "rtk discover" rtk discover

section "Diff"

assert_ok "rtk diff two files" rtk diff Cargo.toml LICENSE
assert_ok "rtk diff identical files" rtk diff Cargo.toml Cargo.toml
assert_fails "rtk diff differing files" rtk diff Cargo.toml LICENSE
assert_contains "rtk diff shows changes" "added" rtk diff Cargo.toml LICENSE

# ── 37. Wc ────────────────────────────────────────────

Expand Down
103 changes: 80 additions & 23 deletions src/cmds/git/diff_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use anyhow::Result;
use std::fs;
use std::path::Path;

/// Ultra-condensed diff - only changed lines, no context
pub fn run(file1: &Path, file2: &Path, verbose: u8) -> Result<()> {
/// Ultra-condensed diff - only changed lines, no context.
/// Returns the diff-convention exit code: 0 if identical, 1 if files differ.
pub fn run(file1: &Path, file2: &Path, verbose: u8) -> Result<i32> {
let timer = tracking::TimedExecution::start();

if verbose > 0 {
Expand All @@ -17,39 +18,37 @@ pub fn run(file1: &Path, file2: &Path, verbose: u8) -> Result<()> {
let content2 = fs::read_to_string(file2)?;
let raw = format!("{}\n---\n{}", content1, content2);

let (rtk, exit_code) = render_file_diff(file1, file2, &content1, &content2);

print!("{}", rtk);
timer.track(
&format!("diff {} {}", file1.display(), file2.display()),
"rtk diff",
&raw,
&rtk,
);
Ok(exit_code)
}

/// Renders the condensed file comparison and returns it with the
/// diff-convention exit code (0 = identical, 1 = differences found).
fn render_file_diff(file1: &Path, file2: &Path, content1: &str, content2: &str) -> (String, i32) {
let lines1: Vec<&str> = content1.lines().collect();
let lines2: Vec<&str> = content2.lines().collect();
let diff = compute_diff(&lines1, &lines2);
let mut rtk = String::new();

if diff.added == 0 && diff.removed == 0 {
rtk.push_str("[ok] Files are identical");
println!("{}", rtk);
timer.track(
&format!("diff {} {}", file1.display(), file2.display()),
"rtk diff",
&raw,
&rtk,
);
return Ok(());
if diff.changes.is_empty() {
return ("[ok] Files are identical\n".to_string(), 0);
}

let mut rtk = String::new();
rtk.push_str(&format!("{} → {}\n", file1.display(), file2.display()));
rtk.push_str(&format!(
" +{} added, -{} removed, ~{} modified\n\n",
diff.added, diff.removed, diff.modified
));

rtk.push_str(&format_diff_changes(&diff));

print!("{}", rtk);
timer.track(
&format!("diff {} {}", file1.display(), file2.display()),
"rtk diff",
&raw,
&rtk,
);
Ok(())
(rtk, 1)
}

/// Run diff from stdin (piped command output)
Expand Down Expand Up @@ -307,6 +306,64 @@ mod tests {
assert!(result.changes.is_empty());
}

// --- render_file_diff (issue #2364 regression) ---

#[test]
fn test_render_modified_only_yaml_not_identical() {
// "a: 1" vs "a: 2" is classified as modified (similarity > 0.5);
// the identical check must not ignore modified-only diffs.
let (out, code) = render_file_diff(
Path::new("one.yaml"),
Path::new("two.yaml"),
"a: 1\n",
"a: 2\n",
);
assert!(
!out.contains("identical"),
"modified-only diff reported as identical:\n{}",
out
);
assert!(out.contains("~1 modified"));
assert!(out.contains("a: 1"));
assert!(out.contains("a: 2"));
assert_eq!(code, 1, "differing files must exit 1 (diff convention)");
}

#[test]
fn test_render_modified_only_json_not_identical() {
let (out, code) = render_file_diff(
Path::new("j1.json"),
Path::new("j2.json"),
"{\"a\": 1}\n",
"{\"a\": 2}\n",
);
assert!(
!out.contains("identical"),
"modified-only diff reported as identical:\n{}",
out
);
assert_eq!(code, 1);
}

#[test]
fn test_render_identical_files_exit_zero() {
let (out, code) = render_file_diff(
Path::new("a.yaml"),
Path::new("b.yaml"),
"a: 1\nb: 2\n",
"a: 1\nb: 2\n",
);
assert!(out.contains("[ok] Files are identical"));
assert_eq!(code, 0);
}

#[test]
fn test_render_added_removed_exit_one() {
let (out, code) = render_file_diff(Path::new("t1.txt"), Path::new("t2.txt"), "x\n", "y\n");
assert!(out.contains("+1 added, -1 removed"));
assert_eq!(code, 1);
}

// --- condense_unified_diff ---

#[test]
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1699,11 +1699,11 @@ fn run_cli() -> Result<i32> {

Commands::Diff { file1, file2 } => {
if let Some(f2) = file2 {
diff_cmd::run(&file1, &f2, cli.verbose)?;
diff_cmd::run(&file1, &f2, cli.verbose)?
} else {
diff_cmd::run_stdin(cli.verbose)?;
0
}
0
}

Commands::Log { file } => {
Expand Down
Loading