Guide
LF and CRLF in Git: how to avoid whole-file changes
Understand why Git can mark an entire file as changed and define consistent line endings with .gitattributes.
by Tools in a Tab · Published on · Reviewed on
Short answer
LF and CRLF represent a line ending with different bytes. If an editor or Git
converts an entire file from one form to the other, a diff can report every
line as changed even though the visible text is identical. Set the policy in
.gitattributes, renormalize once, and review that change separately from
functional edits.
What actually changes
- LF is the byte
0A. - CRLF is the two-byte sequence
0D 0A.
Two files that look the same can therefore have different SHA-256 hashes. It also explains why a byte-level diff finds changes that a comparator configured to normalize endings can ignore. Tools in a Tab’s text comparator normalizes CRLF and CR to LF so it can focus on the content of each line.
Define a repository policy
A common baseline for cross-platform projects is:
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
The text attribute lets Git normalize text to LF in the index. eol=lf or
eol=crlf requests a specific representation in the working tree. The
official gitattributes documentation
defines these behaviors and should be the reference for special cases.
Do not indiscriminately mark binary files as text. Declare formats that must not be transformed as binary, for example:
*.png binary
*.zip binary
Apply the rule to existing files
After saving .gitattributes, Git may need to reconsider files already under
version control. Renormalize on a clean branch and keep it in a dedicated
commit:
git add --renormalize .
git diff --cached
Review the diff before committing. If it also contains code changes, separate them so reviewers can distinguish a mechanical conversion from real edits.
Keep it from returning
Configure editors to respect .gitattributes, keep the policy in version
control, and do not rely only on each person’s global Git settings. If a hash
does not match, check the line ending and final newline before assuming data
corruption.
The practical split is simple: Git defines the shared representation; the editor displays it. When both follow an explicit rule, diffs return to showing only meaningful changes.