I tried moving a file into a subfolder, and git sees that it has been just "renamed"... but also sees that its whole content got removed and then added.
For some reason with some files I could move them without losing all of their lines' actual/correct/original git blame
. But with some, I can't.
I'm on Windows and using SourceTree. It seems like that maybe it's not git's fault but SourceTree's?
I tried it on my other computer, using Windows 10 and SourceTree as well, and everything works there. I could move around everything everywhere without triggering changes, affecting git blames, etc.
Any recommendations? I guess commiting file movements with CLI would be the go-to, but I'm using SourceTree to avoid that. :\
Anyway if there is no other solution, could you recommend a quick command to easily & safely commit a whole folder's and its subfolders' movement?
Thanks in advance!
There are two things to understand here.
First, Git knows nothing about "what changed". Git traffics entirely in commits. A commit is not a change or a difference; it is a snapshot of the entire state of your project. Thus, if you make a commit, and then move a file and add everything and commit again, all you have is one commit that contains file A (but not file B) and another commit that contains file B (but not file A). In other words, Git is completely neutral as to "what happened"; that is something that humans like to think about, but it is no business of Git's. Git just knows that there was one state of things and then there was another state of things. As King Lear says, "Look on this picture and on this."
But second, Git also tries to be kind to humans by giving information about what changed, even though it doesn't actually know anything about the matter. When you ask for a diff, whether that be through git show
or git log
or git diff
or whatever, Git tries to do exactly what a human being would do: it looks at the first commit and looks at the second commit, and tries to "spot the difference" between them. The conclusions that it comes to are not in any way baked into the repository; the repository contains nothing but commits. These conclusions about what the difference is are ephemeral — Git is trying figure out right now how these commits differ, purely for the purposes of showing the difference to you.
(The exception to this rule is what happens when Git performs a merge. A merge is a situation where Git calculates a diff and actually uses it to generate a completely new commit. All commands that use merge logic — git merge
, git cherry-pick
, git rebase
— operate this way.)
So now, with the basics out of the way, let's talk about how Git displays the difference between two commits to you, the human. If you make a commit, and then move a file and add everything and commit, as far as Git is concerned the only thing that happened is that one file vanished (was deleted) and another file appeared (was created). That is, as it were, its fallback position: "That's my story and I'm sticking to it." And you have to admit that this is a good rock-bottom description of what happened.
But Git has in fact heard of the concept of file renaming, and it knows that when it displays a diff or a history, knowing about file renaming might be useful. So when a file disappears and another appears, Git also compares the files and tries to decide whether they might in fact be, in terms of their content, the "same" file. If all you did between commits was move one file, without doing anything else at all, Git is very very likely to conclude that the file was renamed. And that's nice, because if you ask to do a git log
where you are tracing the history of a file back through time (i.e. git log --follow
), Git can then display the history before the move and the history after the move as a single history. (Remember, Git does not know anything about history; this is purely a display convenience to help you, the human.)
But that — and this is is my point — is as far as it goes. Git is just a machine, and "very very likely" is no more than it says. Whether Git will in fact see a particular diff between two commits as involving a file renaming is something of a crapshoot. There is uncertainty here as to how Git will behave. In fact, you can try to affect the way Git thinks about this; see the -B
and -M
options of git log
, where you get to say how similar two files need to be to be considered the same file.
Moreover, you are not using Git directly; you are using a GUI. So you are reliant on that GUI for two things that you do not control: what the GUI says to Git, and how the GUI displays what Git says to the GUI. Those things are uncertain too.
So with all these layers of uncertainty, both the uncertainty as to how Git will understand the history and the extra layers of uncertainty added by the use of the GUI, it is not surprising if sometimes the GUI shows a file rename as purely "one file vanished and another file appeared" — because that, after all, is the rock bottom truth of the matter.