Git cherry-pick causes merge conflict while merging does not

两盒软妹~` 提交于 2019-12-05 06:21:18
VonC

I just tested that scenario, after reading:

If the modification to the first line (Some text. instead of Some text) was done immediately after 0x2, as in 0x3, then git cherry-pick 0x3 works without conflict.

But if that same change is done a few commits later, the git cherry-pick won't generate a patch compatible with the content of 0x2.

There is no notion of "common ancestor" here (as incorrectly stated here): only of "patch".

A git cherry-pick, even if it uses merging strategies, is not a git merge.

  • A git merge will look for a common ancestor (going back in the history of commits of 0x5, to find a common commit with 0x2: here 0x2 itself)
  • A git cherry-pick only creates a patch by doing a unified diff between two commits (by default, the commit you mention and its immediate parent 0x4)

The point is: that patch won't be able to be applied on top of 0x2.

Try if for yourself: a git cherry-pick 0x5 is like doing a git diff -p 0x4..0x5.
You will see in that patch lines which are not part of 0x2, and reflect changes coming from 0x3 or 0x4.

Such a patch cannot be applied on 0x2, as it is based on a content which does not exist for 0x2.

I have created a test repo in which I made from master two modifications.

  • Adding some text from other branch (no modification to the first line)
  • Then a second commit modifying the first line.

Here what my patch looks like:

C:\Users\vonc\prog\git\tests\cp>git diff -p dev~..dev
diff --git a/f b/f
index 2b6dd7b..1c4bb79 100644
--- a/f
+++ b/f
@@ -1,4 +1,4 @@
-"Some text"
+"Some text."^M

 Adding some text from other branch.  Adding some more text.

I cannot apply (cherry-pick) that commit onto 0x2, as 0x2 has no line "Adding some text from other branch" yet. It only has one line "Some text".

That is why git reports a conflict: the content has diverged, and that patch is supposed to change line 1 on top of a file including "Adding some text from other branch", which 0x2 does not have.

This differs from a git merge of course, as a git merge will look for a common ancestor (here 0x2) and reports all modifications since 0x2 onto 0x2 without any issue.

I'm going to be less technical, and more "common sense" than the other answers. (Which is not always the right thing to do, but I think it is in this case.)

You are thinking, "It's obvious, the change that I want simply appends a line to the end of the file. Why can git not just do that?"

Git is applying a patch. Applying a patch involves trying to figure out where the patch should go. This involves looking at the approximate line number and looking for n lines of identical context before and after the change being made. In this case, the after context is End Of File. But the before context "Adding some text from other branch. Adding some more text." cannot be found in the destination file. Git therefore flags this as "I can't match up some before and after context for this change, so I will put conflict markers in where the change should probably go, but leave it up to human intelligence to do the final resolution."

While in this case you happen to know that this could have been trivially resolved, there are many similar cases where coders have been very glad that git flagged a situation like this and let them manually fix it.

If this is something you will run into often, git allows you to write custom merge handlers for files so if you know more about the expected and allowed merge patterns and syntax of your files, you can code that into a merge handler.

If you say merge, all three (3-5) commits are merged. If you cherry-pick only the last one is used. The file(s) it concerns may have changes already in 3 and/or 4 so the initial stage doesn't match what is expected.

Do check the conflict to see why it happens with a merge tool.

For example this will cause a conflict:

Original
Original
Original
Commit3
Original
Commit5
Original

Here git's diff engine will show that you changed a line that is preceded by a few lines, including a line from commit 3. Cherry-pick doesn't see that line in the original so git will scream conflict and ask you to handle it. There is no conflicting edit per se (for example the same line was edited), git just isn't sure where to put the edited part.

Of course you can manually edit the files however you want. Only merging branches will have issues and conflicts and not all conflicts mean "you can't do this", they just mean "I don't know exactly what to do, you handle it yourself."

Let's make a small experiment of your case.

mkdir tmp
cd tmp
git init
echo "root" >> hello
git add hello
git commit -m 'root commit'
git branch exp
#open the file hello and write 111 in the 2nd line
git add hello
git commit -m '111'
#open the file hello and modify 111 to 112 in the 2nd line
git add hello
git commit -m '222'
#now we have 3 commits in the master branch
git log --oneline
6b86a6d 112
663a36b 111
28b2e12 root commit
git checkout exp
#now we are in the exp branch, which has only 1 root commit.
git log --oneline
28b2e12 root commit
#now we try to cherry pick 6b86a6d 
git cherry-pick 6b86ad6
error: could not apply 6b86a6d... 112
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
#a conflict occurs,why?

The 2nd commit in the master branch is equal to modify the 2nd line, from empty to 111 The 3rd commit in the master branch is equal to modify the 2nd line, from 111 to 112

We can see the 3rd depends on the 2nd. If there was no 111, we could not update 111 to 112. When we cherry-picked the 3rd without the 2nd, no 111 can be found in the file hello of the exp branch, which led to the conflict.

If we cherry-picked the 2nd first, hello's 2nd line would be updated from empty to 111. And then if we cherry-picked the 3rd, hello's 2nd line would be updated from 111 to 112.

Let's go on with the experiment.

#abor the cherry-pick first
git cherry-pick --abort
#go back to the master branch
git checkout master
> world
git add world
git commit -m 'add new file world'
git log --oneline
1cf8f90 add new file world
6b86a6d 112
663a36b 111
28b2e12 root commit
#now we have 4 commits in the master branch
#cherry pick the 4th commit into the exp branch
git checkout exp
git cherry-pick 1cf8f90
[exp 79a34bb] add new file world
 Date: Sun May 15 20:55:39 2016 +0800
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 world
#no error

The 4th commit is equal to add a new file world. This change does not depend on the 2nd or the 3rd commit. It can be cherry-picked into the exp branch without any trouble.

Let's go back to the conflict scenario.

#cherry pick the 3rd commit again
git cherry-pick 6b86a6d
error: could not apply 6b86a6d... 112
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
git status
On branch exp
You are currently cherry-picking commit 6b86a6d.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   hello
#we can see conflict ocurrs in the file hello. open hello with text editor
hello world
<<<<<<< HEAD
=======
112
>>>>>>> 6b86a6d... 112

<<< and >>> part is the conflict context. The <<< and === part is what it is now in the current branch, aka the exp branch. It's blank. === and >>> part is what it is in the commit 6b86a6d. It's 112. The cherry-pick process cannot decide which part to apply, so we have to make it by ourselves. If we insist it should be blank, then just remove the lines of <<<,===,>>>, and 112. If we want 112 instead, then just remove the lines of <<<,===,>>>. Of course we can keep all except <<<,===, and >>> in some case. In a word, keep what you want and remove what is not needed. Save and exit. git add hello;git cherry-pick --continue Then all is done. For more info please refer to 'HOW CONFLICTS ARE PRESENTED' in git merge --help.

In another case, if two commits from two branches update the same line or lines with different contents and git cherry-pick one commit to the other branch, it leads to conflict too.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!