Home > Back-end >  How to grab a commit from another branch without a merge?
How to grab a commit from another branch without a merge?

Time:09-27

At face value, this seems like a textbook use case for cherry pick. Googling around, I find statements like:

Cherry picking in Git means to choose a commit from one branch and apply it onto another.

The command git cherry-pick commit applies the changes introduced by the named commit on the current branch.

So I go and try it... and half my file turns into merge conflicts (a regular merge turns a further 8 other files into merge conflicts, so that's not useful either). The commit in question only changes ~5 lines or so, and not half the file.

For context, the 2 branches look roughly like:

  1. P -> [20 commits] -> A -> B = other_branch
  2. P -> [30 unrelated commits] -> C = my_branch

The only thing commit B does is to change ~5 lines in foo.py I want to cherry pick those changes into commit C, but apparently cherry pick hates me.

I found these other related questions:

What use case is git cherry-pick useful for?

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

They suggest that the above quotes are actually lies, and cherry pick is actually a fancy 3 way merge? I think one of them (or another question) offers the following solution:

git show commitB > patch.txt
git apply patch.txt

I also tried

git diff commitA commitB | git apply

but all I get is

error: patch failed: foo.py:20
error: foo.py: patch does not apply

Is manual copy pasta my only solution?

On a related note, what/when is git cherry pick good for? I can't think of any good use cases. If 2 branches differ slightly, a plain old merge does almost the same thing. If 2 branches differ greatly (my case), cherry pick just flops.

CodePudding user response:

Cherry-pick cannot simply "copy a commit". A commit in Git references a tree and a tree is a full snapshot of your complete project's files (and implicitly, directories).

When you perform a cherry-pick, Git will first compute the "changes of this commit" (basically running git diff thatcommit thatcommit^) and then try to re-apply those changes to your current HEAD. Usually, this works well enough; given that the files changed in the picked-from commit have not diverged too much from your current HEAD commit. If the same lines (or nearby lines) have changed in both branches, Git cannot automatically merge (or cherry-pick) the changes. Simply because it cannot detect which are the "correct" changes.

So when you say that the file has only 5 lines changed, then the cherry-pick should apply cleanly. Unless of course, more lines have changed, e.g. line-endings were normalized in one branch, but not the other (command line flags help with that).

To quickly find out how much your file has diverged on both branches, run diff: git diff branchA branchB -- foo.py.

If more lines have changed (and from your description, it sounds like that), you are probably better off re-applying those changes manually. But since you claim that only a few lines have changed, that shouldn't be that difficult.

CodePudding user response:

Why you are getting conflicts :

suppose you are in the following situation :

--*--*--*--*--*--H <- HEAD, mybranch (your current state)
   \
    *--*--x--y--* <- some/otherbranch
             ^
       (the commit you want to cherry pick)

If you run git cherry-pick <y>, git will try to reconcile :

  • the changes introduced by the commit, that is : the diff x -> y
  • with changes on your branch : the diff between x -> H

So : if the patch says "there is a change at line 22", and your branch also introduce changes on line 22 (when compared to x), you are bound to have conflicts.

(actually, it's even if you have changes near line 22, you will have conflicts)


How to fix conflicts :

A decent enough way to fix conflicts is to edit them in a 3-way diff viewer, such as kdiff3 or winmerge or meld or ...

You can ask git to open the 3 way diff merge tool for you, by running git mergetool :

git mergetool
# if you want to select a specific mergetool:
git mergetool --tool=vimdiff

If the commit you chose only introduced a few changes, fixing the conflicts will look a lot like :

  • mainly take the content from current commit ("ours" side)
  • include the few changes inroduced in commit y

In the end : you get to choose what content gets committed as the "correct" version for including the changes from commit y.

  • Related