mirror of
https://github.com/dandavison/delta.git
synced 2024-11-09 12:39:09 +03:00
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:
parent
6e242c7699
commit
9c840f6f4c
@ -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(())
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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 = "
|
||||
|
Loading…
Reference in New Issue
Block a user