Learning Objectives
By the end of this lesson you will be able to:
- Pick a contiguous range of commits with the correct boundary syntax
- Resolve a conflict during a pick and continue the sequence
- Choose between
--continue,--skip,--abortand--quit - Recognise why a pick conflicts even though the merge would not have
Picking a Range
One commit is git cherry-pick A. Several adjacent commits use range syntax:
git cherry-pick 8f3c2a1..4d9e7b2
The lower bound is exclusive. That range means "everything after 8f3c2a1, up to and including 4d9e7b2". 8f3c2a1 itself is not picked, which surprises almost everybody once.
To include it, use the three-dot-free ^ form:
git cherry-pick 8f3c2a1^..4d9e7b2
8f3c2a1^ is the parent of 8f3c2a1, so starting exclusive from the parent means starting inclusive from the commit itself.
Check before you run it, always:
git log --oneline 8f3c2a1^..4d9e7b2
Whatever that prints is exactly what will be applied, in that order — oldest first, like the rebase todo list.
-x works on ranges too, and annotates each resulting commit individually:
git cherry-pick -x 8f3c2a1^..4d9e7b2
Why a Pick Conflicts
This is worth understanding rather than memorising, because it explains why picks conflict more often than merges.
A merge has three inputs: your branch, their branch, and the common ancestor. That ancestor lets Git tell "you changed this line" apart from "they changed this line".
A cherry-pick has no such shared ancestor to reason from — the source and target branches diverged long ago. Git falls back to using the picked commit's parent as the base, which is a much weaker approximation. When the target branch has drifted, that approximation fails and you get a conflict on code that a merge would have handled silently.
Nothing is wrong when this happens. It is the normal cost of moving a change between branches that have grown apart.
Resolving It
The stop looks like this:
error: could not apply 4d9e7b2... fix: clamp refund amount
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run "git cherry-pick --continue".
The loop is the one you already know:
# 1. Edit the file, remove the conflict markers, keep the right content
# 2. Stage it
git add src/payments/refund.ts
# 3. Continue
git cherry-pick --continue
Git commits the pick and moves to the next commit in the range, if there is one.
As during a rebase, --continue writes the commit — do not git commit yourself.
The "ours"/"theirs" labels here are more intuitive than in a rebase: ours is your target branch and theirs is the commit being picked. That is the sense most people expect.
The Four Exits
| Command | Effect |
|---|---|
--continue |
Commit the resolution and proceed to the next commit in the range |
--skip |
Discard the current commit and proceed to the next |
--abort |
Cancel everything, including commits already picked in this range |
--quit |
Stop, but keep the commits already picked |
--quit is the one that does not exist in rebase, and it is genuinely useful. Picking a range of six, you find the fourth one is not what you wanted: --quit keeps the three that landed cleanly and leaves you on your branch, without unwinding good work.
--abort is still the right answer when you are lost. It restores the branch to before the whole operation, and it always works.
Conflict Resolution Is a Judgement Call
Two specific things to check when resolving a picked conflict, both of which matter more here than in a routine merge:
Does the surrounding code still make sense? A fix written against a function signature that changed in the meantime can be resolved into something that compiles and is wrong. Read the whole function, not just the conflict hunk.
Is the fix still needed at all? Occasionally you find the target branch already solved the problem differently. Then the pick is unnecessary — --abort, and say so, rather than resolving a conflict into a duplicate fix.
Pro Tip: When a picked conflict is not obvious, stop and ask the commit's author. They resolved this same code in their head a week ago and can tell you in thirty seconds what the merged version should look like. A silently mis-resolved backport is a fix that ships and does not work.
Key Takeaways
A..BexcludesA; useA^..Bto include it- Preview any range with
git log --oneline <range>before picking it -xannotates every commit in a range- Picks conflict more than merges because there is no common ancestor to reason from
- Resolve,
git add,git cherry-pick --continue— never commit by hand --quitkeeps the commits already picked;--abortunwinds them all- Read the whole function when resolving, and check whether the fix is still needed