git cherry-pick says "...38c74d is a merge but no -m option was given"
Asked Answered
S

6

883

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 ...
Subdominant answered 10/2, 2012 at 14:26 Comment(1)
If all you want is to take a bunch of changes from one branch to another, w/o caring for merge history, head straight to this golden answer.Plug
Z
912

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.

Zollverein answered 10/2, 2012 at 14:34 Comment(13)
@Subdominant You should probably also learn about git rebase - it's like a merge, but instead of integrating two branches it transplants one to sit atop the other.Zollverein
how do you know the parent number?Hazelton
@Hazelton 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).Zollverein
Another tip: sometimes it's helpful to use "git merge --squash ..." if you want to lose the extra history and you want to incorporate changes from all parents, recursively.Charger
Not very clear from the cherry-pick help files is that if you want the 1st parent you should type the 2nd parent in the cherry-pick command. To emphasize the answer: "it collapses all the changes made in the parent you didn't specify to -m into that one commit".Headstock
In my case I accidentally did git reset --hard HEAD^ on a merge commit and wanted to get it back. Cherry-pick obviously failed with the subject message since it's not the right tool here. So I just edited .git/refs/heads/master to point back to the reset merge commit's SHA. All is good again.Nemhauser
@Nemhauser You could also have just done a 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.Zollverein
Am I weird for feeling chills up the spine reading about HEAD@{1}? :) Thanks for letting me know.Nemhauser
@wufoo: Git is much safer than running with scissors -- in general, you can't ever really lose anything once it has been committed (or even stashed). It may seem like history is removed/rewritten by certain commands, but you can always find it again with gitk --reflog. In my case, I was able to use git reset (sha) followed by a re-commit to setup a cherry-pick around the "merge" that this message was complaining about (which had been produced as the results of a git commit --amend).Clishmaclaver
WARNING: 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!Prorogue
@Zollverein well, it's not that helpful to know that parent 1 is the 1st parent... Are they given a number at random? Or is there some structure (probably is), like parent 1 is the parent that is being merged into (i.e. it's a branch in which git merge otherBranch was called) or something.Mateya
I don't know what version of git this advice is for, but git merge also collapses merges (2.30.1)Buzzell
usually the cherry-picking is useful in scenarios where you maintain a constant dev version and different production releases as seperate versions. and there happens an issue in some prod version, u fix it and commit in dev branch and dont want to merge with the desired production release instead just cherry-pick that particular commit.Secern
W
233

-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.

Windgall answered 9/12, 2018 at 14:50 Comment(7)
This answer stackoverflow.com/a/38669506 provides good explanations for the -m valueIckes
-m best explainedHanging
This is the best explanation i've seen about the -m, thanks a lot!Baltimore
The answer is a little ambiguous, what does it mean by "git cherry-pick E -m 1 means using D-E" ?Blowy
I recommend reading around the topic of branching for further details found here: git-scm.com/docLangill
@SumitJain I think the missing information is that here, for merge commit E, parent 1 is C and parent 2 is D, so if you specify parent 1 -m 1 (C), then the differences from the other side of the merge (D-E) will be applied. If you specify parent 2 -m 2 (D), then the differences from the other side of the merge (B-C-E) will be applied.Streetwalker
@Streetwalker Your assumption seems to be wrong. -m 1 means the first parent which is D. But the cherry-picked content is the difference to D, which is B-C.Dictaphone
P
107

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:

  1. (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.

  1. (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.
Prorogue answered 23/4, 2018 at 14:2 Comment(4)
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 --ontoOhl
What is the -m option's meaning ?Chemiluminescence
@Ohl Could you make your comment an answer with the graph you mentioned? I suspect what you're mentioning is what I'm looking for as with the merge commits that aren't mine are sneaking in to my branch.Leotie
so i have 2 branches dev1 and dev 2 i have created a branch "someChange" and merged it into dev1 now i want to merge that into dev2 in this case what should i use -1 or -2 ?Preraphaelite
B
50

@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.

  1. git checkout -b tempZ Z
  2. git rebase Y
  3. git checkout -b newX X
  4. git cherry-pick Y..tempZ
  5. (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.)

If you instead want to discard the existing commit metadata and squash everything in Y..Z into a single commit, see @ephemerr's answer.

Babs answered 2/5, 2016 at 19:3 Comment(7)
git rebase Y says Current branch tempZ is up to dateEnravish
I think that means that Y..Z was already linear. So you can ignore that message and proceed with steps 3 and 4.Babs
Interesting idea, I had to draw it on paper to fully appreciate what was going on =DEscarpment
Brilliant. git cherry-pick for a whole range complained either that the -m option was missing or that it was provided. Your solution was golden. (One suggestion: delete tempZ branch after)Khan
this is amazing! i've been struggling with cherry pick and only this made sense. I had a bit of trouble with the letters picked ( some branches and commits are capital letters and some branches are lowercase )Ro
git cherry-pick Y..Z omits Y; you need git cherry-pick Y^..Z. Otherwise excellent idea!Diffractometer
The stated premise was that you want to cherry-pick the commits Y..Z. If instead you want to cherry-pick Y^..Z, then yes, substitute Y^ for Y everywhere. (Incidentally, I think git's notation here makes perfect sense if you consider that Y..Z are precisely the commits needed to take the state as of Y to the state as of Z.)Babs
L
9

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
Lounging answered 23/5, 2018 at 6:49 Comment(1)
This of course loses the metadata associated with the original commit. I guess it's a matter of opinion whether it is simpler. I do use it sometimes when I want to lose the metadata and only keep the overall code changes. Note that it works even if Y is not an immediate parent of Z (in which case the changes will be squashed).Babs
Q
-1

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
Qulllon answered 9/2 at 1:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.