One trick I've successfully applied is to use git rebase
and dos2unix
to fix this automatically.
To to this, run an interactive rebase:
git rebase -i [last_good_commit]
This drops you in an editor listing all the commits to be reapplied. For example:
pick ad31eff3 Some commit message
pick 138fab7d Some other commit message
Now modify each line into exec git checkout xxx . && dos2unix *.txt && git commit -a -C xxx
, where both xxx
's are the hash of the commit in question, and *.txt
lists all the files in which you need to fix line endings.
For the example above, this becomes:
exec git checkout ad31eff3 . && dos2unix *.txt && git commit -a -C ad31eff3
exec git checkout 138fab7d . && dos2unix *.txt && git commit -a -C 138fab7d
Save the file and quit the editor: Git will process each command in turn and you should be left with a modified history where the line endings were never wrong.
Using a regex for generating the commands file
Here's a regex (actually a vim
command - adapt it for the regex dialect your editor speaks) that can be used to convert the rebase commands file with pick
lines to the correct commands for this fix:
:%s/pick \([^ ]*\) .*/exec git checkout \1 . \&\& dos2unix *.txt \&\& exec git commit -a -C \1
How this this work?
For each commit, this trick runs the following commands:
git checkout xxx .
This updates the current working copy contents to match the original commit (with incorrect line endings) exactly, without touching the branch head (current commit) or index at all.
dos2unix *.txt
This fixes all the line endings in the working copy, replaying dos CRLF with unix LF (you can use unix2dos
or whatever other command here if you need some other conversion).
git commit -a -C xxx
This commits all the changes (from the original commit, but with changed line endings), reusing the commit message from the original.
The trick here is that for each commit, the first command switches back to the original files. Normally with a rebase, you just pick commits, but that won't work in this case - once you've fixed the line endings in the first commit, the subsequent commits will likely no longer apply, leaving you to fix the mess. In this case, for e.g. the second commit the git checkout
used will just use the files from the original commit (tree) as-is, meaning it completely ignores (and thus also reverts) the line endings fixed for the first commit. But that's ok - the dos2unix
command will happily fix them again for every commit, and the end result is a clean history where you cannot tell that the line endings were ever wrong.
A smart reader will notice that we're actually not really using any rebase features, since all commits are created using explicit git commit
commands instead of letting rebase handle this. So this just uses rebase to reset to the good commit, generate a list of commits to apply and execute commands in order (and do some useful checking, such as whether the working copy is clean and commands do not fail).
Using this for other things
This approach also works well for other automatic repeatable fixes, such as code formatting. I originally figured out this approach to run astyle
on a new codebase without creating a massive fix commit.