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.