summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilliam Escande <wescande@google.com>2022-02-06 23:29:41 +0100
committerDan Davison <dandavison7@gmail.com>2022-03-30 08:41:48 -0400
commit9c840f6f4cc25530368e21c6cc54a716655e920d (patch)
treebd27f4f54b4e0a789a88c68da8a3d3aed579adc0
parent6e242c76994b7d2d2ce4f5ce79ff843fc4394ed5 (diff)
Add support for irreversible-delete
Fix #128 Add support for `git diff -D` (aka irreversible delete) The patch is adding a title for removed file, even when there is no file content in the diff output Without the patch, there was no output related to the file at all
-rw-r--r--src/delta.rs3
-rw-r--r--src/handlers/diff_header.rs123
-rw-r--r--src/handlers/diff_header_diff.rs2
-rw-r--r--src/tests/test_example_diffs.rs33
4 files changed, 127 insertions, 34 deletions
diff --git a/src/delta.rs b/src/delta.rs
index 7ba29973..27b30512 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -157,6 +157,7 @@ impl<'a> StateMachine<'a> {
let _ = self.handle_commit_meta_header_line()?
|| self.handle_diff_stat_line()?
|| self.handle_diff_header_diff_line()?
+ || self.handle_diff_header_file_operation_line()?
|| self.handle_diff_header_minus_line()?
|| self.handle_diff_header_plus_line()?
|| self.handle_hunk_header_line()?
@@ -173,7 +174,7 @@ impl<'a> StateMachine<'a> {
|| self.emit_line_unchanged()?;
}
- self.handle_pending_mode_line_with_diff_name()?;
+ self.handle_pending_line_with_diff_name()?;
self.painter.paint_buffered_minus_and_plus_lines();
self.painter.emit()?;
Ok(())
diff --git a/src/handlers/diff_header.rs b/src/handlers/diff_header.rs
index 3e317bee..39262c1b 100644
--- a/src/handlers/diff_header.rs
+++ b/src/handlers/diff_header.rs
@@ -14,9 +14,11 @@ const DIFF_PREFIXES: [&str; 6] = ["a/", "b/", "c/", "i/", "o/", "w/"];
#[derive(Debug, PartialEq)]
pub enum FileEvent {
+ Added,
Change,
Copy,
Rename,
+ Removed,
NoEvent,
}
@@ -49,6 +51,25 @@ impl<'a> StateMachine<'a> {
Ok(handled_line)
}
+ fn should_write_generic_diff_header_header_line(&mut self) -> std::io::Result<bool> {
+ // In color_only mode, raw_line's structure shouldn't be changed.
+ // So it needs to avoid fn _handle_diff_header_header_line
+ // (it connects the plus_file and minus_file),
+ // and to call fn handle_generic_diff_header_header_line directly.
+ if self.config.color_only {
+ write_generic_diff_header_header_line(
+ &self.line,
+ &self.raw_line,
+ &mut self.painter,
+ &mut self.mode_info,
+ self.config,
+ )?;
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
#[inline]
fn test_diff_header_minus_line(&self) -> bool {
(matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
@@ -62,7 +83,6 @@ impl<'a> StateMachine<'a> {
if !self.test_diff_header_minus_line() {
return Ok(false);
}
- let mut handled_line = false;
let (path_or_mode, file_event) =
parse_diff_header_line(&self.line, self.source == Source::GitDiff);
@@ -84,21 +104,7 @@ impl<'a> StateMachine<'a> {
}
self.painter.paint_buffered_minus_and_plus_lines();
- // In color_only mode, raw_line's structure shouldn't be changed.
- // So it needs to avoid fn _handle_diff_header_header_line
- // (it connects the plus_file and minus_file),
- // and to call fn handle_generic_diff_header_header_line directly.
- if self.config.color_only {
- write_generic_diff_header_header_line(
- &self.line,
- &self.raw_line,
- &mut self.painter,
- &mut self.mode_info,
- self.config,
- )?;
- handled_line = true;
- }
- Ok(handled_line)
+ self.should_write_generic_diff_header_header_line()
}
#[inline]
@@ -129,25 +135,58 @@ impl<'a> StateMachine<'a> {
self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
self.painter.paint_buffered_minus_and_plus_lines();
- // In color_only mode, raw_line's structure shouldn't be changed.
- // So it needs to avoid fn _handle_diff_header_header_line
- // (it connects the plus_file and minus_file),
- // and to call fn handle_generic_diff_header_header_line directly.
- if self.config.color_only {
- write_generic_diff_header_header_line(
- &self.line,
- &self.raw_line,
- &mut self.painter,
- &mut self.mode_info,
- self.config,
- )?;
- handled_line = true
+ if self.should_write_generic_diff_header_header_line()? {
+ handled_line = true;
} else if self.should_handle()
&& self.handled_diff_header_header_line_file_pair != self.current_file_pair
{
self.painter.emit()?;
self._handle_diff_header_header_line(self.source == Source::DiffUnified)?;
- self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone()
+ self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone();
+ }
+ Ok(handled_line)
+ }
+
+ #[inline]
+ fn test_diff_header_file_operation_line(&self) -> bool {
+ (matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified)
+ && (self.line.starts_with("deleted file mode ")
+ || self.line.starts_with("new file mode "))
+ }
+
+ /// Check for and handle the "deleted file ..." line.
+ pub fn handle_diff_header_file_operation_line(&mut self) -> std::io::Result<bool> {
+ if !self.test_diff_header_file_operation_line() {
+ return Ok(false);
+ }
+ let mut handled_line = false;
+ let (_mode_info, file_event) =
+ parse_diff_header_line(&self.line, self.source == Source::GitDiff);
+ let name = get_repeated_file_path_from_diff_line(&self.diff_line)
+ .unwrap_or_else(|| "".to_string());
+ match file_event {
+ FileEvent::Removed => {
+ self.minus_file = name;
+ self.plus_file = "/dev/null".into();
+ 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()));
+ }
+ FileEvent::Added => {
+ self.minus_file = "/dev/null".into();
+ self.plus_file = name;
+ 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_write_generic_diff_header_header_line()?
+ || (self.should_handle()
+ && self.handled_diff_header_header_line_file_pair != self.current_file_pair)
+ {
+ handled_line = true;
}
Ok(handled_line)
}
@@ -172,7 +211,16 @@ impl<'a> StateMachine<'a> {
)
}
- pub fn handle_pending_mode_line_with_diff_name(&mut self) -> std::io::Result<()> {
+ #[inline]
+ fn test_pending_line_with_diff_name(&self) -> bool {
+ matches!(self.state, State::DiffHeader(_)) || self.source == Source::DiffUnified
+ }
+
+ pub fn handle_pending_line_with_diff_name(&mut self) -> std::io::Result<()> {
+ if !self.test_pending_line_with_diff_name() {
+ return Ok(());
+ }
+
if !self.mode_info.is_empty() {
let format_label = |label: &str| {
if !label.is_empty() {
@@ -204,6 +252,13 @@ impl<'a> StateMachine<'a> {
&mut self.mode_info,
self.config,
)
+ } else if !self.config.color_only
+ && self.should_handle()
+ && self.handled_diff_header_header_line_file_pair != self.current_file_pair
+ {
+ self._handle_diff_header_header_line(self.source == Source::DiffUnified)?;
+ self.handled_diff_header_header_line_file_pair = self.current_file_pair.clone();
+ Ok(())
} else {
Ok(())
}
@@ -292,6 +347,12 @@ fn parse_diff_header_line(line: &str, git_diff_name: bool) -> (String, FileEvent
line if line.starts_with("copy to ") => {
(line[8..].to_string(), FileEvent::Copy) // "copy to ".len()
}
+ line if line.starts_with("new file mode ") => {
+ (line[14..].to_string(), FileEvent::Added) // "new file mode ".len()
+ }
+ line if line.starts_with("deleted file mode ") => {
+ (line[18..].to_string(), FileEvent::Removed) // "deleted file mode ".len()
+ }
_ => ("".to_string(), FileEvent::NoEvent),
}
}
diff --git a/src/handlers/diff_header_diff.rs b/src/handlers/diff_header_diff.rs
index fdd2d39a..a79f6e1c 100644
--- a/src/handlers/diff_header_diff.rs
+++ b/src/handlers/diff_header_diff.rs
@@ -22,7 +22,7 @@ impl<'a> StateMachine<'a> {
} else {
State::DiffHeader(DiffType::Unified)
};
- self.handle_pending_mode_line_with_diff_name()?;
+ self.handle_pending_line_with_diff_name()?;
self.handled_diff_header_header_line_file_pair = None;
self.diff_line = self.line.clone();
if !self.should_skip_line() {
diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs
index 2eca792f..325f2e9a 100644
--- a/src/tests/test_example_diffs.rs
+++ b/src/tests/test_example_diffs.rs
@@ -17,7 +17,6 @@ mod tests {
}
#[test]
- #[ignore] // #128
fn test_added_empty_file() {
DeltaTest::with_args(&[])
.with_input(ADDED_EMPTY_FILE)
@@ -1571,6 +1570,21 @@ src/align.rs:71: impl<'a> Alignment<'a> { │
}
#[test]
+ fn test_file_deleted_without_preimage() {
+ DeltaTest::with_args(&[])
+ .with_input(GIT_DIFF_FILE_DELETED_WITHOUT_PREIMAGE)
+ .expect_contains("removed: foo.bar");
+ }
+
+ #[test]
+ fn test_files_deleted_without_preimage() {
+ DeltaTest::with_args(&[])
+ .with_input(GIT_DIFF_FILES_DELETED_WITHOUT_PREIMAGE)
+ .expect_contains("removed: foo")
+ .expect_contains("removed: bar");
+ }
+
+ #[test]
fn test_file_mode_change_with_diff() {
DeltaTest::with_args(&["--navigate", "--keep-plus-minus-markers"])
.with_input(GIT_DIFF_FILE_MODE_CHANGE_WITH_DIFF)
@@ -2334,6 +2348,23 @@ old mode 100700
new mode 100644
";
+ // This output can be generated with `git diff -D`
+ const GIT_DIFF_FILE_DELETED_WITHOUT_PREIMAGE: &str = "
+diff --git a/foo.bar b/foo.bar
+deleted file mode 100644
+index e019be0..0000000
+";
+
+ // This output can be generated with `git diff -D`
+ const GIT_DIFF_FILES_DELETED_WITHOUT_PREIMAGE: &str = "
+diff --git a/foo b/foo
+deleted file mode 100644
+index e019be0..0000000
+diff --git a/bar b/bar
+deleted file mode 100644
+index e019be0..0000000
+";
+
const GIT_DIFF_FILE_MODE_CHANGE_WITH_DIFF: &str = "
diff --git a/src/script b/src/script
old mode 100644