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
This commit is contained in:
William Escande 2022-02-06 23:29:41 +01:00 committed by Dan Davison
parent 6e242c7699
commit 9c840f6f4c
4 changed files with 127 additions and 34 deletions

View File

@ -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(())

View File

@ -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),
}
}

View File

@ -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() {

View File

@ -17,7 +17,6 @@ mod tests {
}
#[test]
#[ignore] // #128
fn test_added_empty_file() {
DeltaTest::with_args(&[])
.with_input(ADDED_EMPTY_FILE)
@ -1570,6 +1569,21 @@ src/align.rs:71: impl<'a> Alignment<'a> { │
.expect_contains("Δ src/delta.rs (mode 100700 -> 100644)");
}
#[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"])
@ -2332,6 +2346,23 @@ new mode 100644
diff --git a/src/delta.rs b/src/delta.rs
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 = "