Skip to content
Home ยป The hell of Git line endings and the (not so) simple fix

The hell of Git line endings and the (not so) simple fix

git logo

I wrote a stream-of-consciousness post a few months ago about what I do in my day job at Microsoft, working in the Database Docs team. Basically we spend most of our day in an obscure distributed version control system (DVCS) by the name of Git. Written over a weekend by the creator of Linux himself, and also self-named, Git became a dominant version control system. Probably because it’s free. Probably because GitHub uses it.

Git has its problems, not least an issue with larger repository sizes causing dramatic performance issues, especially if you haven’t added git.exe to your malware detection exception list.

A line endings primer

Git was born from Linux, so it uses Linux line endings by default. In modern non-Windows environments, this is just a line feed character (LF), represented by the escaped character \n. If you like hexadecimal code (hex) for some reason like I do, this is 0x0A.

On Windows environments, line endings are represented by a combination of two characters: the carriage return (CR, or \r), followed by a line feed character (LF, or \n). So in the vast majority of cases, text files on Windows will have two characters for line feeds: CR LF, or \r\n. In hex, this would be 0x0D0A.

The cross-platform line ending conundrum solution

Git by default uses LF, and Windows by default uses CR LF. On Windows, you have the option to work with your CR LF line endings, and when you commit your files back into the repository, it can automatically check them back in using LF. All you have to do is set core.autocrlf to true. That’s it. But were it that simple, we would have a shorter blog post to read.

Houston, we have a problem

About 20 years ago there was a known bug on Windows XP. Under very particular circumstances, depending on the weather, which leg you stand on, whether you wear blue that day, but mostly which text editor you use, you might introduce corruption in your text file by adding an additional carriage return character. So in some files, on some lines, your line ending would be CR CR LF, or \r\r\n. Hex aficionados actually have a term for this: the 0D 0D 0A problem, also known as the Windows XP word wrap bug (via StackOverflow).

There is a bug in the Windows XP version of Notepad that can cause extra CR characters to be stored in the display window.

If you have the word wrap option turned on and the display window contains long lines that wrap around, then saving the file causes Notepad to insert the characters CR CR LF at each wrap point in the display window, but not in the saved file.

The CR CR LF characters can cause oddities if you copy and paste them into other programs. They also prevent Notepad from properly re-wrapping the lines if you resize the Notepad window.

Depending on your choice of computing platform, you either think this is hilarious, or a complete nightmare. As it turns out, this is not as uncommon as you think. Those of you in the “hilarious” school will be displeased to discover that it’s not limited to Notepad on Windows XP, and has been discovered in several commercial and home-grown text editors over the years. With the proliferation of large language models (LLMs) introducing long-standing bugs from open source applications into your code in 2025, it’s even possible you could use a text editor today that doesn’t know how to handle the extra carriage return.

In my internal docs cleaner tool (which I called DocsCleaner because I have no imagination), I specifically look for the extra \r character on its own. Why? Because people use different operating systems and I’ve been burned by this before, specifically with Git, and specifically in 2008 when I first started using it.

Getting to the point

We’re 560 words in already, so when do we get to the good stuff?

There are two problems we’re dealing with here:

Line endings on Linux, macOS, and containers, are different to line endings on Windows.

Git solves this for you. It’s been there for a very long time, and it’s called core.autocrlf, which stands for “let Git sort out line endings for you”. On Windows, as soon as you install Git, you must (not should) set your core.autocrlf setting to true at a global level. On non-Windows machines, you must set your core.autocrlf setting to input.

  • Windows: git config --global autocrlf true
  • Not Windows: git config --global autocrlf input

That’s it. On Linux, macOS, and in containers, you just use the default (LF). On Windows, when your files are checked out, you use CR LF. You’re done.

Windows has a known bug which has presented over the years as adding an extra carriage return character.

This extra character could be hiding anywhere, and presents as “random files showing up in my Git branch I can’t seem to get rid of”. This one is also easy to fix.

  • If you’re on Windows: Run git add --renormalize . in the root folder of your repository, commit the changes, push them to origin or upstream, and you’re done. The renormalize feature rewrites all the line endings (in all the files in your local branch) by replacing \r\r\n with \r\n.
  • If you’re on a non-Windows machine: Run git add --renormalize . in the root folder of your repository at least twice. Because it replaces \r\n with \n, and you happen to have content that contains this particular \r\r\n line ending corruption, it’ll just convert those to \r\n, requiring a second pass.

Renormalize is an understated option that should be more well known. If you hadn’t heard of it before, you’re welcome.

Are we there yet?

So why is this blog post already 860 words long?

Well, now we get into the fun part. (“Fun” is a monosyllabic word starting with “f-u”.)

Imagine for the sake of argument that you maintain a Git repository with over 16,000 files, with hundreds of forks and clones by hundreds of internal folks across scores of teams and disciplines, with varied experience using Git. Let’s say you’ve figured out this bug, and want to rewrite line endings for the entire repository. In this hypothetical scenario which definitely didn’t happen at Microsoft this week, you get the idea of using the .gitattributes file to tell all your users how to set line endings for all text files.

Git aficionados know this already, but a .gitattributes file at the repo level can override your local settings, your global settings, and your system settings, even if you’ve set core.autocrlf on your machine. The .gitattributes file lets you specify in detail what constitutes a text file, and how to write line endings for each of those extensions. In our scenario, this would be *.md, *.yml, *.sql, *.cs, *.vb, *.xml, *.ps1, and *.svg. And on Thursday last week, we definitely didn’t accidentally set each one to crlf for a few hours before discovering the problem for non-Windows users.

Got to the point

All I’m saying is, Git is complicated. It is not user-friendly. It is not fit for purpose for large repositories with over a million commits. However, it’s what we have, and we’re stuck with it. If you’re dealing with line endings (you are), and you might have users who work in different operating system environments (you do even if you only use Windows, because development containers are a thing now), you need to:

  1. Make it a rule to set your global core.autocrlf setting to true for Windows machines, and input for non-Windows users.
  2. Be very careful of how you configure a repository’s .gitattributes file, because it can override your configuration settings.
  3. Use the --renormalize option from time to time if you find random files showing up in your Git branch, with no apparent changes. You might be suffering from the Windows XP word wrap bug.

This was a long post. It took me 50 minutes to write. That’s far less time than it took to figure this out in the first place, so hopefully it will help you with your cross-platform line ending nightmare on Git.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.