Git merge conflict talking about deletion but it is not

I am trying to resolve the conflict in git merge (git version 2.9) - the conflict assumes that the file in the "other" branch is deleted, but it is not. Here's a (fairly) short reproducible recipe:

 cd /some/new/folder/somewhere git init echo 'a on master' > a git add a git commit -m 'add a on master' mkdir f git mv af/ git commit -m 'move a to f on master' git checkout -b hotfix HEAD^ echo 'new content a on hotfix' > a mkdir f git add a git mv af/ git commit -m 'move a to f on hotfix with different content' git checkout master git merge hotfix 

Git now assumes that f / a has been removed in the patch branches, but this is clearly not the case.

 CONFLICT (rename/delete): f/a deleted in hotfix and renamed in HEAD. Version HEAD of f/a left in tree. Automatic merge failed; fix conflicts and then commit the result. 

I would expect a "normal" conflict for f / a as follows:

 <<<<<<< HEAD a on master ======= new content a on hotfix >>>>>>> hotfix 

I don’t understand why git offers conflict how to rename / delete conflict?

Thanks for any help.

+5
source share
2 answers

Good formulated question.

There is one drawback in your example:

file a consists of one separate line, therefore editing this line means "100% difference between two files".
git rename algorithm will not detect two 100% different files as rename.

If you change your example:

  • writing 4 lines to your source file a
  • editing only the first when changing its contents to hotfix

merging does not cause conflicts and leads to f/a , which includes a patch modification.


I hope this means that in your real-world scenario, the number of times that git says created vs deleted is limited.

If you can handle them manually, here you can see the usual three-way merge:

  # find the merge-base : $ git merge-base master hotfix 3e12717099f3dc7b83b3d267e5cbd580ec8e69a1 # get the content you expect for "ours", "base" and "theirs" : $ git show master:f/a > f/a.ours $ git show hotfix:f/a > f/a.theirs # here is the manual part : I don't know how to have an automatic detection # of the correct base file : $ git show 3e12717:a > f/a.base # start a 3-way merge : $ meld f/a.ours f/a.base f/a.theirs # if you are satisfied with the content in a.base, # and want to use it as your new a : $ mv f/a.base f/a $ git add f/a 
+1
source

I don’t understand why git offers conflict how to rename / delete conflict?

Git discovered a rename. More precisely, he discovered one, but not the other.

I used your example script and got the same result:

 CONFLICT (rename/delete): f/a deleted in hotfix and renamed in HEAD. Version HEAD of f/a left in tree. Automatic merge failed; fix conflicts and then commit the result. 

There are two keys here: how git merge merges and how git diff works. Start with the first one.

How merging works (like a verb)

The purpose of the merger is to combine two sets of changes on two different lines of development (usually this is done by two different people, but in our case, made by one person "in two different hats", one for a while). These changes must necessarily begin with some common starting point, which git calls the merge base.

To perform this merge, git must find the merge base. For regular merges like this, a merge base is a commit that is shared between the HEAD (current commit) and the target commit. In my specific case, a target called hotfix allows commit hash b45a155... :

 $ git rev-parse hotfix b45a15547101d836d84dbdf4758d71dc91c93353 

and HEAD is 2ca7d2d... :

 $ git rev-parse HEAD 2ca7d2d15d4d537edb828a7f3bfff3a2182630ec 

The merging base of these two commits is the initial commit d763d32 add a on master :

 $ git merge-base --all HEAD hotfix d763d32af0cafdb0378b96b25e56fd70d63213d1 $ git log --graph --decorate --oneline --all * b45a155 (hotfix) move a to f on hotfix with different content | * 2ca7d2d (HEAD -> master) move a to f on master |/ * d763d32 add a on master 

We really don't need all these hashes, but sometimes it's nice to see them in a specific form. The fact is that we have two different sets of changes: “what we did”, going from d763d32 to 2ca7d2d , and “what they did”, going from d763d32 to b45a155 .

We can find the first of these things by running git diff :

 $ git diff d763d32 2ca7d2d # using raw IDs $ git diff hotfix...master # using the special "..." syntax 

This is "what we did." I will show it in a moment.

Next, we (or Git) can find the second of these things by running git diff again:

 $ git diff d763d32 b45a155 # using raw IDs $ git diff master...hotfix # using the syntax again 

How git diff makes differences

This explanation becomes long and confusing in great detail when it matters, and ultimately it really matters. Let me pass it on to fooobar.com/questions/1259949 / .... So git will try to determine the renames. Whether he can detect them depends on many details.

In our particular case, however, what happens is that git detects renaming when it goes from merging to the tip of the wizard:

 $ git diff hotfix...master diff --git a/ab/f/a similarity index 100% rename from a rename to f/a 

The three-dot syntax describes git diff in order to find the base for merging the two specified commits (tip hotfix and tip master ), then diff, which combines the base with the second commit, i.e. tip of the wizard. He discovered a rename: the source file a was 100% identical to the new f/a file. Therefore, we have a renaming.

The second diff, though ... ah, there is a problem:

 $ git diff master...hotfix diff --git a/ab/a deleted file mode 100644 index 81d07e3..0000000 --- a/a +++ /dev/null @@ -1 +0,0 @@ -a on master diff --git a/f/ab/f/a new file mode 100644 index 0000000..158795c --- /dev/null +++ b/f/a @@ -0,0 +1 @@ +new content a on hotfix 

The old content a in the merge base is too different from the new content f/a in the accumulation declaration. git not only did not find the rename, it will never find the rename: the file is too different. This is not like the original in the least.

Rename detection usually works better than this.

In practice, when files are renamed, they usually retain a lot or even all of the original contents, as happened in the change of merge with the captain base. However, if they do not save "enough", git will not detect the renaming.

Fortunately, git has been around for a long time, since I wrote this answer back in January 2012. It still applies today - you can use -X rename-threshold=number to adjust the rename detection threshold level during merge - and almost everyone has git newer than 1.7.4. However, its flaws are still applicable. You can also read this other answer that I wrote in April 2016.

If you have many files and need automatic detection of renaming, you may need to prove yourself. If you have only one file, you can simply merge the file manually by doing your own “rename detection” using git merge-file to create a merge result. Just extract the three revisions (base, HEAD and others) into temporary files and use git merge-file to combine these three to get the desired result. Replace git with a slightly lame version with the correct one, and git add and you will go well.

+1
source

Source: https://habr.com/ru/post/1259948/


All Articles