Topic 7.1
Line Endings, .gitattributes & Large Files (Git LFS)
In one line
Windows and Unix-based systems use different invisible line-ending characters, .gitattributes lets you control exactly how Git handles that (and other file-type quirks) per-file-type, and Git LFS solves the genuine problem of large binary files bloating a repository's history forever.
Think of it like this
Two people writing on the same page, one always pressing Enter with a slightly different, invisible keystroke than the other — visually identical on screen, but the underlying character sequence differs, and version control sees that difference as a change even when nothing meaningful actually changed.
Key ideas
- 01
Windows traditionally uses CRLF (carriage return + line feed) to end each line; Unix-based systems (Linux, macOS) use just LF (line feed) — a genuinely common cross-platform annoyance where a file that looks completely unchanged shows as ENTIRELY modified in
git diff, purely because line endings were silently converted by an editor or Git itself. - 02
git config --global core.autocrlf true(on Windows) orinput(on Mac/Linux) tells Git to normalize line endings automatically — converting to LF when committing and, on Windows specifically, back to CRLF when checking files out, so the repository itself stores a consistent format regardless of which OS anyone commits from. - 03
A
.gitattributesfile, committed to the repository itself (unlike personalcore.autocrlfconfig), makes line-ending handling CONSISTENT for every contributor regardless of their own local Git configuration —* text=autois the standard baseline line, letting Git auto-detect text files and normalize them, with more specific rules addable per file type (e.g. forcing.shfiles to always use LF, since a CRLF shell script can genuinely fail to run on Linux). - 04
.gitattributesalso controls other genuinely useful per-file-type behavior beyond line endings — marking specific files asbinary(never attempt a text diff or line-ending conversion on them, correct for anything like compiled assets or images), or configuring a custom diff driver for a file type Git can't meaningfully diff as plain text otherwise. - 05
GIT LFS (Large File Storage) solves a different, genuine problem: committing large binary files (video, high-res images, datasets) directly bloats the repository's history PERMANENTLY, since Git normally keeps every version of every file forever — LFS instead stores a small pointer file in the actual Git history, with the real large file content kept in separate LFS storage, fetched on demand only when needed.
Code & diagrams
A realistic baseline for a cross-platform project.
# Auto-detect text files and normalize their line endings
* text=auto
# Force these specific types to always use LF, regardless of OS,
# since a CRLF shell script can fail to run on Linux
*.sh text eol=lf
# Never attempt to diff or line-ending-convert genuine binary files
*.png binary
*.jpg binary
*.pdf binarySetting up LFS for a specific large file type — requires the git-lfs tool installed once.
# One-time setup per machine
git lfs install
# Tell LFS to manage a specific file pattern
git lfs track "*.psd"
git lfs track "datasets/*.csv"
# This creates/updates .gitattributes automatically — commit it
git add .gitattributes
git commit -m "Track large files with Git LFS"
# From now on, files matching those patterns are stored via LFS automatically
git add design-mockup.psd
git commit -m "Add design mockup"
# The actual .psd content lives in LFS storage;
# Git's own history just stores a small pointer file
# Confirm what's currently tracked by LFS
git lfs ls-filesExplain it without notes
Why can a file that looks completely unchanged show up as fully modified in git diff, when working across Windows and Unix-based systems?
What specific problem does Git LFS solve that a plain .gitignore entry for large files does not?
Practice
Create a .gitattributes with * text=auto plus a forced-LF rule for one file type, and commit it to a test repository.
If you have git-lfs available, track a file type with it, commit a matching file, and confirm with git lfs ls-files that it's being managed by LFS rather than stored directly in normal Git history.
Trade-offs
- ↔
Git LFS solves the large-file bloat problem genuinely well, but it adds a real dependency (the
git-lfstool must be installed by everyone who clones the repo) and a genuine ongoing cost (LFS storage is often billed separately by hosting platforms, based on storage and bandwidth) — for a project with only occasional, small binary assets, plain Git without LFS is simpler and sufficient; LFS earns its complexity specifically once large files are numerous or change frequently.
Done when you can
I understand why CRLF vs LF line endings can make an unchanged file appear fully modified.
I can write a basic .gitattributes file to normalize line endings consistently for a whole team.
I know what problem Git LFS solves and when a project genuinely needs it.