You made a two-line change to src/api/routes.ts on a Windows machine, ran git diff, and saw 3,000 lines changed — every single line in the file. Or a teammate’s PR shows a massive red-green diff where the only real change is a single import statement, but all the green lines have a subtle ^M at the end when viewed in raw mode. Git’s core.autocrlf and .gitattributes line-ending rules converted CRLF to LF (or vice versa) when staging the file, producing a diff that touches every line. The repository now has files in mixed line-ending states, and every commit from a Windows machine risks another explosion.
Common causes
Ordered by hit rate, highest first.
1. core.autocrlf = true on Windows, false on macOS/Linux
Windows Git defaults to core.autocrlf = true: it converts LF to CRLF on checkout and CRLF back to LF on commit. When a macOS developer committed LF files and a Windows developer checked them out (converting to CRLF), then ran git add, Git staged the CRLF versions as new content — every line is “changed.”
How to spot it: git config core.autocrlf on the Windows machine returns true. git diff --check reports “whitespace errors” on every line of the affected file.
2. .gitattributes was added or changed after files were already committed
Someone committed * text=auto in .gitattributes to normalize line endings going forward, but did not re-normalize the files already in the repository. Now git status shows hundreds of files as modified (the checkout converted them to CRLF per the text=auto rule, but the index still has LF).
How to spot it: git ls-files --eol shows i/lf w/crlf for many files — index has LF, working tree has CRLF.
3. Editor configured to use CRLF globally on Windows
VS Code on Windows has files.eol: "\r\n" set globally. Every file the editor opens and saves acquires CRLF, even when .gitattributes specifies text eol=lf.
How to spot it: file src/api/routes.ts returns “with CRLF line terminators.” The .gitattributes rule exists but the editor overrode it before the save.
4. Binary files missing from .gitattributes treated as text
A .png or .woff2 font file was not marked as binary in .gitattributes. Git’s text=auto heuristic mis-classified it as text and tried to normalize its line endings, corrupting the binary and producing a full-file diff.
How to spot it: file assets/font.woff2 returns the real binary type. git show HEAD:assets/font.woff2 | file - returns “ASCII text” (wrong).
5. Mixed line endings already in the committed file
The file in the repo already has a mix of LF and CRLF lines (perhaps from multiple contributors). When Git normalizes on staging, the output differs from the input on every mixed line — producing a noisy diff.
How to spot it: cat -A src/api/routes.ts | grep '\^M' | wc -l — a non-zero count means the file already has mixed endings in the committed version.
Shortest path to fix
Step 1: Create or fix .gitattributes at the repo root
cat > .gitattributes << 'EOF'
# Default: normalize all text files to LF in the repo
* text=auto eol=lf
# Explicitly mark binary files
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.pdf binary
*.zip binary
*.gz binary
*.tar binary
# Windows batch files must use CRLF
*.bat text eol=crlf
*.cmd text eol=crlf
# Shell scripts must use LF
*.sh text eol=lf
EOF
git add .gitattributes
git commit -m "chore: add .gitattributes to normalize line endings"
Step 2: Re-normalize all existing files in the repo
# Remove all files from the index without touching the working tree
git rm --cached -r .
# Re-add everything — Git will apply the new .gitattributes rules
git add .
# Commit the normalization
git commit -m "chore: normalize all line endings per .gitattributes"
Step 3: Set core.autocrlf = false on all developer machines
git config --global core.autocrlf false
git config --global core.eol lf
With .gitattributes controlling line endings, autocrlf is redundant and causes the conflicts described above.
Step 4: Fix Windows editor settings
In VS Code’s settings.json (workspace level, committed to the repo):
{
"files.eol": "\n"
}
This ensures VS Code saves all files with LF regardless of OS.
Step 5: Verify the fix with a clean checkout
git stash
git checkout -- .
git diff --check # should return nothing
git stash pop
Prevention
- Commit
.gitattributesin the very first commit of every new repository — before any files with mixed line endings can be added. - Set
core.autocrlf = falsein your organization’s Git config template so every new clone starts with the correct setting:git config --global init.templateDir ~/.git-templates. - Use
.editorconfigalongside.gitattributesto configure editor line endings consistently:end_of_line = lfin.editorconfigis respected by VS Code, JetBrains, Sublime, and most modern editors. - Add
git diff --checkas a pre-commit hook step to catch line-ending regressions before they are committed. - Document the required
core.autocrlf = falsesetting inCONTRIBUTING.mdfor Windows developers. - After adding
.gitattributes, notify all developers to rungit add --renormalize .to re-normalize their working copies without losing unstaged changes. - Use
git ls-files --eolin CI to assert that no committed files have mixed or unexpected line endings.
FAQ
Q: After the normalization commit, git diff still shows thousands of lines changed for some files. Why?
A: Those files have the new LF content in the index but the working tree still has CRLF (from a prior checkout). Run git checkout -- <file> to re-checkout with the correct line endings, or git add --renormalize . to re-stage with normalization applied.
Q: We have a Windows-only build tool that requires CRLF batch scripts. How do we handle that?
A: Add *.bat text eol=crlf and *.cmd text eol=crlf to .gitattributes. These files will always be checked out with CRLF on all platforms, even macOS and Linux.
Q: Is text=auto safe for all projects?
A: For most projects yes. The auto heuristic uses the Git heuristic for binary vs. text detection (same as git diff). For projects with unusual binary formats that resemble text (e.g., SVG files with no newlines), add explicit *.svg text eol=lf rules to avoid misclassification.
Q: Can git diff --ignore-space-change help when reviewing PRs with CRLF noise?
A: For code review, yes: git diff --ignore-all-space strips whitespace differences including CRLF. But committing CRLF noise to the repo is still a problem for blame, git log -S, and bisect — fix the root cause with .gitattributes instead of just ignoring the noise.