The line-ending problem
Windows uses CRLF (\r\n), Linux and macOS use LF (\n). When developers on different platforms commit to the same repository, files flip back and forth and diffs become unreadable. .gitattributes is the cure.
The simple, robust setup
# .gitattributes
* text=auto eol=lf
*.{cmd,bat,ps1} text eol=crlf
*.png binary
*.jpg binary
*.pdf binary
*.zip binary
* text=auto eol=lf tells Git: "auto-detect text files, store them with LF endings, and convert to LF on checkout." Windows-only scripts force CRLF; binaries are marked so Git does not touch them.
How text=auto works
On commit, Git looks at file contents. Apparent text files have their working-tree line endings normalised to LF in the repository. Apparent binaries are stored verbatim. On checkout, the configured core.autocrlf may convert back to CRLF for Windows users, but with eol=lf in .gitattributes, you override the user setting and get LF everywhere.
Renormalising existing repos
If you adopt .gitattributes on a repo with mixed endings, you must renormalise:
# Backup first!
git add --renormalize .
git commit -m "Normalize line endings"
This rewrites the index according to the new attributes. Diff afterwards to verify only line endings changed.
core.autocrlf versus .gitattributes
Two ways to manage line endings:
core.autocrlf- per-user config; differs between developers..gitattributes- committed to the repo; the same for everyone.
.gitattributes wins. Per-user config invites inconsistency.
Marking binaries
Even if text=auto guesses correctly most of the time, mark binaries explicitly to prevent surprises:
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.pdf binary
*.zip binary
*.tar binary
*.gz binary
*.dll binary
*.exe binary
binary is shorthand for -text -diff: do not normalise, do not diff.
Per-file overrides
Makefile text eol=lf
*.sh text eol=lf
*.bat text eol=crlf
*.ps1 text eol=crlf
*.csv text # treat CSVs as text but allow either ending
Diff drivers and merge drivers
Beyond line endings, .gitattributes can configure diff and merge for specific file types:
*.docx diff=docx
*.pdf diff=pdf
composer.lock merge=ours
This lets you teach Git to compare Word documents via Pandoc or skip merging on lockfiles.
Verifying
git check-attr -a path/to/file
git ls-files --eol
--eol prints the working tree and index line-ending state per file. A clean repo shows consistent values.
The recommendation
Add a .gitattributes file with * text=auto eol=lf and binary markers to every repo on day one. Renormalise once. After that, line-ending churn becomes a non-issue.