diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 000000000..07a61888b --- /dev/null +++ b/src/git.rs @@ -0,0 +1,63 @@ +use std::path::Path; +use std::process::Command; + +/// Corresponds to the diff attribute. See man gitattribute. +pub(crate) enum DiffAttribute { + Set, + Unset, + Unspecified, + Other, +} + +impl From<&str> for DiffAttribute { + fn from(s: &str) -> Self { + match s { + "set" => Self::Set, + "unset" => Self::Unset, + "unspecified" => Self::Unspecified, + _ => Self::Other, + } + } +} + +/// Runs `git check-attr diff` to get the diff attribute of the path. Returns +/// [`Option::None`] when either `git` is not available, file is not inside git +/// directory, or something else went wrong. +pub(crate) fn check_attr(path: &Path) -> Option { + let res = Command::new("git") + .args(["check-attr", "diff", "-z", "--"]) + .arg(path) + .output(); + + match res { + Ok(output) => { + // Either git is not available, or file is outside git directory. + if !output.status.success() { + debug!("git check-attr exited with status {}", output.status); + return None; + } + + // The output format is "path" "attribute name" "value". We + // specified both path and attribute name explicitly, + // so we only need value here. + let stdout = &output.stdout; + let value = stdout.split(|&b| b == b'\0').nth(2); + match value { + None => { + warn!("malformed git check-attr output {stdout:#?}"); + } + Some(value) => match std::str::from_utf8(value) { + Ok(s) => return Some(s.into()), + Err(err) => { + warn!("invalid diff attribute value: {err}"); + } + }, + } + } + Err(err) => { + warn!("failed to execute git: {err}"); + } + } + + None +} diff --git a/src/main.rs b/src/main.rs index 27bbadb8e..e0fccc492 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ mod diff; mod display; mod exit_codes; mod files; +mod git; mod hash; mod line_parser; mod lines; @@ -78,6 +79,7 @@ use crate::files::{ guess_content, read_file_or_die, read_files_or_die, read_or_die, relative_paths_in_either, ProbableFileKind, }; +use crate::git::{check_attr, DiffAttribute}; use crate::parse::guess_language::language_globs; use crate::parse::guess_language::{guess, language_name, Language, LanguageOverride}; use crate::parse::syntax; @@ -413,8 +415,11 @@ fn diff_file( let (mut lhs_src, mut rhs_src) = match ( guess_content(&lhs_bytes, lhs_path, binary_overrides), guess_content(&rhs_bytes, rhs_path, binary_overrides), + check_attr(Path::new(display_path)), ) { - (ProbableFileKind::Binary, _) | (_, ProbableFileKind::Binary) => { + (ProbableFileKind::Binary, _, _) + | (_, ProbableFileKind::Binary, _) + | (_, _, Some(DiffAttribute::Unset)) => { let has_byte_changes = if lhs_bytes == rhs_bytes { None } else { @@ -433,7 +438,7 @@ fn diff_file( has_syntactic_changes: false, }; } - (ProbableFileKind::Text(lhs_src), ProbableFileKind::Text(rhs_src)) => (lhs_src, rhs_src), + (ProbableFileKind::Text(lhs_src), ProbableFileKind::Text(rhs_src), _) => (lhs_src, rhs_src), }; if diff_options.strip_cr {