Back to Blog

What Nobody Tells You About .gitattributes: Surviving the Cross-Platform Line-Ending War

What Nobody Tells You About .gitattributes: Surviving the Cross-Platform Line-Ending War

What Nobody Tells You About .gitattributes: Surviving the Cross-Platform Line-Ending War

Everyone tells you to set git config --global core.autocrlf true and call it a day. That is terrible advice. It’s like trusting every driver on the road to follow a verbal agreement instead of just putting up actual stop signs. If you rely on local developer configurations, you are exactly one "new hire with a default setup" away from a pull request that inexplicably claims you changed 4,000 lines of code when you actually just fixed a typo in a comment.

The problem is that core.autocrlf lives on the machine, not in the repository. You can't enforce it. The only way to stop the "invisible character" madness is to commit a .gitattributes file to the root of your repo. It is the only source of truth that Git actually respects across different operating systems.

The "Invisible" Saboteur

Windows uses Carriage Return + Line Feed (CRLF), while Linux and macOS use just Line Feed (LF). When a Windows dev commits a file that a Mac dev previously touched, Git might see every single line as "changed" because the line endings shifted.

Your diffs become unreadable. Your blame history is ruined. And God help you if you’re running a shell script on a Linux server that was saved with Windows line endings—it’ll crash with errors that make no sense because there’s a hidden \r character at the end of every line.

A Solid Baseline

Stop guessing and start declaring. Create a file named .gitattributes in your project root. Here is the foundation I put in almost every project:

# Handle line endings automatically for files detected as text 
# and leave binary files alone.
* text=auto

# Force bash scripts to always use LF. 
# This saves your life when deploying to Linux from Windows.
*.sh text eol=lf
*.bash text eol=lf

# Ensure Windows-specific files keep their CRLF
*.bat text eol=crlf
*.ps1 text eol=crlf

# Explicitly mark binaries so Git doesn't try to "fix" them
*.png binary
*.jpg binary
*.gif binary
*.pdf binary

The * text=auto line is the heavy lifter. It tells Git to look at the content and decide if it's text. If it is, Git will normalize it to LF in the repo but let the user's OS decide what it looks like on disk.

The Binary Death Trap

Git is usually smart enough to know a JPEG isn't a text file, but "usually" is a dangerous word in production. I’ve seen Git try to "normalize" a proprietary binary format or a small SQLite database, effectively corrupting the file beyond repair.

If you have specific data files or assets, mark them as binary. This tells Git: "Do not touch the bytes inside this file. Don't even think about line endings."

# Treat these as opaque blobs
*.dat binary
*.wasm binary

"I added the file, but my PR is still a mess"

This is the part that trips everyone up. Adding a .gitattributes file does not retroactively fix the files already in your repository. Git only applies these rules when files are staged. If your repo is already a graveyard of mixed line endings, you have to perform a "renormalization."

Here’s the workflow I use to clean the slate. Warning: This will create a massive commit. Do it when nobody else is working on a feature branch.

# 1. Save your work and ensure your working directory is clean
git add .
git commit -m "Save work before renormalization"

# 2. Delete the index (not your files!)
rm .git/index

# 3. Rewrite the index to follow the new .gitattributes rules
git reset

# 4. Git will now see the "real" differences based on your attributes
git add .
git commit -m "Resetting line endings to match .gitattributes"

Why this actually matters for CI/CD

If your CI/CD pipeline runs on Linux (it almost certainly does) but your team codes on Windows, .gitattributes isn't just a "neat trick." It's a stability requirement.

I once spent four hours debugging a Docker build that failed only on the build server. It turned out an entrypoint.sh script had been saved with CRLF by a Windows editor. The Linux container saw #!/bin/bash\r and threw a "Command not found" error because it was looking for a shell named bash\r.

One line in .gitattributes*.sh text eol=lf—would have prevented that entire afternoon of pain.

Don't leave it to chance. Don't leave it to local dev configs. Commit the file, define your rules, and stop fighting the line-ending war.