EthicsComputerCourses
Join training +91 90532 10052

.gitattributes & Line Endings

Git · Lesson 11 of 42 Beginner 9 min read Updated

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 \r as 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/):

Terminal
git ls-files --eol
Output
i/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:

Text
# 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 binary

What each attribute means:

AttributeEffect
text=autoGit detects text files and stores them with LF in the repository
textAlways treat as text and normalise to LF in the repository
eol=lf / eol=crlfUse this ending in the working tree on every computer
binaryShorthand for -text -diff -merge: no conversion, no text diff, no line merge
-textNever 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:

Terminal
git add .gitattributes
git add --renormalize .
git status --short
Output
warning: 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:

Terminal
git commit -m "Normalise line endings with .gitattributes"
git ls-files --eol
Output
[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:

Terminal
git check-attr -a deploy.sh logo.png
Output
deploy.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:

Text
*.min.js -diff

Now 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:

Text
tests/ export-ignore
.gitattributes export-ignore
Terminal
git archive --format=tar HEAD | tar -t
Output
app.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 .gitattributes so it applies to everyone, including CI.
  • Adding .gitattributes but skipping --renormalize. Files already committed with CRLF stay that way until you renormalise.
  • Forgetting binary on images, PDFs or zip files. A wrongly detected file can be corrupted by line ending conversion.
  • Shell scripts without eol=lf. A CRLF deploy.sh fails 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 blame stay useful.

Key points

  • .gitattributes is committed with the project and tells Git how to treat tracked files on every computer.
  • * text=auto stores text files with LF in the repository; the working tree uses the platform's ending.
  • Use eol=lf for shell scripts and eol=crlf for Windows batch files; mark images and archives binary.
  • After adding the file, run git add --renormalize . once and commit it on its own.
  • git ls-files --eol and git check-attr -a show what Git is doing with each file.
  • -diff, export-ignore and GitHub's linguist-generated control 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
* text=auto
*.sh text eol=lf
*.jpg binary
*.png binary
Terminal
git ls-files --eol
git add .gitattributes
git add --renormalize .
git commit -m "Add .gitattributes and normalise line endings"
git ls-files --eol

Before, 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?

2. You added * text=auto, but git ls-files --eol still shows i/crlf for an old file. What should you run?

3. Which line keeps a Linux deploy script working even when it is edited on Windows?

4. What does the binary attribute mean?

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