Git cherry-pick causes merge conflict while merging does not

16,922

Solution 1

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.

Solution 2

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.

Solution 3

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.

Solution 4

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."

Share:
16,922
Curious
Author by

Curious

Updated on June 18, 2022

Comments

  • Curious
    Curious almost 2 years

    I am trying to learn how to use git cherry pick, I read the manual pages that git returns by doing git cherry-pick --help but that did not seem to help. I will try and explain the problem below. I have two branches master and other.

    On branch master The commit history is

    0x2 Second commit from master branch
    0x1 Initial commit
    

    And the only file in the repository that I am tracking readme has the following contents

    Some text
    

    On branch other The commit history is

    0x5 CHECKPOINT, going to cherry-pick onto master
    0x4 second commit from branch other
    0x3 first commit from other branch:
    0x2 Second commit from master branch
    0x1 Initial commit
    

    And the contents of the readme file are

    Some text.
    
    Adding some text from other branch.  Adding some more text.
    
    Going to cherry-pick this commit.
    

    The working directory is clean on both branches with no untracked changes. From this point on when I switch to the master branch and merge with git merge other the merge happens gracefully with no merge conflicts. But when I try git cherry-pick 0x5 there is a merge conflict, I get the following output from git

    error: could not apply 0x5... CHECKPOINT, going to cherry-pick onto master
    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'
    

    And the readme file has the following contents

    <<<<<<< HEAD
    Some text
    =======
    Some text.
    
    Adding some text from other branch.  Adding some more text.
    
    Going to cherry-pick this commit.
    >>>>>>> 0x5... CHECKPOINT, going to cherry-pick onto master
    

    Why is there this merge conflict? I am trying to understand why it occurs. Shouldn't cherry-picking be the same as trying to make all the edits made on the commit that is to be cherry-picked yourself and then commiting that change onto the branch (master in this case)?

    Also when exactly is there a merge conflict in git? I seem to get them at weird times. Is this implementation dependent (for example dependent on the algorithm used to merge)?

    Thank you!

  • Curious
    Curious almost 8 years
    But if I go ahead and manually make the changes that would be made if the cherry-pick goes through then there is no such issue, why then is there an issue when I cherry-pick? Shouldn't the both be semantically the same?
  • Sami Kuhmonen
    Sami Kuhmonen almost 8 years
    @Curious git is very stupid when doing merges (which cherry-pick also is). It will whine about any little thing and ask for you to handle it yourself. It's annoying but then again, it's better not for it to guess if it doesn't know for sure.
  • Curious
    Curious almost 8 years
    I upvoted your answer but I am still having a bit of trouble understanding this. But then why was there no merge conflict when I merged? I edited the file from the end in all of the commits in the other branch so there should have been a merge there also?
  • Sami Kuhmonen
    Sami Kuhmonen almost 8 years
    @Curious I explained the algorithm git uses a bit. If you edit the same file and skip some commits git just doesn't know that it is the proper file and proper place so it asks you to handle it and make sure it's ok.
  • Curious
    Curious almost 8 years
    Thanks! What is the text saying exactly? The first three lines are in the original file on the master branch and commit3 is a commit on the other branch?
  • Sami Kuhmonen
    Sami Kuhmonen almost 8 years
    @Curious Yes. A single line edited in a file near an edit in the cherry-picked edit will cause a conflict. git diff will show how it's seen and there probably are lines that aren't found without commits 3 and 4.
  • Curious
    Curious almost 8 years
    I am not very familiar with the notion of either "common ancestor" or "patch" could you explain them please?
  • VonC
    VonC almost 8 years
    @Curious This is key in understanding why git cherry-pick is *not git merge (even if it useds merging strategy). A common ancestor means going back into the commit history. A patch is just a unified diff between two commits. There is no history there.
  • Curious
    Curious almost 8 years
    So will cherry-pick likely always generate a merge conflict in large projects with every single file that was changed?
  • VonC
    VonC almost 8 years
    @Curious In your case, a cherry-pick generates a conflict because the patch is not compatible with the destination commit (0x2), even though there is no concurrent changes (there is only one change: the first line in 0x5). Since a patch is not based on a common ancestor, but based on a diff with its direct parent commit (0x4), and said parent commit is not the destination commit (0x2, where you apply that patch), then it generates a conflict.
  • VonC
    VonC almost 8 years
    @Curious This has nothing to do with "large project", and everything to do with the "patch" nature of a git cherry-pick. If you were to git cherry-pick 0x3..0x5 onto 0x2, that would work. It would apply diff from 0x3, then 0x4, then 0x5. But only 0x5 will not work, as it is based on changes done in 0x4, which 0x2 knows nothing about.
  • torek
    torek almost 8 years
    Git still does use a merge base here, it's just that the merge base it is using is not actually helpful, and winds up contributing nothing to the process. I'm also reasonably sure that Git used to try applying the patch as a simple patch first, then fall back to the merge code if that failed, but having looked at this in more modern Git source I am not sure if it still does that. Certainly if you use git format patch ... | git am it leaves the merge attempt to a fallback.
  • Farid
    Farid almost 2 years
    To answer @Curious, he, most likely, has figured this out. But for future readers, yes in slightly big projects cherry picking will be pain in the ass for the files where cherry-picked commit is not direct "child" of the commit in the branch you are trying to cherry-pick onto
  • VonC
    VonC almost 2 years
    @Farid Good point. I have included your comment in the answer for more visibility.