Issue with cherry pick: changes from previous commits are also applied

Lahiru Chandima picture Lahiru Chandima · Mar 1, 2017 · Viewed 8.8k times · Source

In my project, I released a version several months ago. After that release, I have done many changes on the master branch.

If I encounter some bugs which were there in the last release, I fix them on the master branch, then cherry pick them to the branch I have created at the last release. Then I can provide a new release with only the bug fix, without releasing the unfinished work on master branch.

When I tried to cherry pick a certain bug fix to the release branch, I encountered a merge conflict.

As I understand, cherry picking a certain commit introduces a new commit to the target branch, with the changes done in the cherry picked commit.

But, when I tried to fix the merge conflict, it seems that git has applied changes on master branch which are not introduced by the cherry picked commit to my release branch. The cherry picked commit only introduced couple of lines to the conflicted file. But, when I try to resolve the conflicts, I see that several other lines are also introduced to the file, which were added to master branch with different commits.

Can someone explain why changes from commits other than cherry picked commit are introduced to my release branch?

Answer

torek picture torek · Mar 1, 2017

As I understand, cherry picking a certain commit introduces a new commit to the target branch, with the changes done in the cherry picked commit.

That is correct. However, these changes may not apply cleanly, in which case:

... I encountered a merge conflict.

At this point, git cherry-pick is doing a three-way merge, which requires choosing a merge base.

Which files or commits are used as the merge base depends in part on your Git version, as I recall. Modern Git, if you run git cherry-pick, uses the cherry-picked commit's parent as the merge base, but at least one form (using git am or git apply with the --3way option, which git rebase can still do) can pick out a much earlier version of a file using the index lines of git diff output. In the end, this probably should not matter too much.

In any case, the merge will, in effect, run git diff from the base commit to each of the two "tips" (the cherry-picked commit, and the HEAD commit to which you are attempting to apply the cherry-pick). To see, visually, what's going on, you should—as usual—start by drawing the commit graph. (I don't have your repository, so I will draw a different graph and hope it's close enough. But you should draw your own—or have Git do it, or use gitk or some such.)

          o--@         <-- branch (HEAD)
         /
...--o--o
         \
          I--P--C--o   <-- otherbranch

I have given single-letter names to various commits here: C is the commit we are about to cherry pick, and P is its parent. I marked our current (HEAD) commit as @ here, though all the real work will happen in the index and work-tree. (Fortunately git cherry-pick requires that the index and work-tree be "clean", unless you are using -n to assemble multiple cherries into one big pick, so the index and work-tree will match commit @ anyway.) And, I marked commit I as "important".

Now, consider what happens if we do a three way merge with P as the base, C as one of the commits, and @ as the other commit. When we compute instructions for changing P into C, we get the diff we want to apply: that's quite straightforward. But when we compute instructions for changing P into @, well, ugh.

We made some important changes in I. Those changes are part of P, i.e., they're in our merge base. But they are not in @. The implication for merging is that Git will think of those important changes as things we are trying to un-do. And in fact, they are! We did not cherry-pick I itself, so we must undo these I changes in order to apply the changes from C.

Wherever those I changes we are "undoing" aren't in @ to start with and don't affect any of the changes from C, we're fine: they're already undone. If, by some chance or purpose, one of those I changes is in @ (perhaps through @'s parent), those aren't even in the set we're trying to undo in the first place, so again we're fine. It's when those changes conflict with, or even just abut into (the context of), the P-to-C changes, that we have problems.

In this case, Git will show, in one of the two merge conflict areas, some I changes. Those are in the part we're trying to cherry-pick. They are not necessarily being applied, they are just part of the conflict we must resolve. And if you set merge.conflictStyle to diff3—something I generally recommend—the I changes will be shown as part of the merge base, because the merge base is commit P, which is itself based on I (i.e., P's snapshot includes the code from I except where we changed it while making P).

So, it's not entirely clear to me what you are asking about, but it is normal to see, in the merge conflict area, changes unrelated to the bits you are cherry-picking.