4

I have a repo which contains a file that was mistakenly committed with LF line endings, but it needs to have CRLF line endings. To address this, I have added a .gitattributes file to enforce the correct line endings on checkout, this appears to fix the problem when checking out new repos, but existing checkouts refuse to update the file unless I delete the file and then recheck it out. I saw in a different question that

git rm --cached -r .
git reset
git checkout .

will correctly reset line endings from CRLF to LF for affected files, but this does not seem to work for LF to CRLF. How can I get Git to automatically apply the changes from a .gitattributes?

LostSnail
  • 71
  • 3

3 Answers3

5

You can use this sequence:

git rm -r .
git checkout -- .

Note that this destroys any uncommitted changes!

However, your question as stated rings alarm bells.

These line-ending modifications are performed (only) during the index-to-work-tree copy phase. If instructed to do so, this phase will turn LF-only line endings as stored in the index into CR-LF line-endings stored in the work-tree.

Other line-ending modifications are performed (only) during the work-tree-to-index copy phase. If instructed to do so, this phase will turn CR-LF line endings stored in the work-tree into LF-only line endings stored in the index.

To understand all of this, note that:

  • This thing that is called, variously, the index, or the staging area, or (rarely) the cache, has all of your files. Well, temporarily, after git rm -r ., it has no files, but we'll put them all back in the next command. In general, it has a copy of every file, ready to go into the next commit.

  • The work-tree, or working tree or some variation on this name, also has a copy of every file. Your work-tree can also have extra files, which Git calls untracked files. Some or all of these untracked files may be ignored. (No tracked file is ever ignored. A file is tracked if and only if it exists in the index.)

  • Git makes new commits from whatever is in the index. The work-tree copy is not important here.

  • You cannot see what is in the index—not directly. Files stored in the index (and in commits) are in a special, read-only, Git-only, frozen format. You can get a list of all the files in the index using git ls-files (add --stage to get detailed information), but this is rarely useful.

What you can see are the files in the work-tree. They have been extracted from the frozen version, re-hydrated as it were, and turned into ordinary files. All of your computer programs can deal with them, because they're just plain old files in whatever format your computer likes. You can do anything you like to them.

Git has to copy files between the index—where they're freeze-dried and ready to be committed—and the work-tree. Sometimes, the direction of copy is from index, to work-tree. At this time, Git can turn LF-only into CR-LF. Other times, the direction of copy is from work-tree, to index. At this time, Git can turn CR-LF into LF-only.

Despite the profusion of options, these two transforms are the only line-ending transforms Git does. The various options are all just different ways to tell Git when to do them, and when not to do them.

Here's the alarm-bells line in your question:

I have a repo which contains a file that was mistakenly committed with LF line endings.

As far as Git is concerned, LF-only line endings are good. They are the natural state of files. So committing the files this way is not a mistake—not to Git anyway.

If you want such files to have CR-LF endings when they are ordinary files—not the freeze-dried Git-only internal Git files—that's fine too: you tell Git please do LF-only to CR-LF during extract and it does that. You can also tell Git please to CR-LF to LF-only during git add and it does that too, and your files continue to be LF-only when freeze-dried, but CR-LF when visible, usable, edit-able, etc.

If you want Git never to mess with your files at all—to leave them with CR-LF line endings even when freeze-dried—that's OK too. But Git is all about storing data forever. These frozen copies cannot be changed. All the freeze-dried, read-only, Git-ified versions stored in all the existing commits will be LF-only, for all time. If you compare these LF-only files to freeze-dried CR-LF files, every line will be different.

(It is possible to take all the old commits, convert them to updated different commits that have CR-LF endings, and throw out all the old commits and force everyone everywhere to switch from the old commits to the new ones. Sometimes that's the way to go. Sometimes it's not. You'll have to make this decision yourself.)

torek
  • 330,127
  • 43
  • 437
  • 552
  • While I like the extensive write up as it will likely help others who may be missing knowledge, I'm well aware of most of this (with the exception that I was under the impression that the CRLF to LF conversion, if performed, was done on the finalization of the commit, not when it was moved to the index). I was looking for a method of applying this difference without having to wipe out my entire working directory and recheckout every file. – LostSnail Aug 03 '19 at 10:38
  • 1
    I disagree with your statement of "As far as Git is concerned, LF-only line endings are good. They are the natural state of files. So committing the files this way is not a mistake—not to Git anyway." Git has no problem handling CRLF as a line ending, and as such, also sees it as a _good_ and _natural_ state of the files, and therefor is not a mistake either. This is also further a moot point because I don't _care_ what Git sees as good or bad, the file being committed with LF line endings was unintentional and containing LF endings in my working tree breaks things, and as such, is _WRONG_. – LostSnail Aug 03 '19 at 10:45
  • If Git does not think LF-only line endings are good, why does Git offer the ability to turn CR-LF line endings into LF-only line endings when stuffed into the frozen files? You can, by the same token, say that Git thinks that CR-LF line endings are good for *work-trees* because Git offers the ability to turn LF-only endings to CRLF endings in the work-tree. But Git doesn't offer the ability to turn LF-only work-tree files into CRLF files in the work-tree-to-index direction. – torek Aug 03 '19 at 16:59
  • Note that none of this implies that CRLF endings are *bad*, either. It just says that, for files as stored *inside* Git (vs those in the work-tree), LF-only line endings have one slight bit of preference, because the ability to do that is built-in. Should Git gain an ability to translate LF-only to CR-LF on the way in, Git would stop having this slight preference. – torek Aug 03 '19 at 17:01
  • My point is that what the line endings are, or even the presence of a line ending, is ultimately irrelevant to git, so it makes no sense to declare LF endings good when none of the alternatives are bad, and it makes even less sense to declare that using LF was not a mistake simply because Git can handle them, despite the fact that it breaks the project the file is a part of by using LF instead of CRLF. I could commit an empty file or a file with no line ending, or a file with either/both LF and CRLF, and Git will handle it just fine because they are files and that's really all Git cares about. – LostSnail Aug 05 '19 at 04:05
1

With Git 2.16+, try:

git add --renormalize .

This assume you have added in your .gitattributes

*.myExtension text eol=crlf
VonC
  • 1,042,979
  • 435
  • 3,649
  • 4,283
  • 1
    This was the first thing I tried. Unfortunately, this appears to only normalize the line endings so that they are consistent, since all the line endings are already LF, this operation does nothing. – LostSnail Aug 03 '19 at 10:20
0

If you haven't committed your new .gitattributes yet you won't want to reset your index. The simplest way to redo the current checkout with new attributes for everything but without erasing your dotfiles is to

git ls-files -z '[^.]*' | xargs -0 rm; git checkout -- .

(which is safer than the easier-and-usually-good-enough rm *;git checkout -- .)

jthill
  • 42,819
  • 4
  • 65
  • 113