I made some changes in my master branch and want to bring those upstream. When I cherry-pick the following commits. However, I get stuck on fd9f578 where git says:
$ git cherry-pick fd9f578
fatal: Commit fd9f57850f6b94b7906e5bbe51a0d75bf638c74d is a merge but no -m option was given.
What is git trying to tell me and is cherry-pick the right thing to be using here? The master branch does include changes to files which have been modified in the upstream branch, so I'm sure there will be some merge conflicts but those aren't too bad to straighten out. I know which changes are needed where.
These are the commits I want to bring upstream.
e7d4cff added some comments...
23e6d2a moved static strings...
44cc65a incorporated test ...
40b83d5 whoops delete whitspace...
24f8a50 implemented global.c...
43651c3 cleaned up ...
068b2fe cleaned up version.c ...
fd9f578 Merge branch 'master' of ssh://extgit/git/sessions_common
4172caa cleaned up comments in sessions.c ...
The way a cherry-pick works is by taking the diff a changeset represents (the difference between the working tree at that point and the working tree of its parent), and applying it to your current branch.
So, if a commit has two or more parents, it also represents two or more diffs - which one should be applied?
You're trying to cherry pick fd9f578
, which was a merge with two parents. So you need to tell the cherry-pick command which one against which the diff should be calculated, by using the -m
option. For example, git cherry-pick -m 1 fd9f578
to use parent 1 as the base.
I can't say for sure for your particular situation, but using git merge
instead of git cherry-pick
is generally advisable. When you cherry-pick a merge commit, it collapses all the changes made in the parent you didn't specify to -m
into that one commit. You lose all their history, and glom together all their diffs. Your call.
-m
means the parent number.
From the git doc:
Usually you cannot cherry-pick a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows cherry-pick to replay the change relative to the specified parent.
For example, if your commit tree is like below:
- A - D - E - F - master
\ /
B - C branch one
then git cherry-pick E
will produce the issue you faced.
git cherry-pick E -m 1
means using D-E
, while git cherry-pick E -m 2
means using B-C-E
.
-m
value
-m
best explained
Simplify. Cherry-pick the commits. Don't cherry-pick the merge.
If you determine you need to include the merge vs cherry-picking the related commits, you have two options:
(More complicated and obscure; also discards history) you can indicate which parent should apply.
Use the -m option to do so. For example, git cherry-pick -m 1 fd9f578 will use the first parent listed in the merge as the base.
Also consider that when you cherry-pick a merge commit, it collapses all the changes made in the parent you didn't specify to -m into that one commit. You lose all their history, and glom together all their diffs. Your call.
(Simpler and more familiar; preserves history) you can use git merge instead of git cherry-pick.
As is usual with git merge, it will attempt to apply all commits that exist on the branch you are merging, and list them individually in your git log.
git merge
is not an alternative to cherry-pick because it will also merge commits that merged branch where based on. I've actually had a neat graph here, but stack-overflow does not allow formatting in the comments. Imagine you have separate release branch R, dev branch D and feature branches F and G. F is merged into D and then G starts and is merged into D. Business decides that only changes from G should go in and you want these changes into release branch R, you can either cherry pick the merge commit or rebase --onto
branch G on R. The alternative is git rebase --onto
@Borealid's answer is correct, but suppose that you don't care about preserving the exact merging history of a branch and just want to cherry-pick a linearized version of it. Here's an easy and safe way to do that:
Starting state: you are on branch X
, and you want to cherry-pick the commits Y..Z
.
git checkout -b tempZ Z git rebase Y git checkout -b newX X git cherry-pick Y..tempZ (optional) git branch -D tempZ
What this does is to create a branch tempZ
based on Z
, but with the history from Y
onward linearized, and then cherry-pick that onto a copy of X
called newX
. (It's safer to do this on a new branch rather than to mutate X
.) Of course there might be conflicts in step 4, which you'll have to resolve in the usual way (cherry-pick
works very much like rebase
in that respect). Finally it deletes the temporary tempZ
branch.
If step 2 gives the message "Current branch tempZ is up to date", then Y..Z
was already linear, so just ignore that message and proceed with steps 3 onward.
Then review newX
and see whether that did what you wanted.
(Note: this is not the same as a simple git rebase X
when on branch Z
, because it doesn't depend in any way on the relationship between X
and Y
; there may be commits between the common ancestor and Y
that you didn't want.)
git rebase Y
says Current branch tempZ is up to date
Y..Z
was already linear. So you can ignore that message and proceed with steps 3 and 4.
Simplification of @Daira Hopwood method good for picking one single commit. Need no temporary branches.
In the case of the author:
Z is wanted commit (fd9f578)
Y is commit before it
X current working branch
then do:
git checkout Z # move HEAD to wanted commit
git reset Y # have Z as changes in working tree
git stash # save Z in stash
git checkout X # return to working branch
git stash pop # apply Z to current branch
git commit -a # do commit
Success story sharing
git rebase
- it's like a merge, but instead of integrating two branches it transplants one to sit atop the other.git show
and the like).git reset --hard HEAD@{1}
to get back your missing commit.git reset
isn't restricted to moving "backwards" in the history.git checkout -b mybranch HEAD@{1}
would also work.git merge
may have unintended consequences. That command will add all other (older) commits that exist on the parent branch. Usually people choose to cherry-pick because they don't want the other commits. Be sure you double-check that you are implementing only the changes you want!