Find Out Why Git Treats a File That Way with git check-attr

Use git check-attr to explain why Git treats a path as text, binary, mergeable or otherwise special, instead of guessing from .gitattributes. The guide uses Git 2.43.0 from the installed git-man package, version 2.43.0-1ubuntu7.3. Allow about fifteen minutes. You need a Git working tree and permission to read its files. These checks are read-only; no elevated privileges are required.

1. Start in the right repository

Change to the working tree that contains the path you want to inspect. Use a real path in place of PATH/TO/FILE:

$ cd /path/to/repository
$ git rev-parse --show-toplevel
/path/to/repository

If git rev-parse fails, stop there. Running the check outside a work tree can give an error or inspect a different repository than the one you meant.

Tip: the command does not need sudo, and adding it can change which configuration and repository context Git sees.

Checkpoint: confirm the installed version when the result needs to be reproducible.

$ git --version
git version 2.43.0

2. Query one attribute for one path

The simplest form takes an attribute name followed by one or more pathnames. This asks about the text attribute:

$ git check-attr text -- PATH/TO/FILE
PATH/TO/FILE: text: unspecified

The three common states mean different things:

A value is printed literally, such as text: auto or diff: java. Unspecified is not the same as unset: an unspecified text attribute, for example, can leave Git to consult core.autocrlf.

Do not read the final word as a generic success flag. The output is a record in the form path: attribute: info, and the useful answer is the attribute state in the third field.

3. Inspect the attributes that actually apply

Use --all when you want every attribute associated with the path:

$ git check-attr --all -- PATH/TO/FILE
PATH/TO/FILE: text: auto
PATH/TO/FILE: diff: java

This output omits unspecified attributes. That is useful for finding active rules, but it cannot prove that an omitted attribute is unset. Query a named attribute separately when the distinction matters.

To compare several known attributes, list their names before the separator:

$ git check-attr text eol diff merge -- PATH/TO/FILE
PATH/TO/FILE: text: auto
PATH/TO/FILE: eol: unspecified
PATH/TO/FILE: diff: java
PATH/TO/FILE: merge: unspecified

The -- marker makes the boundary explicit. It stops a pathname beginning with a dash being mistaken for an option, and it makes the command easier to review before you run it.

4. Connect the result to .gitattributes

Attributes come from pattern rules. A line such as this in .gitattributes marks common text files and assigns a diff driver to Java files:

*       text=auto
*.java  diff=java
*.jpg   -text -diff

Attribute syntax has four useful forms:

Later matching rules override earlier ones per attribute.

Git checks $GIT_DIR/info/attributes first, then .gitattributes files from the path's directory towards the work tree root, followed by global and system-wide attribute files. The repository-local file suits a personal rule that must not be committed. Put shared project policy in a version-controlled .gitattributes file instead.

Patterns resemble .gitignore patterns, with two traps: negative patterns are not allowed, and a pattern ending in a slash does not recursively match a directory. Use directory/** when the rule should cover its contents.

5. Check staged rules instead of working-tree rules

If you have edited .gitattributes but not staged it, the default check can reflect the working-tree file. Use --cached to ask what the index contains:

$ git check-attr --cached text diff -- PATH/TO/FILE
PATH/TO/FILE: text: auto
PATH/TO/FILE: diff: unspecified

This is a read-only comparison. If the cached result differs from the normal result, inspect the attribute file and the index before committing.

Warning: do not 'fix' a surprising answer by staging or committing files until you understand which rule you want distributed.

6. Inspect a committed tree

Use --source with a commit, branch or tag when the question is about a tree rather than the current checkout:

$ git check-attr --source HEAD~1 text diff -- PATH/TO/FILE
PATH/TO/FILE: text: unspecified
PATH/TO/FILE: diff: unspecified

The pathname must exist in the selected tree for a meaningful comparison. This answers 'what did the previous commit say?' without checking out another revision. It does not alter HEAD, the index or the working tree.

7. Use stdin for batches and scripts

For many paths, pass them on standard input. Without -z, one pathname is read per line:

$ printf '%s\n' 'src/main.java' 'README.md' | git check-attr --stdin text
src/main.java: text: auto
README.md: text: unspecified

Use -z when pathnames may contain newlines, or when another program expects NUL-delimited records. It changes both stdin separators, when --stdin is used, and output separators:

$ printf '%s\0' 'src/main.java' 'README.md' | git check-attr --stdin -z text | od -An -t x1
 73 72 63 2f 6d 61 69 6e 2e 6a 61 76 61 00 74 65
 78 74 00 61 75 74 6f 00

Parse the records as path, attribute, value, each terminated by NUL. Do not split this mode on colons: a pathname can contain characters that make human-formatted output ambiguous.

8. Recover from the usual wrong answer

If the result says unspecified, work down this list:

Recovery: to undo an accidental attribute-file edit, restore it with your normal version-control workflow, and only after checking the diff. For a tracked file whose working-tree edit is unwanted, git restore -- .gitattributes discards that edit. Review git diff -- .gitattributes first.

Warning: the restore is irreversible for uncommitted content, so do not run it if you need the changes.

Done means