Skip to content

Commit

Permalink
Fix headers of modified binary files, closes dandavison#1621
Browse files Browse the repository at this point in the history
  • Loading branch information
imbrish committed Feb 25, 2024
1 parent 084a6d1 commit 7b8a929
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/handlers/diff_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ fn parse_diff_header_line(line: &str, git_diff_name: bool) -> (String, FileEvent

/// Given input like "diff --git a/src/my file.rs b/src/my file.rs"
/// return Some("src/my file.rs")
fn get_repeated_file_path_from_diff_line(line: &str) -> Option<String> {
pub fn get_repeated_file_path_from_diff_line(line: &str) -> Option<String> {
if let Some(line) = line.strip_prefix("diff --git ") {
let line: Vec<&str> = line.graphemes(true).collect();
let midpoint = line.len() / 2;
Expand Down
14 changes: 14 additions & 0 deletions src/handlers/diff_header_diff.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::delta::{DiffType, InMergeConflict, MergeParents, State, StateMachine};
use crate::handlers::diff_header::{get_repeated_file_path_from_diff_line, FileEvent};

impl<'a> StateMachine<'a> {
#[inline]
Expand All @@ -25,6 +26,19 @@ impl<'a> StateMachine<'a> {
self.handle_pending_line_with_diff_name()?;
self.handled_diff_header_header_line_file_pair = None;
self.diff_line = self.line.clone();

// Pre-fill header fields from the diff line. For added, removed or renamed files
// these are updated precisely on actual header minus and header plus lines.
// But for modified binary files which are not added, removed or renamed, there
// are no minus and plus lines. Without the code below, in such cases the file names
// would remain unchanged from the previous diff, or empty for the very first diff.
let name = get_repeated_file_path_from_diff_line(&self.line).unwrap_or_default();
self.minus_file = name.clone();
self.plus_file = name.clone();
self.minus_file_event = FileEvent::Change;
self.plus_file_event = FileEvent::Change;
self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));

if !self.should_skip_line() {
self.emit_line_unchanged()?;
}
Expand Down
33 changes: 17 additions & 16 deletions src/handlers/diff_header_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,26 @@ impl<'a> StateMachine<'a> {
}

pub fn handle_diff_header_misc_line(&mut self) -> std::io::Result<bool> {
let is_binary: bool = self.test_diff_is_binary();
let file_missing: bool = self.test_diff_file_missing();

if !file_missing && !is_binary {
if !self.test_diff_file_missing() && !self.test_diff_is_binary() {
return Ok(false);
}

if is_binary {
match (self.minus_file.as_str(), self.plus_file.as_str()) {
("", "") => {
return self.handle_additional_cases(match self.state {
State::DiffHeader(_) => self.state.clone(),
_ => State::DiffHeader(DiffType::Unified),
});
}
("/dev/null", _) => self.plus_file.push_str(" (binary file)"),
(_, "/dev/null") => self.minus_file.push_str(" (binary file)"),
(_, _) => (),
};
if self.test_diff_is_binary() {
// Print the "Binary files" line verbatim, if there was no "diff" line, or it
// listed different files but was not followed by header minus and plus lines.
// This can happen in output of standalone diff or git diff --no-index.
if self.minus_file.is_empty() && self.plus_file.is_empty() {
self.emit_line_unchanged()?;
self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone();
return Ok(true);
}

if self.minus_file != "/dev/null" {
self.minus_file.push_str(" (binary file)");
}
if self.plus_file != "/dev/null" {
self.plus_file.push_str(" (binary file)");
}
return Ok(true);
}

Expand Down
61 changes: 57 additions & 4 deletions src/tests/test_example_diffs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,26 +237,37 @@ mod tests {

#[test]
fn test_binary_files_differ() {
let config = integration_test_utils::make_config_from_args(&[]);
let config =
integration_test_utils::make_config_from_args(&["--file-modified-label", "modified:"]);
let output = integration_test_utils::run_delta(BINARY_FILES_DIFFER, &config);
let output = strip_ansi_codes(&output);
assert!(output.contains("Binary files a/foo and b/foo differ\n"));
assert!(output.contains("\nmodified: foo (binary file)\n"));
}

#[test]
fn test_binary_file_added() {
let config = integration_test_utils::make_config_from_args(&[]);
let output = integration_test_utils::run_delta(BINARY_FILE_ADDED, &config);
let output = strip_ansi_codes(&output);
assert!(output.contains("added: foo (binary file)\n"));
assert!(output.contains("\nadded: foo (binary file)\n"));
}

#[test]
fn test_binary_file_removed() {
let config = integration_test_utils::make_config_from_args(&[]);
let output = integration_test_utils::run_delta(BINARY_FILE_REMOVED, &config);
let output = strip_ansi_codes(&output);
assert!(output.contains("removed: foo (binary file)\n"));
assert!(output.contains("\nremoved: foo (binary file)\n"));
}

#[test]
fn test_binary_files_differ_after_other() {
let config =
integration_test_utils::make_config_from_args(&["--file-modified-label", "modified:"]);
let output = integration_test_utils::run_delta(BINARY_FILES_DIFFER_AFTER_OTHER, &config);
let output = strip_ansi_codes(&output);
assert!(output.contains("\nrenamed: foo ⟶ bar\n"));
assert!(output.contains("\nmodified: qux (binary file)\n"));
}

#[test]
Expand All @@ -268,6 +279,32 @@ mod tests {
assert!(output.contains("\nSubject: [PATCH] Init\n"));
}

#[test]
fn test_standalone_diff_files_are_identical() {
let diff = "Files foo and bar are identical\n";
let config = integration_test_utils::make_config_from_args(&[]);
let output = integration_test_utils::run_delta(diff, &config);
assert_eq!(strip_ansi_codes(&output), diff);
}

#[test]
fn test_standalone_diff_binary_files_differ() {
let diff = "Binary files foo and bar differ\n";
let config = integration_test_utils::make_config_from_args(&[]);
let output = integration_test_utils::run_delta(diff, &config);
assert_eq!(strip_ansi_codes(&output), diff);
}

#[test]
fn test_diff_no_index_binary_files_differ() {
let config = integration_test_utils::make_config_from_args(&[]);
let output = integration_test_utils::run_delta(DIFF_NO_INDEX_BINARY_FILES_DIFFER, &config);
assert_eq!(
strip_ansi_codes(&output),
"Binary files foo bar and sub dir/foo bar baz differ\n"
);
}

#[test]
fn test_commit_style_raw_no_decoration() {
let config = integration_test_utils::make_config_from_args(&[
Expand Down Expand Up @@ -2307,6 +2344,22 @@ diff --git a/foo b/foo
deleted file mode 100644
index c9bbb35..5fc172d 100644
Binary files a/foo and /dev/null differ
";

const BINARY_FILES_DIFFER_AFTER_OTHER: &str = "
diff --git a/foo b/bar
similarity index 100%
rename from foo
rename to bar
diff --git a/qux b/qux
index 00de669..d47cd84 100644
Binary files a/qux and b/qux differ
";

const DIFF_NO_INDEX_BINARY_FILES_DIFFER: &str = "\
diff --git foo bar sub dir/foo bar baz
index 329fbf5..481817c 100644
Binary files foo bar and sub dir/foo bar baz differ
";

const GIT_DIFF_WITH_COPIED_FILE: &str = "
Expand Down

0 comments on commit 7b8a929

Please sign in to comment.