Oliver Salzburg Oliver Salzburg - 3 months ago 19
Git Question

Why does git keep messing with my line endings?

I'm on Windows and I have

core.autocrlf
disabled:

$ git config core.autocrlf; git config --global core.autocrlf
false
false


Now, I would assume, that git does not mess with any line endings, but some files in my repo keep causing issues.

I just cloned a fresh copy of the repo, and git is already telling me that there are unstaged changes. When I
git diff -R
, I can see that CRLF line endings have been added to a file:

diff --git b/nginx-1.11.1/contrib/geo2nginx.pl a/nginx-1.11.1/contrib/geo2nginx.pl
index bc8af46..29243ec 100644
--- b/nginx-1.11.1/contrib/geo2nginx.pl
+++ a/nginx-1.11.1/contrib/geo2nginx.pl
@@ -1,58 +1,58 @@
-#!/usr/bin/perl -w
-
-# (c) Andrei Nigmatulin, 2005
+#!/usr/bin/perl -w^M
+^M
+# (c) Andrei Nigmatulin, 2005^M


I don't understand where these line endings come from, but I'm also unable to "revert" this change. When I checkout the file again, it will still be modified afterwards:

$ git checkout -f nginx-1.11.1/contrib/geo2nginx.pl
$ git status
On branch dev
Your branch is up-to-date with 'origin/dev'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: nginx-1.11.1/contrib/geo2nginx.pl

no changes added to commit (use "git add" and/or "git commit -a")


This makes no sense to me, so I just run
dos2unix
on the file:

$ dos2unix nginx-1.11.1/contrib/geo2nginx.pl
dos2unix: converting file nginx-1.11.1/contrib/geo2nginx.pl to Unix format...


Now there surely shouldn't be any changes, right? But the file is still being shown as modified in
git status
and
git diff
will still report CRLF line endings in the working copy.

When I now stage and commit the file, the resulting file will have LF line endings, even though the diff showed CRLF line endings.

I don't have a global
.gitattributes
(
git config --global core.attributesfile
does not output anything). And the
.gitattributes
in the project has
* text eol=lf
set (full
.gitattributes
).

What is going on here and how can I resolve this?

Reproduce the issue



I can repro this issue with an open source project I'm maintaining:

$ git clone git@github.com:fairmanager/fm-log.git
Cloning into 'fm-log'...
remote: Counting objects: 790, done.
remote: Total 790 (delta 0), reused 0 (delta 0), pack-reused 790
Receiving objects: 100% (790/790), 201.71 KiB | 138.00 KiB/s, done.
Resolving deltas: 100% (418/418), done.
Checking connectivity... done.

$ cd fm-log/
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: .idea/dictionaries/OliverSalzburg.xml
modified: .idea/inspectionProfiles/Project_Default.xml
modified: .idea/inspectionProfiles/profiles_settings.xml

no changes added to commit (use "git add" and/or "git commit -a")

Answer

Interesting: with the newly added-to-question repo I tried cloning it and, on BSD, see the same thing:

$ git clone git@github.com:fairmanager/fm-log.git
Cloning into 'fm-log'...
remote: Counting objects: 790, done.
remote: Total 790 (delta 0), reused 0 (delta 0), pack-reused 790
Receiving objects: 100% (790/790), 201.71 KiB | 0 bytes/s, done.
Resolving deltas: 100% (418/418), done.
Checking connectivity... done.
$ cd fm-log/
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .idea/dictionaries/OliverSalzburg.xml
    modified:   .idea/inspectionProfiles/Project_Default.xml
    modified:   .idea/inspectionProfiles/profiles_settings.xml

no changes added to commit (use "git add" and/or "git commit -a")

Trying to see what's going on:

$ git diff
warning: CRLF will be replaced by LF in .idea/dictionaries/OliverSalzburg.xml.
[snip]

It seems that HEAD:.idea/ files actually have carriage-returns in them (and the last line has no newline, hence the prompt right after the close angle bracket):

$ git show HEAD:.idea/dictionaries/OliverSalzburg.xml | vis
<component name="ProjectDictionaryState">\^M
  <dictionary name="OliverSalzburg">\^M
    <words>\^M
      <w>colorizer</w>\^M
      <w>multiline</w>\^M
    </words>\^M
  </dictionary>\^M
</component>$ 

The work-tree version likewise has carriage returns (no cut and paste but vis shows the same \^M line endings).

So what has happened in this case, at least, is that due to the .gitattributes setting of:

$ head -2 .gitattributes 
# In general, use LF for text
* text eol=lf

Git will convert these files to LF-only during commit, vs the HEAD version that contains CR-LF endings. This is what git status is saying here.

Commenting out the * text eol=lf in .gitattributes makes the status go away (since the files won't be converted), though of course .gitattributes is now marked as modified. Interestingly, putting the attribute line back again, the status goes completely silent: it's necessary to force git checkout to replace the work-tree version to get the status back (e.g., manually rm -rf .idea and check out again, or git reset --hard).

(Presumably—I'm guessing a bit here—the index entry gets marked specially during git checkout when Git notices that the work-tree and HEAD version differ. This makes Git inspect the file closely. Modifying .gitattributes and running an internal diff via git status probably un-marks the index entries. This part is pure speculation meant to explain the weird behavior of git status...)