Git and line endings

I’m reasonably proficient with git, but I came across the core.safecrlf configuration for the first time only today. This is the opportunity to consolidate my thoughts on the subject of line endings in git.

What happens inside git repo

Due to its origins in unix/linux world, git thinks the “normal” way to end lines is with a line feed (lf, 0x0a). So this is always the way it will store files internally when it decides to normalize them. Now when does this normalization happen ? When git thinks it is a text file, which happens in two cases :

  • The preferred way : when there is an appropriate entry in a .gitattributes file, typically a * text=auto line, where git will use its heuristics to decide if a file is text or not. The advantage is that the .gitattributes file(s) come with the repo, so there is no risk of local settings to come in the way.
  • When core.autocrlf is set to true or input, and the same heuristics as above identify a text file.

I used to think that on a pure Windows project, there was no reason not to keep the native line endings (carriage return followed by line feed, crlf, 0x0d 0x0a) all the way. However, the “new Microsoft” general openness means it is less and less frequent to purely stay in Windows world. Moreover, having git know additional info about files through .gitattributes has other advantages. So now I would advise to always include a .gitattributes file and let git do its magic with text files.

How about the working directory ?

This is where it gets a bit more complicated. One of the reasons is that git configuration comes from multiple places, so for instance the local .git/config settings override the global ~/.gitconfig ones. A nice trick to better understand your configuration is the command git config --list --show-origin. Another invaluable command is git ls-files --eol, which will show you for each file its line ending style in the repo (index) and in the working directory, along with its attributes.

  • Of course any manipulation done by any tool or editor may affect the current state of your files, here we’re interested in what’s there after a clone or checkout.
  • First, if there is an attribute eol for a file (identified as text), the corresponding value (lf or crlf) is always used. A classical use is *.sh text eol=lf, where even on Windows, you want your bash to understand the script
  • Else, if core.autocrlf is set to input, nothing is done, which usually means lf are used
  • Else, if core.autocrlf is set to true, crlf are used
  • Else (i.e. core.autocrlf is set to false), the value of core.eol is used : either lf, crlf, or native, the default, where the platform usual ending is used.

warn: CRLF would be replaced by LF in file

Before staging a file, git checks if a round-trip commit then checkout will leave it unchanged. A rather frequent case when this is not the case, is when using cross-platform tools under Windows, with core.autocrlf set to true. If the tool generates of modifies a file so that it has lf endings, then it will of course also have lf within the repo, but theses will be changed back to crlf on checkout ! Similarly, if core.autocrlf is set to false or input, with a handling of line endings through .gitattributes, then a text file with crlf will be normalized to lf in the repo, and checked out the same, losing the cr.

This is where core.safecrlf comes into the picture : when a staging is deemed not reversible, according to its value, git will either prevent the operation (if set to true), issue a warning (set to warn, the default), or do nothing (set to false).

Conclusion

With most tools on Windows now properly handling lf, I’m not sure that core.autocrlf set to true is that useful. So I think that I will go with input for the global setting, with possible overrides on per-repo basis.

Since most of my repos have (or will have) an appropriate .gitattributes file, I’m confident enough that text files are handled properly, and since my tooling is not always consistent eol-wise, I’ll set core.safecrlf to false globally, trying not to forget overriding it in the few repos without .gitattributes.

For any new project, I’ll have a .gitattributes file along the lines of :

* text=auto

*.sh text eol=lf

*.sln text eol=crlf
*.bat text eol=crlf
*.cmd text eol=crlf