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
42 changes: 36 additions & 6 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// 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, MissingFile};
use crate::params::{parse_params, Format, MissingFile, Params};
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;
Expand Down Expand Up @@ -80,6 +80,22 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
return ExitCode::from(2);
}

if !params.text && (is_binary(&from_content) || is_binary(&to_content)) {
if from_content == to_content {
maybe_report_identical_files();
return ExitCode::SUCCESS;
}
print_files_differ(
if params.brief {
"Files"
} else {
"Binary files"
},
&params,
);
return ExitCode::from(1);
}

// run diff
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Expand All @@ -95,11 +111,7 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
}
};
if params.brief && !result.is_empty() {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
print_files_differ("Files", &params);
} else {
io::stdout().write_all(&result).unwrap();
}
Expand All @@ -110,3 +122,21 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
ExitCode::from(1)
}
}

/// Number of leading bytes inspected when deciding whether an input is binary.
const BINARY_PROBE_LEN: usize = 16 * 1024;

/// An input is treated as binary when a NUL byte occurs near its start.
fn is_binary(content: &[u8]) -> bool {
content[..content.len().min(BINARY_PROBE_LEN)].contains(&0)
}

/// Print the one-line summary used instead of the differences, `kind` naming
/// what was compared.
fn print_files_differ(kind: &str, params: &Params) {
println!(
"{kind} {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
}
26 changes: 26 additions & 0 deletions src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub struct Params {
pub tabsize: usize,
pub width: usize,
pub missing_file: MissingFile,
pub text: bool,
}

impl Default for Params {
Expand All @@ -56,6 +57,7 @@ impl Default for Params {
tabsize: 8,
width: 130,
missing_file: MissingFile::default(),
text: false,
}
}
}
Expand Down Expand Up @@ -112,6 +114,10 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
}
continue;
}
if param == "-a" || param == "--text" {
params.text = true;
continue;
}
if param == "-t" || param == "--expand-tabs" {
params.expand_tabs = true;
continue;
Expand Down Expand Up @@ -710,6 +716,26 @@ mod tests {
);
}
#[test]
fn text() {
for option in ["-a", "--text"] {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("old.bin"),
to: os("new.bin"),
text: true,
..Default::default()
}),
parse_params(
[os("diff"), os(option), os("old.bin"), os("new.bin")]
.iter()
.cloned()
.peekable()
)
);
}
}
#[test]
fn expand_tabs() {
assert_eq!(
Ok(Params {
Expand Down
51 changes: 51 additions & 0 deletions tests/by-util/test_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,57 @@ fn missing_file_as_empty() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

#[test]
fn binary_files() -> Result<(), Box<dyn std::error::Error>> {
let tmp_dir = tempdir()?;
let left = tmp_dir.path().join("left.bin");
let right = tmp_dir.path().join("right.bin");
std::fs::write(&left, b"header\x00\x7fpayload-one\n")?;
std::fs::write(&right, b"header\x00\x7fpayload-two\n")?;

let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg(&left).arg(&right);
cmd.assert()
.code(predicate::eq(1))
.failure()
.stderr(predicate::str::is_empty())
.stdout(format!(
"Binary files {} and {} differ\n",
left.display(),
right.display()
));

// Identical binary inputs are not reported.
let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg(&left).arg("-");
cmd.write_stdin(b"header\x00\x7fpayload-one\n".to_vec());
cmd.assert().code(predicate::eq(0)).success().stdout("");

// -q keeps its usual wording.
let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg("-q").arg(&left).arg(&right);
cmd.assert()
.code(predicate::eq(1))
.failure()
.stderr(predicate::str::is_empty())
.stdout(format!(
"Files {} and {} differ\n",
left.display(),
right.display()
));

// -a and --text force a line-by-line comparison.
for option in ["-a", "--text"] {
let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg(option).arg(&left).arg(&right);
cmd.assert()
.code(predicate::eq(1))
.failure()
.stdout(predicate::str::starts_with("1c1\n"));
}
Ok(())
}

#[test]
fn compare_file_to_directory() -> Result<(), Box<dyn std::error::Error>> {
let tmp_dir = tempdir()?;
Expand Down
Loading