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

2

17

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?

Transcript answered 1/3, 2017 at 11:22 Comment(2)
I'm seeing this also, first time ever after years of using git on Windows. Tried latest git for Windows 64-bit 2.13.0.windows.1 and an older one 2.10.x. git cherry-pick does not give me a merge conflict, but the modified file has changes it should not have and I get the same using a patch and git apply.Reprise
I have created a repo which may help clarify: github.com/ianmiell/cherry-pick-mysteryMervin
P
13

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.

Postdate answered 1/3, 2017 at 21:27 Comment(3)
I'm seeing the same kinds of conflicts. Your explanation is very educational, so thank you. However, it doesn't provide a solution. A git patch built for P-C applied to @ fails. So, the follow-up question becomes: How can we make git apply only the P-C changes without the o-I-P changes?Insobriety
@Suncat2000: there's no guarantee that any of this can be automated. However, if you git show commit C, so that Git presents P-vs-C as a diff, and then use git apply to apply that diff to commit @, that may, in some cases, work better than git cherry-pick C. You can choose to apply with --reject or with --3way if the patch doesn't apply cleanly at first, though --3way makes it much more like cherry-picking: you're likely to end up with the same conflicts (minus any recognition of renamed files).Postdate
If you do use any of these approaches, it's important that you inspect and/or test the result carefully. git diff ... | git apply can blindly put, e.g., closing braces in the wrong places, for languages that use { ... } grouping constructs.Postdate
B
3

I see that several other lines are also introduced to the file, which were added to master branch with different commits

Make sure you don't have undesired changes on that commit.

$ git show <commit-sha>

Cherry-pick the commit.

$ git checkout <release-branch>
$ git cherry-pick <commit-sha>

Better resolve the conflicts manually.

Or, If you want to keep the commit's changes then accept theirs otherwise accept ours (keep the release branch changes)

$ git checkout --theirs -- .
Or,
$ git checkout --ours -- .

$ git status                     # see the changes
$ git add .
$ git commit -m 'Fix conflicts'
$ git push origin HEAD

Alternative: This solution would be effective if you want to take few file changes from the target commit to release branch.

The cherry picked commit only introduced couple of lines to the conflicted file

$ git checkout <release-branch>
$ git checkout <commit-sha> <file> .      # change the <file> to commit's version

$ git add .
$ git commit -m 'Fix the bug'
$ git push origin HEAD
Bodine answered 1/3, 2017 at 11:56 Comment(3)
I exactly did what you mentioned. The commit I cherry picked didn't have any undesired changes by itself, but the file changed by that commit does have some undesired changes (introduced by other previous commits on master branch). So, I don't understand why these changes are introduced on release branch. Also, your alternative is not suitable for me since taking the whole file from master branch is not going to work for me due to the mentioned undesired changes.Transcript
"(introduced by other previous commits on master branch)" it does not go with git nature! While you cherry-pick only that commit's changes should come. --- Alternative solution doesn't take the master's changes. git checkout <commit-sha> <file> = commit's file changes != master's HEAD file changes. It should be the same result as cherry-pick to the file.Bodine
Actually I was also surprised to see this behavior, since I have done cherry picking many times but have never seen something like this. I tried your alternative solution, and what it does is checking out the file at the given commit hash. It doesn't checkout just the change introduced by the given commit, but the complete file at the given commit hash. Since other previous commits on master branch has done undesired changes in the file, this is not what I want.Transcript

© 2022 - 2024 — McMap. All rights reserved.