Skip to content

Commit cd10de9

Browse files
committed
Add support for gitattributes
Runs git check-attr to get the files git attributes, and treats the file as binary if the attribute value is "unset", matching the built-in git diff. Fixes #466 Helped-by: @anuramat (#466 (comment))
1 parent b5a4df1 commit cd10de9

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

src/git.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use std::path::Path;
2+
use std::process::Command;
3+
4+
/// Corresponds to the diff attribute. See man gitattribute.
5+
pub(crate) enum DiffAttribute {
6+
Set,
7+
Unset,
8+
Unspecified,
9+
Other,
10+
}
11+
12+
impl From<&str> for DiffAttribute {
13+
fn from(s: &str) -> Self {
14+
match s {
15+
"set" => Self::Set,
16+
"unset" => Self::Unset,
17+
"unspecified" => Self::Unspecified,
18+
_ => Self::Other,
19+
}
20+
}
21+
}
22+
23+
/// Runs `git check-attr diff` to get the diff attribute of the path. Returns
24+
/// [`Option::None`] when either `git` is not available, file is not inside git
25+
/// directory, or something else went wrong.
26+
pub(crate) fn check_attr(path: &Path) -> Option<DiffAttribute> {
27+
let res = Command::new("git")
28+
.args(["check-attr", "diff", "-z", "--"])
29+
.arg(path)
30+
.output();
31+
32+
match res {
33+
Ok(output) => {
34+
// Either git is not available, or file is outside git directory.
35+
if !output.status.success() {
36+
debug!("git check-attr exited with status {}", output.status);
37+
return None;
38+
}
39+
40+
// The output format is "path" "attribute name" "value". We
41+
// specified both path and attribute name explicitly,
42+
// so we only need value here.
43+
let stdout = &output.stdout;
44+
let value = stdout.split(|&b| b == b'\0').nth(2);
45+
match value {
46+
None => {
47+
warn!("malformed git check-attr output {stdout:#?}");
48+
}
49+
Some(value) => match std::str::from_utf8(value) {
50+
Ok(s) => return Some(s.into()),
51+
Err(err) => {
52+
warn!("invalid diff attribute value: {err}");
53+
}
54+
},
55+
}
56+
}
57+
Err(err) => {
58+
warn!("failed to execute git: {err}");
59+
}
60+
}
61+
62+
None
63+
}

src/main.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ mod diff;
3838
mod display;
3939
mod exit_codes;
4040
mod files;
41+
mod git;
4142
mod hash;
4243
mod line_parser;
4344
mod lines;
@@ -68,6 +69,7 @@ use crate::files::{
6869
guess_content, read_file_or_die, read_files_or_die, read_or_die, relative_paths_in_either,
6970
ProbableFileKind,
7071
};
72+
use crate::git::{check_attr, DiffAttribute};
7173
use crate::parse::guess_language::language_globs;
7274
use crate::parse::guess_language::{guess, language_name, Language, LanguageOverride};
7375
use crate::parse::syntax;
@@ -405,8 +407,11 @@ fn diff_file(
405407
let (mut lhs_src, mut rhs_src) = match (
406408
guess_content(&lhs_bytes, lhs_path, binary_overrides),
407409
guess_content(&rhs_bytes, rhs_path, binary_overrides),
410+
check_attr(Path::new(display_path)),
408411
) {
409-
(ProbableFileKind::Binary, _) | (_, ProbableFileKind::Binary) => {
412+
(ProbableFileKind::Binary, _, _)
413+
| (_, ProbableFileKind::Binary, _)
414+
| (_, _, Some(DiffAttribute::Unset)) => {
410415
let has_byte_changes = if lhs_bytes == rhs_bytes {
411416
None
412417
} else {
@@ -425,7 +430,7 @@ fn diff_file(
425430
has_syntactic_changes: false,
426431
};
427432
}
428-
(ProbableFileKind::Text(lhs_src), ProbableFileKind::Text(rhs_src)) => (lhs_src, rhs_src),
433+
(ProbableFileKind::Text(lhs_src), ProbableFileKind::Text(rhs_src), _) => (lhs_src, rhs_src),
429434
};
430435

431436
if diff_options.strip_cr {

0 commit comments

Comments
 (0)