Files
sausage/userland/diffutils/src/diff.rs
T
surma b9e3e3677d userland: search and exec tools, spawn library (T14)
rg (ripgrep 15.2), grep (ours, on ripgrep's regex crates), find and xargs
(ours), sed (uutils 0.3 wrapper), diff and cmp (uutils diffutils 0.6 with -r),
fd 10.5, jaq 2.3, env and timeout. userland/spawn is a small Command-like
client of the process interface. search-test.nu: 42 cases match native tools.
2026-11-06 17:21:51 +00:00

152 lines
5.3 KiB
Rust

// This file is part of the uutils diffutils package.
//
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use crate::params::{parse_params, Format};
use crate::utils::report_failure_to_read_input_file;
use crate::{context_diff, ed_diff, normal_diff, side_diff, unified_diff};
use std::env::ArgsOs;
use std::ffi::OsString;
use std::fs;
use std::io::{self, stdout, Read, Write};
use std::iter::Peekable;
use std::process::{exit, ExitCode};
// Exit codes are documented at
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
// An exit status of 0 means no differences were found,
// 1 means some differences were found,
// and 2 means trouble.
pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
let params = parse_params(opts).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
});
if params.recursive {
return recursive(&params).unwrap_or_else(|error| {
eprintln!("diff: {error}");
ExitCode::from(2)
});
}
compare(&params)
}
/// Compare directory entries in lexical order, with GNU diff's recursive output.
fn recursive(params: &crate::params::Params) -> io::Result<ExitCode> {
let from = std::path::Path::new(&params.from);
let to = std::path::Path::new(&params.to);
if !from.is_dir() || !to.is_dir() {
if fs::read(from)? != fs::read(to)? {
println!("diff -r {} {}", from.display(), to.display());
}
return Ok(compare(params));
}
let mut names = std::collections::BTreeSet::new();
for dir in [from, to] {
for entry in fs::read_dir(dir)? { names.insert(entry?.file_name()); }
}
let mut status = ExitCode::SUCCESS;
for name in names {
let left = from.join(&name);
let right = to.join(&name);
let result = if !left.exists() || !right.exists() {
let parent = if left.exists() { from } else { to };
println!("Only in {}: {}", parent.display(), name.to_string_lossy());
ExitCode::from(1)
} else if left.is_dir() != right.is_dir() {
println!("File {} is a {} while file {} is a {}", left.display(),
if left.is_dir() { "directory" } else { "regular file" }, right.display(),
if right.is_dir() { "directory" } else { "regular file" });
ExitCode::from(1)
} else {
let mut child = params.clone();
child.from = left.into_os_string();
child.to = right.into_os_string();
recursive(&child)?
};
if result == ExitCode::from(2) { status = result; }
else if result == ExitCode::from(1) && status == ExitCode::SUCCESS { status = result; }
}
Ok(status)
}
fn compare(params: &crate::params::Params) -> ExitCode {
// if from and to are the same file, no need to perform any comparison
let maybe_report_identical_files = || {
if params.report_identical_files {
println!(
"Files {} and {} are identical",
params.from.to_string_lossy(),
params.to.to_string_lossy(),
);
}
};
if params.from == "-" && params.to == "-"
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
{
maybe_report_identical_files();
return ExitCode::SUCCESS;
}
// read files
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
if filepath == "-" {
let mut content = Vec::new();
io::stdin().read_to_end(&mut content).and(Ok(content))
} else {
fs::read(filepath)
}
}
let mut io_error = false;
let from_content = match read_file_contents(&params.from) {
Ok(from_content) => from_content,
Err(e) => {
report_failure_to_read_input_file(&params.executable, &params.from, &e);
io_error = true;
vec![]
}
};
let to_content = match read_file_contents(&params.to) {
Ok(to_content) => to_content,
Err(e) => {
report_failure_to_read_input_file(&params.executable, &params.to, &e);
io_error = true;
vec![]
}
};
if io_error {
return ExitCode::from(2);
}
// run diff
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
Format::Context => context_diff::diff(&from_content, &to_content, &params),
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
}),
Format::SideBySide => {
let mut output = stdout().lock();
side_diff::diff(&from_content, &to_content, &mut output, &params)
}
};
if params.brief && !result.is_empty() {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
} else {
io::stdout().write_all(&result).unwrap();
}
if result.is_empty() {
maybe_report_identical_files();
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}