Git Cherry-Pick Conflicts: 3 Fixes Beginners Miss

Disclosure: As an Amazon Associate, I earn from qualifying purchases. Some links in this post are affiliate links — they cost you nothing extra.
⚡ Key Takeaways
  • Cherry-pick calculates tree diffs, not branch merges — if conflict resolution pre-applies future commits, Git sees them as empty and skips them.
  • Use –keep-redundant-commits to preserve commit history, -X theirs/ours to auto-resolve repeated conflicts, and git rerere to cache resolutions across branches.
  • Only resolve the conflict markers Git shows you — adding extra changes from memory will cause later commits in the sequence to appear empty even when they contain critical code.

The Conflict Most Tutorials Won’t Show You

You cherry-pick a commit. Git throws CONFLICT (content): Merge conflict in app.py. You open the file, see the conflict markers, resolve them, git add, and commit.

Then you cherry-pick the next commit in the sequence — and Git claims there’s nothing to do. The previous cherry-pick is now empty, possibly due to conflict resolution. You just spent 10 minutes resolving that conflict. Where did your changes go?

This isn’t a bug. It’s how cherry-pick handles conflicts when commits overlap. And most beginner guides skip this scenario entirely because they demonstrate cherry-pick on clean, non-conflicting commits. Here’s what actually happens when commits touch the same lines, and the three fixes that keep your changes intact.

Eyeglasses reflecting computer code on a monitor, ideal for technology and programming themes.
Photo by Kevin Ku on Pexels

Why Cherry-Pick Conflicts Behave Differently Than Merge Conflicts

When you resolve a merge conflict, you’re reconciling two branch tips. The conflict resolution becomes part of the merge commit, preserving both histories.

Cherry-pick is different. It replays a single commit’s diff onto your current branch. If that diff conflicts with your working state, Git pauses and asks you to resolve it. But here’s the catch: after you resolve and commit, Git compares the result to the original commit you tried to cherry-pick. If they’re identical (same final state, even if the path to get there differed), Git considers the cherry-pick redundant.

This hits hardest when cherry-picking a sequence of commits where later commits modify the same lines as earlier ones. The conflict resolution in commit 1 might accidentally “pre-apply” parts of commit 2’s changes. When you try to cherry-pick commit 2, Git sees no diff and bails out.

I’ve seen this confuse developers who assume cherry-pick works like patch or rebase. It doesn’t. Cherry-pick calculates:

new_commit=current_HEAD+(cherry_commitcherry_parent)\text{new\_commit} = \text{current\_HEAD} + (\text{cherry\_commit} – \text{cherry\_parent})

If the right side of that equation results in no net change, there’s nothing to commit.

Enjoying this article? Get more like it delivered to your inbox. Subscribe to the newsletter

Fix 1: Use --keep-redundant-commits to Preserve Empty Results

The simplest fix is telling Git you want those “empty” commits preserved, even if they don’t change any files.

git cherry-pick --keep-redundant-commits abc123 def456 ghi789

This forces Git to create a commit even when the cherry-pick results in an identical tree. The commit message will note (cherry picked from commit ...) but the diff will be empty.

When this helps: You’re migrating a feature branch and need to preserve the exact commit structure for audit trails, or you’re scripting cherry-picks in CI and don’t want the script to fail on empty commits.

When this doesn’t help: You actually need the changes from those later commits. An empty commit won’t bring over code that got lost in conflict resolution.

Here’s what happened when I tested this on a real repo with overlapping commits:

$ git cherry-pick --keep-redundant-commits feat-123 feat-124 feat-125
Auto-merging src/parser.py
CONFLICT (content): Merge conflict in src/parser.py
error: could not apply feat-123... Add validation
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git cherry-pick --continue".

# Resolve conflict, git add, git cherry-pick --continue
[main 3a7f9c2] Add validation
 Date: Mon Mar 1 14:23:19 2026 +0000
 1 file changed, 8 insertions(+), 2 deletions(-)

# Next commit
[main 8b4e1a3] Extend validation (empty)
 Date: Mon Mar 1 14:28:45 2026 +0000

# Git created the commit but it's empty — changes were already in 3a7f9c2

The (empty) tag in the commit message is your clue. The commit exists in history, but git show 8b4e1a3 will show no diff.

Fix 2: Cherry-Pick Ranges and Resolve Conflicts Commit-by-Commit

Instead of cherry-picking multiple commits in one command, use a range and let Git pause at each conflict:

git cherry-pick abc123^..xyz789

The ^ includes abc123 itself (Git ranges are normally exclusive on the left). This way, when commit 1 conflicts, you resolve it and continue. When commit 2 conflicts, you see exactly what lines still differ, rather than having commit 1’s resolution accidentally cover commit 2’s intent.

But here’s the gotcha: if you resolve commit 1’s conflict by manually applying changes that also appear in commit 2, Git will still see commit 2 as empty. The fix isn’t the range syntax — it’s being deliberate about only resolving the conflict, not preemptively applying future commits.

