LostSnail
LostSnail

Reputation: 151

Git recheckout files after change to .gitattributes

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?

Upvotes: 8

Views: 3652

Answers (3)

VonC
VonC

Reputation: 1326586

With Git 2.16+, try:

git add --renormalize .

This assume you have added in your .gitattributes

*.myExtension text eol=crlf

Upvotes: 5

jthill
jthill

Reputation: 60383

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 -- .)

Upvotes: 0

torek
torek

Reputation: 489045

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.)

Upvotes: 7

Related Questions