Why You Need -m 1
Cherry-picking a merge commit directly fails: it has two parents, and Git doesn’t know which one to diff against. -m 1 tells Git to “use the first parent (usually the mainline) as the base.”
| |
The most common scenario: the feature branch has already landed in main through a merge commit, and now you need the same feature on a maintained release branch, without dragging along the other commits on main.
You could cherry-pick each commit from feature one by one, but that gets tedious as the commit count grows, and any conflicts you resolved by hand during the merge won’t come along. Picking the merge commit itself gets you the feature’s complete change in one command.
Core Concept: A Merge Commit Has Two Parents
A regular commit has one parent; a merge commit has two. Parent 1 is the branch you were on when you ran the merge, and parent 2 is the branch that was merged in.
When you run git merge feature on main, the tip of main at that moment becomes parent 1 and the tip of feature becomes parent 2. Merging a PR on GitHub or GitLab with “Create a merge commit” uses the same order: the target branch comes first.
To confirm the order, look at the Merge: line in the output of git log -1 <sha>. The first hash listed is parent 1:
| |
You can also print each parent with git rev-parse <sha>^1 <sha>^2:
| |
What -m 1 and -m 2 Actually Compute
At its core, cherry-pick applies “the diff between a commit and its parent” to the current branch. -m (long form --mainline) just picks one of the two parents as the base. Whichever parent you pick, you take the changes from the other side.
| Option | Base | Diff computed | What lands on the current branch |
|---|---|---|---|
-m 1 | parent 1 (C, main side) | C → M | Everything from feature (A, B, plus any conflict resolution done during the merge) |
-m 2 | parent 2 (B, feature side) | B → M | Changes on main since feature branched off (C) |
When the job is “move an already-merged feature to another branch,” you almost always want -m 1.
Hands-On: Build a Demo Repository
You can copy and run the commands below as-is. They build the repository from the diagrams: release stays at init, feature has two commits, main has one unrelated commit, and merge commit M brings feature into main.
We’ll go in four steps and check the commit graph with git log --oneline --graph --all after each one. The output shown is real (your SHAs will differ).
Step 1: Initial commit, branch off release
| |
| |
There’s only one commit, and both main and release point to it.
Step 2: Two commits on the feature branch
| |
| |
feature has moved two commits ahead; main and release are still at init.
Step 3: An unrelated commit on main
| |
| |
History forks after init: feature’s A and B on one side, main’s C on the other.
Step 4: Merge feature into main with a merge commit
| |
--no-ff guarantees that a merge commit is created.
| |
Two lines lead into M (8865ad0): the vertical one from C (06233b4, parent 1) and the one on the right from B (d9b8793, parent 2). That’s the same order as Merge: 06233b4 d9b8793 earlier.
Run the Cherry-Pick and Verify
Switch to release and pick M with -m 1. Since main currently points at M, you can use the branch name directly:
| |
Running it and checking the result in the terminal:
| |
release got feature’s login.txt, and main’s README.md did not come along. Note that the new 20de01d is an ordinary single-parent commit (its only parent is f643d1c), not a merge commit. It just reuses M’s commit message.
Comparison: What Happens with -m 2
With -m 2 you get main’s README.md instead, and none of feature’s changes. Try it on a fresh branch from init:
| |
| |
The base is now B. B already contains login.txt, so the diff from B to M is only the README.md that C introduced.
-m 2 still has its uses. If someone first ran git merge main on feature and then fast-forwarded main to that merge, the parent order is reversed, and you’d need -m 2 to get feature’s changes.
Common Pitfalls and Best Practices
The biggest pitfall is assuming parent 1 is the mainline. Confirm the parent order before you act, and the rest is easy to handle.
- Check the order first. Use the
Merge:line fromgit log -1 <sha>to confirm which one is parent 1, especially for merges someone else made. - Add
-xto record the source.git cherry-pick -x -m 1 <sha>appends(cherry picked from commit ...)to the commit message, so you can find where a change came from later. - Handle conflicts as usual. Fix the conflicted files,
git addthem, then rungit cherry-pick --continue. To give up,git cherry-pick --abortputs the branch back where it was. - Watch out for later merges. Cherry-pick creates new SHAs, and Git doesn’t remember that these changes are already in
release. If you later mergefeatureormainintorelease, identical changes usually merge cleanly, but anythingfeaturechanged afterwards may conflict. - Reverting works differently. A cherry-picked commit is a regular commit, so
git revert <new sha>is enough. Reverting the original merge commit M needsgit revert -m 1 Mtoo. - You can apply
-m 1to a whole batch. Since Git 2.21,-malso works on regular commits, so a range that includes merge commits can be picked with one command.
With -x, the last line of the commit message records the full SHA of the source merge commit:
| |
| |
Cheat Sheet
The one thing to remember: -m N uses the Nth parent as the base and takes the changes from the other side. To move a merged feature, use -m 1.
| Command | What it does |
|---|---|
git cherry-pick -m 1 <M> | Apply M’s diff against parent 1 (the merged-in branch’s changes) to the current branch |
git cherry-pick -m 2 <M> | Apply M’s diff against parent 2 (the mainline side’s changes) to the current branch |
git cherry-pick -x -m 1 <M> | Same as above, and record the source SHA in the commit message |
git log -1 <M> | Show the Merge: line to confirm the parent order |
git rev-parse <M>^1 <M>^2 | Print parent 1 and parent 2 |
git cherry-pick --continue | Continue after resolving conflicts and running git add |
git cherry-pick --abort | Abandon this cherry-pick and return to the previous state |
git revert -m 1 <M> | Revert everything the merge brought in |