Reputation: 14591
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 ...
Upvotes: 926
Views: 736088
Reputation: 493
Someone lost a lot of commits when merging a developement branch into the main branch where the successive comments contents in the main branch was exactly the same. This was discovered quite a few commits later.
I put together a script to recreate a branch that could be merged again while keeping most of the commit history. The commit 428a1758ad5 was the commit on the main branch where the development branch started from. Commit 2a28248dd was the last commit on that branch before it was lost by a "null merge" in the main branch.
The script cherry-picks each commit one by one and uses diffs to make sure that the next commit on the "redo" branch has the same contents as the reference commit.
The script can be executed again, and it will cleanup the previous run by deleting the "redo" branch.
parent=428a1758ad5
branch=redo-1
# Result of
# git rev-list --reverse --ancestry-path 428a1758ad5..2a28248dd
commits="dc8154dd623783c38cecad0b58031daf153277a3
[REDACTED]
2a28248dd4e9202d7cadfc9e91de35d29ab16173"
# The above list reduced to remove commits on "side-branches"
# (manual edit by examining
# git log --graph --decorate --pretty=oneline --abbrev-commit --all`)
# :
commits="dc8154dd623783c38cecad0b58031daf153277a3
[REDACTED]
2a28248dd4e9202d7cadfc9e91de35d29ab16173"
# Checkout the reference commit
git checkout $parent
# Remove the result from the previous run
git branch -D $branch
# Create the branch again, to retry the method
git checkout -b $branch $parent
# Check if a commit hash is a merge
is_merge () { return $(( ! $(git rev-list --no-walk --count --merges "$@") )) ; }
for c in $commits ; do
echo $c >> latest
if is_merge $c ; then
# In case it is a merge, the mainline can be 1 or 2.
# Trying one, and if there is a difference, try the other
git cherry-pick -m 2 $c
if ! git diff --quiet $c ; then
# Try using other mainline
git cherry-pick --abort
git reset --hard
git cherry-pick -m 1 $c
if ! git diff --quiet $c ; then break ; fi
fi
else
git cherry-pick $c
if ! git diff --quiet $c ; then break ; fi
fi
# Sometimes the commit message must be confirmed interactively
# but most of the time it continues automatically.
# (could probably be optimised, but it does the job)
git commit --allow-empty --no-verify
done
Upvotes: -1
Reputation: 2359
@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
, preserving the commit metadata.
git checkout -b tempZ Z
git rebase Y
git checkout -b newX X
git cherry-pick Y..tempZ
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.)
If you instead want to discard the existing commit metadata and squash everything in Y..Z
into a single commit, see @ephemerr's answer.
Upvotes: 51
Reputation: 98559
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.
Parent 1 is the "first parent", 2 is the "second parent", and so on. The order is the one in which they're listed in the commit (as viewed by git show and the like).
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.
Upvotes: 962
Reputation: 4046
If you determine you need to include the merge vs cherry-picking the related commits, you have two options:
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.
git merge
instead of git cherry-pick
.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.Upvotes: 113
Reputation: 2911
-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
.
Upvotes: 259
Reputation: 1983
Simplification of @Daira Hopwood method good for picking one single commit. Need no temporary branches.
In the case of the author:
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
Upvotes: 9