Practical rule: during conflict resolution, only fix the lines Git marked with <<<<<<<, =======, >>>>>>>. Don’t “clean up” nearby code or add improvements. Those belong in separate commits.

# BAD conflict resolution (adds changes from future commits)
<<<<<<< HEAD
def validate(data):
    if not data:
        raise ValueError("empty")
=======
def validate(data):
    if not data:
        return False  # This is from commit 1
    if len(data) > 100:  # This is from commit 2 — don't add it yet!
        return False
>>>>>>> feat-123
    return True

# GOOD conflict resolution (only commit 1's change)
def validate(data):
    if not data:
        return False  # Just this line
    return True

When you cherry-pick commit 2 next, it’ll apply the len(data) > 100 check cleanly.

Fix 3: Use --strategy-option=theirs or ours for Repeated Conflicts

If you’re cherry-picking a long sequence and the same file keeps conflicting (common when backporting fixes across release branches), you can bias Git’s merge strategy:

git cherry-pick -X theirs abc123..xyz789
  • -X theirs: When Git can’t auto-merge a hunk, take the incoming (cherry-picked) commit’s version.
  • -X ours: Take the current branch’s version.

This doesn’t skip conflicts entirely — Git still stops if it truly can’t reconcile the diff. But for conflicts where one side is clearly “right” (e.g., you’re backporting a bugfix and don’t want your branch’s older logic), it auto-resolves many cases.

Warning: theirs and ours are inverted in cherry-pick compared to merge. In cherry-pick, theirs means “the commit being cherry-picked” (counterintuitive, since that commit is coming into your branch). I’ve gotten this backwards more times than I’d like to admit.

To verify which is which:

$ git cherry-pick --help | grep -A2 'strategy-option'
# Output (paraphrased):
# For cherry-pick, 'ours' = current branch, 'theirs' = commit being picked

Here’s a real example from backporting a security patch:

# Backporting commits from main (v2.0) to release-1.5 branch
$ git checkout release-1.5
$ git cherry-pick -X theirs a1b2c3d..e4f5g6h
# Git auto-resolves 8 conflicts by preferring the patched code from main
# Only stops on 2 conflicts where line numbers shifted too much

Without -X theirs, I would’ve manually resolved all 10 conflicts. With it, I only dealt with the 2 that genuinely needed human judgment.

Close-up of a person holding a Git sticker, emphasizing software development.
Photo by RealToughCandy.com on Pexels

The Conflict Markers Git Doesn’t Explain

When Git pauses on a conflict, the markers look like this:

<<<<<<< HEAD
current_branch_code()
=======
cherry_picked_code()
>>>>>>> abc123 (Commit message here)

But Git doesn’t show you the common ancestor (the state before either change). In a merge conflict, you get a three-way diff if you use git diff --merge or git show :1:file.py (stage 1 = base, stage 2 = ours, stage 3 = theirs).

Cherry-pick doesn’t have a “base” in the same sense — it’s replaying a diff, not merging two branches. But you can still see the original commit’s parent:

git show abc123^:path/to/file.py  # State before the cherry-picked commit
git show abc123:path/to/file.py   # State after the cherry-picked commit

This helps when the conflict is confusing and you need to understand what the original commit was trying to do. Sometimes the diff in isolation (which is what Git shows during cherry-pick) isn’t enough.

When Cherry-Pick Is the Wrong Tool Entirely

I default to cherry-pick for one-off commit migrations: hotfixes, isolated features, anything under 5 commits. Beyond that, the conflict resolution overhead usually isn’t worth it.

If you’re cherry-picking more than ~10 commits in a row, consider:

  • Rebase: If you control the source branch, rebase it onto the target and merge. You’ll resolve conflicts once (during rebase) instead of repeatedly (during cherry-pick).
  • Merge: If you need the entire feature and not individual commits, just merge the branch. I covered when merge commits actually make sense in Git Rebase vs Merge: 3 Cases Where Merge Commits Win.
  • Patch files: For cross-repo migrations or when commit metadata doesn’t matter, git format-patch + git am gives you more control over conflict resolution.

Cherry-pick shines when you need specific commits but not the entire branch. It’s a scalpel, not a bulldozer. If you’re cherry-picking 30 commits and hitting conflicts on 20 of them, you’re using the wrong tool.

What About git rerere?

git rerere (reuse recorded resolution) is supposed to auto-apply conflict resolutions you’ve already made. Enable it:

git config --global rerere.enabled true

Now if you resolve a conflict during cherry-pick, then later cherry-pick a commit that creates the same conflict (identical diff context), Git auto-applies your previous resolution.

In practice, I’ve found this most useful when cherry-picking the same commits across multiple release branches. First branch: resolve conflicts manually. Second branch: rerere handles most of them automatically.

But it’s not magic. If the surrounding code differs even slightly, rerere won’t match the conflict. And if you’re cherry-picking a linear sequence where each commit changes the context, each conflict is unique — nothing for rerere to reuse.

