CRLF Logo

When working in software development, one of the most subtle yet crucial aspects you need to be aware of is the difference between line endings in text files between Windows (CRLF \r\n) and Linux/MacOS (LF \n).

This small detail can cause big problems if not handled correctly, especially when working in mixed environments – version control conflicts, script incompatibilities, compilation or execution issues. I wrote this post to have a handy reference for dealing with this topic, including a few tricks.


Introduction

When working on a cross-platform project (Windows and Mac/Linux) it’s advisable to standardize, define a coherent policy for handling CRLF versus LF. Here are some solutions:

  • Have Git manage it, let it take care of checking line endings before commits.
    • This can be done with the core.autocrlf option at a global level
      • git config --global core.autocrlf true - Recommended on Windows.
      • git config --global core.autocrlf input - Recommended on Linux/MacOS.
    • But it can also be done in a more granular way.
      • Using the .gitattributes file at the repository root.
  • Another option is to do it manually, manage it yourself:
    • With an editor that supports choosing (for example VSCode).
    • Doing manual conversions with dos2unix and unix2dos
    • Common sense and maintaining control, controlling edits, relying on tools and scripts to manage and respect the line ending format.

These are some of the questions I asked myself:

  • Should I enforce the use of LF (Unix style) in the repository? The decision is yes, it’s worth it.
  • Of the various solutions, which one do I choose? I decide to use .gitattributes.
  • If my repo is old and has multiple existing files with inconsistent line endings, how do I handle it? The decision is to use a tool to convert most to LF (with exceptions where I leave CRLF).
  • Should the version control system automatically normalize line endings during commits and checkouts? The decision is yes, through the .gitattributes file.
  • How do I handle line endings in third-party libraries or git submodules? The decision is to leave them as-is, don’t touch them.
  • Will I need tools or scripts to verify and enforce the line ending policy (for example, a pre-commit hook)? Not necessary.

Renormalization

An example of how I would (in two phases) normalize a repository. During the process, a couple of quite useful tools.

  • Command git ls-files --eol which gives us invaluable information
  • Program git-repo-eol-analyzer to verify normalization before and after.

Phase 1, I add the .gitattributes file to my clone

curl -LJs -o .gitattributes https://gist.githubusercontent.com/LuisPalacios/9821bd9c9ff8183cf772fbe11cec55fc/raw/.gitattributes
git commit -a -m 'adding .gitattributes to unify line endings'
git push
# Ask the team to pull and pause work until phase 2 is complete.

Phase 2, normalization, find and convert all files that I’m interested in to LF (Unix). The find command below is an example, adapt it to your needs.

# On MacOS
find -E . -iregex '.*\.(c|cpp|h|hpp|md|svg|csx|yml|yaml|gitattributes|gitignore|json)' -exec dos2unix {} \;

# On Linux or WSL2
find . -regextype posix-extended -regex '.*\.(c|cpp|h|hpp|md|svg|csx|yml|yaml|gitattributes|gitignore|json)$' -exec dos2unix {} \;

git commit -a -m 'Files normalized with dos2unix'
git push

Now all that’s left is to ask the team to pull again. Although it’s much better to ask them to clone the repository from scratch. That way they’ll see a completely synchronized copy.