Home > other >  git merge with renames in one branch
git merge with renames in one branch

Time:09-16

I have two branches. In one branch I only renamed files with git mv. In another branch some of those same files were modified. I would like to merge the latter branch into the former. I would think that since git knows about the rename operation this would not be a problem, yet this is not what I am seeing

Here is a scaled down experiment demonstrating the issue:

/tmp/gittest λ git init
Initialized empty Git repository in /private/tmp/gittest/.git/
/tmp/gittest:no-branch*? λ touch foo
/tmp/gittest:no-branch*? λ git add foo
/tmp/gittest:no-branch*? λ git commit -m initial
[main (root-commit) 532197b] initial
 1 file changed, 0 insertions( ), 0 deletions(-)
 create mode 100644 foo
/tmp/gittest:main? λ git checkout -b moved
Switched to a new branch 'moved'
/tmp/gittest:moved? λ git mv foo foo.moved
/tmp/gittest:moved*? λ git commit -m 'move it'
[moved 384b27d] move it
 1 file changed, 0 insertions( ), 0 deletions(-)
 rename foo => foo.moved (100%)
/tmp/gittest:moved? λ git checkout main
Switched to branch 'main'
/tmp/gittest:main? λ ls
foo
/tmp/gittest:main? λ echo "v2" > foo
/tmp/gittest:main*? λ git add foo
/tmp/gittest:main*? λ git commit -m 'changed'
[main 25c98d1] changed
 1 file changed, 1 insertion( )
/tmp/gittest:main? λ git checkout moved
Switched to branch 'moved'
/tmp/gittest:moved? λ ls
foo.moved
/tmp/gittest:moved? λ git merge main
CONFLICT (modify/delete): foo deleted in HEAD and modified in main. Version main of foo left in tree.
Automatic merge failed; fix conflicts and then commit the result.

I get a similar result with rebase.

I am aware that git stores tree states not diffs, but I thought the fact that git explicitly knew about the rename would help with that.

What is going on and what is the correct approach here?

CodePudding user response:

Even though you "told" Git about the rename, it still doesn't "know". At least, it doesn't know from the git mv command.

The working tree's internal state after git mv a.txt b.txt is more or less the same as if you had done:

$ mv a.txt b.txt
$ git rm --cached a.txt
$ git add b.txt

The fact that you used git mv isn't recorded anywhere and doesn't influence Git's operation.

The default merge strategy, either recursive on older versions of Git or its newer cousin ort on newer versions, does attempt to do rename detection, but it does this based on similarities of deleted and added files, not by "remembering" that you did a git mv at some time in the past.

Fortunately, recursive/ort should do this rename detection on a commit by commit basis, so if you have a commit where a bunch of files were renamed with no modifications, the similarity index for these files will be 100%, and the operation will always be detected as a rename. The resulting merge should always be able to successfully merge the rename operations from one branch with modifications made on another branch to yield the desired result.

Unfortunately, there is one exception. Empty files always have similarity index 0% when compared to any other file. This is to prevent detecting unrelated empty file deletions and additions as renames. That's why your test case fails. If you replace the touch foo with echo 'a' >foo so the initial foo is non-empty, your test case will merge fine. The recursive merge strategy will see the rename (at 100% similarity) on the moved branch and the modifications on the main branch and merge the rename and the modification into a correct final result.

The fact that you are experiencing this problem outside of your test case is odd, assuming you are renaming non-empty files. Double-check that there is a commit that renames the files in question with no modification (or at least high enough similarity to be recognized as a rename -- git diff or git show on the commit can help determine this). Also, I see that you have modified your Git configuration to make main instead of master your default branch. Make sure you haven't made a similar configuration change to set your default merge strategy to something other than recursive/ort.

If you discover that the commit containing the renames did include some modifications that resulted in the files being too dissimilar to be identified as renames, you can modify the similarity threshold from its default of 50% in the merge command. For example:

git merge -X find-renames=40% main

CodePudding user response:

Good evening!

The moved branch refers to some commit and this commit refers to a tree and this tree doesn't refer to a valid blob, it refers to a blob for foo.moved filename (not foo)

Look at example in my case:

stack -> git lg

* ae9ca46 (master) changed
| * af7a220 (HEAD, moved) move it
|/
* a02287a initial



stack -> git cat-file -p af7a220
tree 7e4a4fd9f5c65d2dce07ff9050d28247bf9ce8f3
parent a02287a28f210498c286ae158b07ecb53a4fc94d
author nodorgrom <[email protected]> 1663272773  0300
committer nodorgrom <[email protected]> 1663272773  0300

move it



stack -> git cat-file -p 7e4a4fd9f5c65d2dce07ff9050d28247bf9ce8f3
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    foo.moved

CodePudding user response:

Make sure to at least try K. A. Buhr's answer first.

This wouldn't be my first choice (or maybe not even my second), but in a pinch you could also rename the files on the branch to be merged in with a new commit on that branch, and then do the merge. If those are the only files that have conflicts then you could even use merge -X theirs to automate the resolution in one shot.

  •  Tags:  
  • git
  • Related