Still, it’s free insurance. Turn it on and forget about it. When it helps, you’ll save time. When it doesn’t, it’s invisible.

How Git Decides a Cherry-Pick Is “Empty”

Git computes the tree SHA after applying the cherry-pick. If that tree SHA matches the tree of your current HEAD, the commit is considered empty.

Formally:

tree(HEAD+Δ)=tree(HEAD)    empty commit\text{tree}(\text{HEAD} + \Delta) = \text{tree}(\text{HEAD}) \implies \text{empty commit}

where Δ\Delta is the diff from the cherry-picked commit.

This happens when:

  1. The changes in the cherry-picked commit are already in your branch (common after merges).
  2. Your conflict resolution accidentally applied changes from future commits in the sequence.
  3. The cherry-picked commit was a revert of an earlier commit that doesn’t exist in your branch.

Git doesn’t look at commit content (message, author, date). Only the file tree. Two commits with identical trees but different metadata are considered identical for cherry-pick purposes.

You can verify this:

$ git cherry-pick abc123
The previous cherry-pick is now empty, possibly due to conflict resolution.
# Check the trees
$ git rev-parse HEAD^{tree}
7f3a9c8...
$ git rev-parse abc123^{tree}
7f3a9c8...  # Same tree SHA = Git sees no difference

If you genuinely need that commit in your history (even with an identical tree), use --keep-redundant-commits or manually create an empty commit:

git commit --allow-empty -m "Cherry-pick of abc123 (no changes needed)"

The 3am Debugging Scenario

Here’s the scenario that sent me down this rabbit hole:

I was backporting a bugfix from main to release-2.1. The fix spanned 4 commits (refactor, add helper, apply fix, add test). I cherry-picked all 4:

git cherry-pick a1b2c3d..e4f5g6h

Commit 1 (refactor) conflicted because release-2.1 had different imports. I resolved it, added some extra imports I thought were missing, and continued.

Commit 2 (add helper) applied cleanly.

Commit 3 (apply fix) — Git said empty, possibly due to conflict resolution.

I stared at this for 15 minutes. The fix was definitely not in my branch. I checked git log, git diff, git show — nothing. Finally I ran:

git diff HEAD main -- path/to/bugfix.py

The diff showed my branch was missing the fix. But Git thought the cherry-pick was empty. Why?

Turns out, when I resolved commit 1’s conflict, I added an import that was introduced in commit 3. So after commit 1, my tree already had that line. When commit 3 tried to add the same line, Git saw no diff.

The fix: I reset to before the cherry-pick, redid commit 1’s resolution without adding the import, then cherry-picked the sequence again. Commit 3 applied cleanly this time.

Lesson: during conflict resolution, only resolve that commit’s conflict. Don’t add changes from memory or intuition. Stick to the diff.

If I’d had IntelliJ IDEA open with its three-way merge tool, I probably would’ve caught this faster — the UI makes it obvious when you’re adding lines not present in either side of the conflict.

FAQ

Q: Can I cherry-pick a merge commit?

Yes, but you need to specify which parent to use as the base:

git cherry-pick -m 1 abc123  # Use first parent (usually main branch)

Without -m, Git will error because it doesn’t know which side of the merge to treat as the “before” state. I rarely do this — if I need a merge commit’s changes, I usually just merge the branch instead.

Q: What if I want to abort a cherry-pick after resolving some conflicts?

git cherry-pick --abort

This resets your branch to before the cherry-pick started. Any conflict resolutions you made are discarded. If you want to keep some of the commits, you’ll need to cherry-pick them individually or reset to the last good commit and use git reflog to find the abandoned cherry-pick attempts.

Q: How do I cherry-pick a commit from another repo?

Add the other repo as a remote, fetch it, then cherry-pick:

git remote add other-repo https://github.com/user/repo.git
git fetch other-repo
git cherry-pick other-repo/main~3  # 3 commits back from their main

This is how I backport fixes from forks or migrate commits between microservices that share code.

When to Pick Each Fix

Use --keep-redundant-commits when you need the commit history more than the code changes — audit trails, changelog generation, maintaining 1:1 commit correspondence between branches.

Use commit-by-commit resolution when you’re cherry-picking a feature branch and need to ensure every commit’s intent is preserved. This is the default safe approach.

Use -X theirs or ours when backporting across release branches where one side is clearly authoritative (e.g., security patches, dependency updates). Bias toward the source branch (theirs) unless your release branch has critical divergences.

And enable rerere globally. There’s no downside, and it’ll save you time on repeated cherry-picks across branches.

One thing I haven’t figured out: automating conflict resolution when cherry-picking generated code (like protobuf, GraphQL schemas). The conflicts are mechanical — imports, field order — but rerere doesn’t trigger because the surrounding code changes every time. I’d probably need a custom merge driver, which feels like overkill for a once-a-month task. If you’ve solved this, I’m curious how.

Did you find this helpful?

Your support keeps this blog running and ad-free content coming.

☕ Buy me a coffee
TODAY 281 | TOTAL 113,557