.gitattributes & Line Endings
A .gitignore file tells Git which files to leave alone. A .gitattributes file does the opposite job: it tells Git how to treat the files it does track. Is this file text or binary? Which line ending should it have? Should Git show a diff for it? Should it go into a release archive?
The most common reason teams add this file is line endings. If your team mixes Windows laptops with Linux servers and Mac users, a missing .gitattributes can make every line of a file look changed, or break a shell script on the server. In this lesson you will fix that properly for the whole team, and learn a few other attributes you will meet at work.
Why line endings cause trouble
A text file marks the end of each line with invisible characters. Windows uses two, carriage return and line feed (CRLF, written \r\n). Linux and macOS use only the line feed (LF, \n).
The Install & Configure Git lesson explained that Git for Windows sets core.autocrlf=true, which converts line endings for you. That works only on computers where someone set it correctly. A teammate with a different setting, an editor that rewrites endings, or a CI server can still commit CRLF files. Then:
- A diff shows every line as changed, although only one word was edited, and real changes hide in the noise.
- A shell script with CRLF fails on Linux, because the shell sees
\ras part of each command. - Merges conflict on lines nobody really touched.
The core.autocrlf setting is personal: it lives in each person's config, not in the repository. A .gitattributes file is committed, so the rule travels with the project and applies on every computer.
Seeing the problem with git ls-files --eol
Priya's sweet-shop repository was started on a Windows laptop with conversion switched off. git ls-files --eol shows the line ending of each file in the index (i/, what is stored in Git) and in the working tree (w/, the file on disk), plus any attributes (attr/):
git ls-files --eoli/lf w/lf attr/ deploy.sh i/-text w/-text attr/ logo.png i/crlf w/crlf attr/ menu.html i/crlf w/crlf attr/ start.bat
menu.html is stored with CRLF inside Git. A Linux teammate who edits it will soon produce a diff full of changed endings. -text means Git thinks the file is binary, which is correct for the logo.
Writing a .gitattributes file
Each line is a pattern (the same style as .gitignore) followed by one or more attributes. Create .gitattributes in the project root:
# Let Git decide which files are text, and store them with LF
* text=auto
# Files that must keep one line ending on every computer
*.sh text eol=lf
*.bat text eol=crlf
# Never touch these
*.png binaryWhat each attribute means:
| Attribute | Effect |
|---|---|
text=auto | Git detects text files and stores them with LF in the repository |
text | Always treat as text and normalise to LF in the repository |
eol=lf / eol=crlf | Use this ending in the working tree on every computer |
binary | Shorthand for -text -diff -merge: no conversion, no text diff, no line merge |
-text | Never convert line endings for this file |
With text=auto, the files on disk use your platform's ending: CRLF on Windows (unless your config says otherwise) and LF on Linux and macOS. Shell scripts must have LF to run on Linux, and Windows batch files expect CRLF, so those two get a fixed eol.
When a pattern has an attribute in .gitattributes, it wins over core.autocrlf. The config setting is used only for files where the text attribute is not specified.
Renormalising files already in the repository
Adding the file does not rewrite what is already stored. The documentation is careful here: with text=auto, a file that was already committed with CRLF is not converted. You have to tell Git to clean the index once, with git add --renormalize:
git add .gitattributes
git add --renormalize .
git status --shortwarning: in the working copy of '.gitattributes', LF will be replaced by CRLF the next time Git touches it A .gitattributes M menu.html M start.bat
The warning appears on Windows because text=auto files use CRLF in a Windows working tree. It is information, not an error. menu.html and start.bat are now staged with LF inside Git. Commit and check again:
git commit -m "Normalise line endings with .gitattributes"
git ls-files --eol[main 279fdc9] Normalise line endings with .gitattributes 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 .gitattributes i/lf w/lf attr/text=auto .gitattributes i/lf w/lf attr/text eol=lf deploy.sh i/-text w/-text attr/-text logo.png i/lf w/crlf attr/text=auto menu.html i/lf w/crlf attr/text eol=crlf start.bat
Every text file is LF in the index, which is what all platforms share. On Priya's Windows laptop the working copies of menu.html and start.bat keep CRLF, and deploy.sh stays LF so it runs on the server.
Make the renormalising commit on its own, with nothing else in it, and tell your team. It touches many lines, so mixing it with real changes makes review impossible.
Checking which attributes apply
When you are not sure why Git treats a file in some way, ask it. git check-attr -a lists every attribute set for a path:
git check-attr -a deploy.sh logo.pngdeploy.sh: text: set deploy.sh: eol: lf logo.png: binary: set logo.png: diff: unset logo.png: merge: unset logo.png: text: unset
You can see that binary expanded into its three parts. This is the attributes version of git check-ignore -v from Ignoring Files with .gitignore.
Other useful attributes
Line endings are the main use, but a few more attributes are worth knowing.
Hiding noisy diffs. Minified files and lock files are one enormous line. -diff tells Git to stop printing them:
*.min.js -diffNow git diff prints only Binary files a/app.min.js and b/app.min.js differ for that file instead of a wall of code. You can also give file types a diff driver, for example *.php diff=php, so diff headers show the PHP function a change belongs to.
Leaving files out of release archives. git archive builds a zip or tar file from a commit. Files marked export-ignore are skipped, which keeps tests and tooling out of downloads:
tests/ export-ignore
.gitattributes export-ignoregit archive --format=tar HEAD | tar -tapp.min.js deploy.sh logo.png menu.html start.bat
The tests/ folder and .gitattributes are not in the archive. (On Windows, Git also prints the LF/CRLF warnings from earlier while running these commands; they are left out here.)
GitHub display hints. GitHub reads some extra attributes of its own. linguist-generated marks generated files such as dist/bundle.js: GitHub collapses their diffs in pull requests and leaves them out of the repository's language statistics. Git itself ignores these attributes.
Common mistakes
- Relying only on
core.autocrlf. It is a personal setting. Put the rule in.gitattributesso it applies to everyone, including CI. - Adding
.gitattributesbut skipping--renormalize. Files already committed with CRLF stay that way until you renormalise. - Forgetting
binaryon images, PDFs or zip files. A wrongly detected file can be corrupted by line ending conversion. - Shell scripts without
eol=lf. A CRLFdeploy.shfails on Linux with confusing errors such as$'\r': command not found. - Mixing the renormalising commit with real work. Keep it separate so reviewers and
git blamestay useful.
Key points
.gitattributesis committed with the project and tells Git how to treat tracked files on every computer.* text=autostores text files with LF in the repository; the working tree uses the platform's ending.- Use
eol=lffor shell scripts andeol=crlffor Windows batch files; mark images and archivesbinary. - After adding the file, run
git add --renormalize .once and commit it on its own. git ls-files --eolandgit check-attr -ashow what Git is doing with each file.-diff,export-ignoreand GitHub'slinguist-generatedcontrol diffs, archives and pull request display.
Exercise
Create a practice repository with three files: a notes.txt with CRLF endings, a build.sh script and a small image. Commit them without any attributes and check git ls-files --eol. Then add a .gitattributes that normalises text files, keeps build.sh on LF everywhere and marks *.jpg and *.png as binary. Renormalise, commit, and check again.
Show solution
* text=auto
*.sh text eol=lf
*.jpg binary
*.png binarygit ls-files --eol
git add .gitattributes
git add --renormalize .
git commit -m "Add .gitattributes and normalise line endings"
git ls-files --eolBefore, notes.txt shows i/crlf. After renormalising it shows i/lf, build.sh shows attr/text eol=lf, and the image shows -text, so Git never converts it.
Quick Quiz
1. Why is .gitattributes more reliable than asking everyone to set core.autocrlf?
core.autocrlf is a personal setting; .gitattributes travels with the repository.
2. You added * text=auto, but git ls-files --eol still shows i/crlf for an old file. What should you run?
Files already committed with CRLF are not converted until you renormalise the index.
3. Which line keeps a Linux deploy script working even when it is edited on Windows?
eol=lf forces LF in the working tree on every platform, and text stores it with LF.
4. What does the binary attribute mean?
binary is shorthand for -text -diff -merge.
Learn Git with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
