Line endings are one of those small but painful details in cross-platform development. Windows tools (like Visual Studio) prefer CRLF (\r\n) line endings, while Linux and macOS use LF (\n).

Git adds its own layer of normalization to keep repositories consistent across different platforms. If you don’t set things up correctly, you can run into endless diffs, merge conflicts, or the dreaded Visual Studio β€œLine endings inconsistent, do you want to normalize?” dialog.

This article explains a clean setup: CRLF in your working copy on Windows, LF stored in Git, and how Linux/macOS users can stay on LF without conflict.


The Problem

  • Windows editors default to CRLF endings.
  • Linux/macOS build tools and Docker images expect LF endings.
  • Git doesn’t enforce a line ending style unless you tell it to.
  • Mixing the two leads to noisy diffs and warnings when files contain both CRLF and LF.

The Solution: Normalize with Git

Git has a built-in setting to handle line ending conversion automatically:

git config --global core.autocrlf true

With this enabled:

  • On checkout: Git converts LF β†’ CRLF so your local files look natural in Windows editors.
  • On commit: Git converts CRLF β†’ LF so the repository always stores files with LF endings.

This way, Windows developers get the endings they expect, and the repo stays consistent for cross-platform builds.


Adding a .gitattributes File

To lock the behavior down, commit a .gitattributes file at the root of your repo. For example:

# Normalize all text files
* text=auto

# Explicitly treat JSON and XML as text with proper diffs
*.json text diff
*.xml  text diff

# Protect binary files
*.png  binary
*.jpg  binary
*.jpeg binary
*.gif  binary
*.ico  binary

This ensures:

  • All text files are normalized to LF in the repo.
  • JSON/XML are always diffed as text.
  • Images are never corrupted by line ending conversion.

Editor Configuration for Windows Developers

Developers on Windows often want Visual Studio to always save files with CRLF. You can enforce this with .editorconfig:

[*]
end_of_line = crlf
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true

Now Visual Studio won’t nag about mismatched line endings and will always use CRLF locally.


Linux and macOS Developers: Staying on LF

Linux/macOS developers usually want LF locally, not CRLF. But since the repo’s .editorconfig may specify end_of_line = crlf for Windows users, how do they avoid being forced into CRLF?

Option 1: Global EditorConfig override

Each user can create a personal ~/.editorconfig file in their home directory:

# ~/.editorconfig
[*]
end_of_line = lf

This overrides the repo’s setting, so files are always saved with LF locally. Editors like VS Code, Rider, and Visual Studio for Mac respect this.

Option 2: Per-repo local override (not committed)

If a developer only wants LF overrides for a single repo, agree on a local file pattern like .editorconfig.local and add it to .gitignore:

# Ignore per-user overrides
.editorconfig.local

Developers can then create .editorconfig.local in the repo root with:

[*]
end_of_line = lf

Git will ignore this file, but editors will respect it.


Putting It All Together

  1. Repo: Always stores LF endings (thanks to core.autocrlf and .gitattributes).
  2. Windows users: Work with CRLF locally (via .editorconfig + core.autocrlf=true).
  3. Linux/macOS users: Work with LF locally (via ~/.editorconfig or .editorconfig.local).
  4. CI/CD: Gets normalized LF files straight from Git, ensuring consistent builds.

Why This Matters

  • Avoids messy diffs where every line shows up as changed.
  • Keeps your repository platform-neutral.
  • Lets developers on Windows work naturally with CRLF, while Linux/macOS developers stay on LF.
  • Prevents Visual Studio’s CRLF/LF warning dialog from interrupting your flow.

TL;DR

  • Use git config core.autocrlf true on Windows.
  • Commit a .gitattributes to normalize text and protect binaries.
  • Windows devs: .editorconfig β†’ end_of_line = crlf.
  • Linux/Mac devs: personal ~/.editorconfig or .editorconfig.local (ignored by Git) β†’ end_of_line = lf.
  • Repository ends up with clean LF, Windows sees CRLF, Linux/Mac see LF, and everyone is happy.

Happy Coding