Usually when I rename/move a file in git, git extensions shows it as a rename operation after the file is staged. Other SO questions like this one indicate that this should happen automatically regardless of whether I use git mv
or regular moves. However, I did a large-scale reorganization today in preparation for a git subtree split
(basically there was a Src
folder that I split into Core
and Main
, and for the record I used Windows Explorer to move the folders, not git mv
). When I staged the changes (only a few project files changed, none of the source code), almost all of the changes appeared as add+remove rather than renames. I hoped this was just a glitch in Git Extensions, but when I pushed to github it wasn't any beter. You can see the mess here:
https://github.com/qwertie/Loyc/commit/3eb4bd9dbe3d0023858659cb96e860921f0819e3
After I performed a local git subtree split --prefix=Core -b core
and pushed to another local repo, it appeared that the history of all files in the Core
folder had been lost.
What's going on? Is there a way to preserve the history of all these moved files?
> git version
git version 1.8.3.msysgit.0
Given that you're using Windows, my first suspicion is that something (does not matter what, except for trying to stop it) turned newlines into CRLFs or vice versa, so that every line really IS different. I was able to clone the URL and sure enough, something modified CRLFs:
$ git show HEAD^ | vis | head -40
[snip]
diff --git a/Src/Baadia/ArrowheadControl.cs b/Baadia/ArrowheadControl.cs
similarity index 94%
rename from Src/Baadia/ArrowheadControl.cs
rename to Baadia/ArrowheadControl.cs
index 2e7aa5c..cdb374f 100644
--- a/Src/Baadia/ArrowheadControl.cs
+++ b/Baadia/ArrowheadControl.cs
@@ -1,18 +1,18 @@
-\M-o\M-;\M-?using System;\^M
-using System.Collections.Generic;\^M
-using System.ComponentModel;\^M
[snip]
+\M-o\M-;\M-?using System;
+using System.Collections.Generic;
[snip]
(The \M-o etc is a byte order marker, which is unchanged. It's the removal of the \^Ms, carriage returns, at the end of each line that has git convinced it's not just a simple rename.)
Edit: now that the source of the CR-LF vs LF has been (sort of) tracked down and you want to "insert" a commit that just does the de-CR-ing...
Let's say (based on what I saw when I cloned) you have this sequence of three commits:
... - A - B - C <-- branch
where A
is the commit that has CRLFs, B
is the commit that has all the files renamed and also the CRLF to LF transition, and C
is the tip of branch branch
.
First, you want to extract commit A, and get into "detached HEAD" mode. That's easy:
git checkout branch~2 # branch = C, branch~1 = B, branch~2 = A
Next, you want to clean up all the files to remove CRs while leaving LFs. On a Unix-like box you might use dos2unix
or whatever, but let's say the zoop
command does it recursively with -R
:
zoop -R . # I'm assuming you're at the top of your work tree
Now commit the result:
git commit -am 'CRLF -> LF only' # or whatever message
The commit graph now looks like this:
... - A - B - C <-- branch
\
A2 <-- HEAD
Now you just want to make the work-tree and index look like commit B
, which we can do with two commands:
git rm -rf .; git checkout branch~1 -- .
The first git command empties the tree and index completely and the second re-populates index and tree from commit branch~1
, which is to say commit B
. (Note that this form of git checkout
does not change branches, it merely extracts files. Being at the top of the repository, we extract the file .
, which recursively extracts all files.) Commit the result using the log message from B
:
git commit -C branch~1
giving:
... - A - B - C <-- branch
\
A2 - B' <-- HEAD
The tree for commit B'
matches that for B
, as does the message; only the parent-ID (and some time stamps) is (are) different.
Repeat steps-for-B
for commit C
, name-able as branch
this time instead of branch~1
.
When all done, move branch branch
to point to commit C'
, as named by HEAD
:
git update-ref -m "move to rewritten history" refs/heads/branch HEAD
or:
git branch -f branch HEAD
(this won't let you specify a custom message), then git checkout branch
to get back on it, abandoning the old commit C
to the reflogs.
(You might be able to use git cherry-pick
to copy commits B
and C
, but that will probably be slower, and will fail if it's confused by the CRLF -> LF change.)
When it comes time to git push
the result, you will have to use a force-push, since updating branch
on github will not be a fast-forward operation (will abandon the old B
and C
commits).