From 6630202259cf984a8b20d7cb76086986a107390c Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Tue, 20 Apr 2021 19:12:54 -0400 Subject: Output paths relative to current working directory Fixes #552 --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/config.rs | 2 ++ src/delta.rs | 30 ++++++++++++++--- src/parse.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 126 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96844e15..0db780e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,7 @@ dependencies = [ "git2", "itertools", "lazy_static", + "pathdiff", "regex", "shell-words", "structopt", @@ -496,6 +497,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "pathdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34" + [[package]] name = "percent-encoding" version = "2.1.0" diff --git a/Cargo.toml b/Cargo.toml index b4e003e0..55ec3b0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ console = "0.14.1" dirs-next = "2.0.0" itertools = "0.10.0" lazy_static = "1.4" +pathdiff = "0.2.0" regex = "1.4.6" shell-words = "1.0.0" structopt = "0.3.21" diff --git a/src/config.rs b/src/config.rs index f822efc2..c6719839 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,7 @@ pub struct Config { pub background_color_extends_to_terminal_width: bool, pub commit_style: Style, pub color_only: bool, + pub cwd_relative_to_repo_root: Option, pub decorations_width: cli::Width, pub error_exit_code: i32, pub file_added_label: String, @@ -182,6 +183,7 @@ impl From for Config { .background_color_extends_to_terminal_width, commit_style, color_only: opt.color_only, + cwd_relative_to_repo_root: std::env::var("GIT_PREFIX").ok(), decorations_width: opt.computed.decorations_width, error_exit_code: 2, // Use 2 for error because diff uses 0 and 1 for non-error. file_added_label, diff --git a/src/delta.rs b/src/delta.rs index 7d52eae1..869bd4a4 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -113,6 +113,8 @@ impl<'a> StateMachine<'a> { let mut handled_line = if line.starts_with("commit ") { self.handle_commit_meta_header_line()? + } else if self.state == State::CommitMeta && line.starts_with(" ") { + self.handle_diff_stat_line()? } else if line.starts_with("diff ") { self.handle_file_meta_diff_line()? } else if (self.state == State::FileMeta || self.source == Source::DiffUnified) @@ -226,6 +228,20 @@ impl<'a> StateMachine<'a> { Ok(()) } + fn handle_diff_stat_line(&mut self) -> std::io::Result { + let mut handled_line = false; + if let Some(cwd) = self.config.cwd_relative_to_repo_root.as_deref() { + if let Some(replacement_line) = + parse::relativize_path_in_diff_stat_line(&self.raw_line, cwd) + { + self.painter.emit()?; + writeln!(self.painter.writer, "{}", replacement_line)?; + handled_line = true + } + } + Ok(handled_line) + } + #[allow(clippy::unnecessary_wraps)] fn handle_file_meta_diff_line(&mut self) -> std::io::Result { self.painter.paint_buffered_minus_and_plus_lines(); @@ -237,8 +253,11 @@ impl<'a> StateMachine<'a> { fn handle_file_meta_minus_line(&mut self) -> std::io::Result { let mut handled_line = false; - let parsed_file_meta_line = - parse::parse_file_meta_line(&self.line, self.source == Source::GitDiff); + let parsed_file_meta_line = parse::parse_file_meta_line( + &self.line, + self.source == Source::GitDiff, + self.config.cwd_relative_to_repo_root.as_deref(), + ); self.minus_file = parsed_file_meta_line.0; self.file_event = parsed_file_meta_line.1; @@ -271,8 +290,11 @@ impl<'a> StateMachine<'a> { fn handle_file_meta_plus_line(&mut self) -> std::io::Result { let mut handled_line = false; - let parsed_file_meta_line = - parse::parse_file_meta_line(&self.line, self.source == Source::GitDiff); + let parsed_file_meta_line = parse::parse_file_meta_line( + &self.line, + self.source == Source::GitDiff, + self.config.cwd_relative_to_repo_root.as_deref(), + ); self.plus_file = parsed_file_meta_line.0; self.painter .set_syntax(parse::get_file_extension_from_file_meta_line_file_path( diff --git a/src/parse.rs b/src/parse.rs index 4c2cf233..65ad72f1 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -28,8 +28,12 @@ pub enum FileEvent { NoEvent, } -pub fn parse_file_meta_line(line: &str, git_diff_name: bool) -> (String, FileEvent) { - match line { +pub fn parse_file_meta_line( + line: &str, + git_diff_name: bool, + cwd_relative_to_repo_root: Option<&str>, +) -> (String, FileEvent) { + let (mut path, file_event) = match line { line if line.starts_with("--- ") || line.starts_with("+++ ") => { let offset = 4; let file = match &line[offset..] { @@ -56,7 +60,44 @@ pub fn parse_file_meta_line(line: &str, git_diff_name: bool) -> (String, FileEve (line[8..].to_string(), FileEvent::Copy) // "copy to ".len() } _ => ("".to_string(), FileEvent::NoEvent), + }; + + if let Some(cwd) = cwd_relative_to_repo_root { + if let Some(relative_path) = pathdiff::diff_paths(&path, cwd) { + if let Some(relative_path) = relative_path.to_str() { + path = relative_path.to_owned(); + } + } + } + + (path, file_event) +} + +// A regex to capture the path, and the content from the pipe onwards, in lines +// like these: +// " src/delta.rs | 14 ++++++++++----" +// " src/config.rs | 2 ++" +lazy_static! { + static ref DIFF_STAT_LINE_REGEX: Regex = + Regex::new(r" ([^\| ][^\|]+[^\| ]) +(\| +[0-9]+ .+)").unwrap(); +} + +pub fn relativize_path_in_diff_stat_line( + line: &str, + cwd_relative_to_repo_root: &str, +) -> Option { + if let Some(caps) = DIFF_STAT_LINE_REGEX.captures(line) { + let path_relative_to_repo_root = caps.get(1).unwrap().as_str(); + if let Some(relative_path) = + pathdiff::diff_paths(path_relative_to_repo_root, cwd_relative_to_repo_root) + { + if let Some(relative_path) = relative_path.to_str() { + let suffix = caps.get(2).unwrap().as_str(); + return Some(format!(" {:<30}{}", relative_path, suffix,)); + } + } } + return None; } pub fn get_file_extension_from_file_meta_line_file_path(path: &str) -> Option<&str> { @@ -245,21 +286,21 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line() { assert_eq!( - parse_file_meta_line("--- /dev/null", true), + parse_file_meta_line("--- /dev/null", true, None), ("/dev/null".to_string(), FileEvent::Change) ); for prefix in &DIFF_PREFIXES { assert_eq!( - parse_file_meta_line(&format!("--- {}src/delta.rs", prefix), true), + parse_file_meta_line(&format!("--- {}src/delta.rs", prefix), true, None), ("src/delta.rs".to_string(), FileEvent::Change) ); } assert_eq!( - parse_file_meta_line("--- src/delta.rs", true), + parse_file_meta_line("--- src/delta.rs", true, None), ("src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ src/delta.rs", true), + parse_file_meta_line("+++ src/delta.rs", true, None), ("src/delta.rs".to_string(), FileEvent::Change) ); } @@ -267,23 +308,23 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line_containing_spaces() { assert_eq!( - parse_file_meta_line("+++ a/my src/delta.rs", true), + parse_file_meta_line("+++ a/my src/delta.rs", true, None), ("my src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ my src/delta.rs", true), + parse_file_meta_line("+++ my src/delta.rs", true, None), ("my src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ a/src/my delta.rs", true), + parse_file_meta_line("+++ a/src/my delta.rs", true, None), ("src/my delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ a/my src/my delta.rs", true), + parse_file_meta_line("+++ a/my src/my delta.rs", true, None), ("my src/my delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ b/my src/my enough/my delta.rs", true), + parse_file_meta_line("+++ b/my src/my enough/my delta.rs", true, None), ( "my src/my enough/my delta.rs".to_string(), FileEvent::Change @@ -294,7 +335,7 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line_rename() { assert_eq!( - parse_file_meta_line("rename from nospace/file2.el", true), + parse_file_meta_line("rename from nospace/file2.el", true, None), ("nospace/file2.el".to_string(), FileEvent::Rename) ); } @@ -302,7 +343,7 @@ mod tests { #[test] fn test_get_file_path_from_git_file_meta_line_rename_containing_spaces() { assert_eq!( - parse_file_meta_line("rename from with space/file1.el", true), + parse_file_meta_line("rename from with space/file1.el", true, None), ("with space/file1.el".to_string(), FileEvent::Rename) ); } @@ -310,11 +351,11 @@ mod tests { #[test] fn test_parse_file_meta_line() { assert_eq!( - parse_file_meta_line("--- src/delta.rs", false), + parse_file_meta_line("--- src/delta.rs", false, None), ("src/delta.rs".to_string(), FileEvent::Change) ); assert_eq!( - parse_file_meta_line("+++ src/delta.rs", false), + parse_file_meta_line("+++ src/delta.rs", false, None), ("src/delta.rs".to_string(), FileEvent::Change) ); } @@ -369,4 +410,38 @@ mod tests { assert_eq!(line_numbers_and_hunk_lengths[1], (358, 15),); assert_eq!(line_numbers_and_hunk_lengths[2], (358, 16),); } + + #[test] + fn test_relative_path() { + for (path, cwd_relative_to_repo_root, expected) in &[ + ("file.rs", "", "file.rs"), + ("file.rs", "a/", "../file.rs"), + ("a/file.rs", "a/", "file.rs"), + ("a/b/file.rs", "a", "b/file.rs"), + ("c/d/file.rs", "a/b/", "../../c/d/file.rs"), + ] { + assert_eq!( + pathdiff::diff_paths(path, cwd_relative_to_repo_root), + Some(expected.into()) + ) + } + } + + #[test] + fn test_diff_stat_line_regex_1() { + let caps = DIFF_STAT_LINE_REGEX.captures(" src/delta.rs | 14 ++++++++++----"); + assert!(caps.is_some()); + let caps = caps.unwrap(); + assert_eq!(caps.get(1).unwrap().as_str(), "src/delta.rs"); + assert_eq!(caps.get(2).unwrap().as_str(), "| 14 ++++++++++----"); + } + + #[test] + fn test_diff_stat_line_regex_2() { + let caps = DIFF_STAT_LINE_REGEX.captures(" src/config.rs | 2 ++"); + assert!(caps.is_some()); + let caps = caps.unwrap(); + assert_eq!(caps.get(1).unwrap().as_str(), "src/config.rs"); + assert_eq!(caps.get(2).unwrap().as_str(), "| 2 ++"); + } } -- cgit v1.2.3