Git and renaming and replacing files
I usually don't have a problem with renaming with git, but I'm facing a really tricky problem I'm trying to solve.
For various reasons, I have a situation where we have a file
. Due to some long-standing decisions, it is in a completely wrong place and needs to be moved to
However, there is a lot of code that needs to be changed, and for various reasons, we need to save the file in a new location and in an old location for a while.
So a natural (ish) approach would be to do this:
git mv dir1/file dir2/file git commit -a
so far so good:
> git diff master --name-status --find-renames R100 dir1/file dir2/file
So then we do
ln -s ../dir2/file dir1/file git commit -a
but it happens
git diff master --name-status --find-renames A dir2/file T dir1/file
And if someone changes
to master and I try to pull it out, I am told that the merge conflict with
remains the same. I thought I was reading other posts that tracked git content but seem to track file names as well as content. And the fact that the content has been moved is completely absent.
So how do I get git to know that I actually renamed the file and then added a new file that just has the same name as the old one?
Note. I would rather not do this multiple times. There are several such files that are affected, and the likelihood that someone is making changes to one of them in parallel is quite high and there is no guarantee that they will be able to pull to get renamed and then pull to get a soft link ...
Example of adding. I was removing a function from a python module
that should never have been there,
should have been empty. This also doesn't show up as a rename. Even though the content of the new file is 99% identical to the original
, and the content of the new
0% is identical to the content of the old one. Everything is fine until I add a file with the same name.
source to share
Git essentially tracks content, not - or rather we should say "in addition to" - us. Diff goes wrong because it
(necessarily) tries to match names and compare the contents of two separate commits (or one commit and current working directory, or one commit and current index, etc., but these are just variations on the "compare two commits ").
More specifically, when
comparing trees 1
, by default, it is assumed that the only candidates for renaming are those where some filename exists in
but not in
, and another (different) filename exists in
but not in
So when you make the first commit, you have two commits - let those A and B - with two trees where it
"disappears" from A and
reappears in B. This is a candidate for renaming -detection, and since the content of the file is 100% identical, git easily sees the rename and gives you the
diff output .
When you make a second commit, you add commit C with a third tree. Comparing B and C works great:
appears in both, and the new symlink
only appears in C, and the diff output from that pair is good too. The problem arises when comparing A and C: now
appears in both, but
- only in C, and
does not understand that there is a candidate for renaming.
There is a flag
- or you can specify
more than once - which (rather unsurprisingly) makes the copy / rename detection code more complex. In this case, git will consider the possibility that a file that "appears unchanged" (has the same name in both trees) can be copied or renamed into another file that "appears new" (exists in the second tree, but not in the first). This is not enabled by default because the fully generic version is extremely computationally intensive.
Unfortunately, there is no way to control the diff options used when calculating diff sets for
. The merge command sets some defaults (-M50%, etc.) and does a few differences and doesn't let you install
. So even if it works for leadership
, it won't resolve your merge conflict.
Note that when you do a merge, 2 git only computes two sets of differences: from merge base 3 to current
and that from merge to merged commit (git merges a commit, not a branch: the fact that the result merges this branch when this the commit is the end of the branch, is sort of a "deliberate" match "). So you can do the rename as one commit and symlink as the second, but to get to
" see "the rename you have to do two separate
s as well. but for this you will need to do git
machines are smarter, so that he can at least understand that changing the file type gives a much better chance of finding the rename if it "finds copies / renames a little harder".
(Note that adding this to the diff engine will fix both problems - git diff without seeing the rename, and git merging without seeing the rename right away.)
1 Trees here mean complete file trees, not git
2 This is particularly the case for a two-parent merge. Octopus merges are handled differently. I have not burst into the internal fusion of octopuses and I can no longer say anything about it.
3 . The merge base depends on two (or more) commits to be merged, and to complicate matters with the default (
) strategy , if there are multiple merge candidates, git computes a "virtual merge base" which does not necessarily match any actual commit. The details are not something I can explain correctly here: I know the general idea, but not the specifics inside git, and in any case it is rarely important and not directly related to your problem. There's a pretty good example here if you want to read more, although this example uses some pretty clear-cut terminology.
source to share