Ch. 16 · Git & CI/CD

Git Cherry-Pick and Patch-Based Backports

Backport a fix to a release branch with git cherry-pick -x, handle the conflict when it depends on an earlier commit, and spot duplicate patches.

~6 min readbeginnerupdated Oct 7, 2026

You’ve fixed a bug on main, and customers on version 1.4 need the fix too. You don’t want to merge main into release/1.4, because that would drag in every half-built 1.5 feature. You want that one fix. git cherry-pick takes the change a commit introduced and applies it on top of your current branch as a new commit.

Interviewers like this command because the one-line description hides two things. A cherry-pick copies a change, it doesn’t move a commit, so you end up with two commits that do the same thing. And a commit’s diff often only makes sense on top of the commits before it, which is where backports get messy.

What cherry-pick actually copies

A commit in Git stores a snapshot, not a diff. When you cherry-pick commit C, Git computes the difference between C and its parent, then applies that difference to your HEAD using the same three-way merge machinery as git merge. The result is a brand-new commit with the same message and author, but a different parent, a different committer timestamp, and therefore a different hash.

That’s the first misconception to clear up: “cherry-pick moves the commit to another branch.” It doesn’t. The original stays on main. You now have a copy on release/1.4, and Git has no built-in link between the two.

A backport, step by step

Here’s the history on main in a small demo repository. The release branch was cut at e520e51:

$ git log --oneline main
ba5a4dc Round discounted price to cents
8975cce Start 1.5 development
5b4dc1e Clamp discount percent to 0-100
e520e51 Add discount
Text

A customer on 1.4 reports prices like 17.991 after a 10% discount. The fix is ba5a4dc. Switch to the release branch and pick it:

$ git switch release/1.4
$ git cherry-pick -x ba5a4dc
Auto-merging discount.js
CONFLICT (content): Merge conflict in discount.js
error: could not apply ba5a4dc... Round discounted price to cents
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
Text

The conflict is more informative than it looks:

export function applyDiscount(price, percent) {
<<<<<<< HEAD
  return price - price * percent / 100;
=======
  const p = Math.min(Math.max(percent, 0), 100);
  return Math.round((price - price * p / 100) * 100) / 100;
>>>>>>> ba5a4dc (Round discounted price to cents)
}
JavaScript

The rounding fix was written on top of the clamping change from 5b4dc1e: it uses the variable p that commit introduced. On the release branch p doesn’t exist. This is the common way a backport fails. The commit you want depends on a commit you didn’t pick.

You have three choices:

  1. Resolve by hand, writing a 1.4 version of the fix that uses percent directly. That’s the right call when the earlier commit is a feature you don’t want in 1.4.
  2. Pick the dependency too. Clamping the percent is itself a sensible fix for a release branch, so here it’s reasonable to include it.
  3. Abort with git cherry-pick --abort, which puts the branch back exactly where it was, and talk to whoever owns the release.

Taking the second option, abort and pick both, oldest first:

$ git cherry-pick --abort
$ git cherry-pick -x 5b4dc1e ba5a4dc
[release/1.4 a26d469] Clamp discount percent to 0-100
 Date: Thu Oct 1 10:00:00 2026 +0000
 1 file changed, 2 insertions(+), 1 deletion(-)
[release/1.4 c3f8157] Round discounted price to cents
 Date: Thu Oct 1 10:00:00 2026 +0000
 1 file changed, 1 insertion(+), 1 deletion(-)
Text

The order matters. Picking ba5a4dc first would conflict again for the same reason. To find dependencies before you start, git log --oneline release/1.4..main -- discount.js lists every commit on main that touched the file since the branch point.

After picking, run the tests on the release branch. A clean cherry-pick only means the text applied. It doesn’t mean the code is correct in 1.4’s context, where other functions may behave differently.

Why -x is worth typing

With -x, Git appends a line to the new commit’s message:

$ git log --format='%h %s%n%b' -1
c3f8157 Round discounted price to cents
(cherry picked from commit ba5a4dc6f5c082c459f027fbacb9c1d9c0589d7c)
Text

Months later, when someone asks “is the rounding fix in 1.4.3?”, that line answers it, and it lets tools and people link a backport to the original review. Leave it out only when the source commit is private (a local branch nobody else will see), because then the reference points at nothing.

Finding patches that are already applied

Because the copies have different hashes, git log release/1.4..main still lists the original commits as “not on the release branch.” Git can compare the diffs themselves instead, using a patch ID: a hash of the change with line numbers and whitespace ignored.

$ git cherry -v release/1.4 main
- 5b4dc1e81b54006392b58db0a76fa0069c7626d7 Clamp discount percent to 0-100
+ 8975cce767eea242b502f11d2e53c3fbf26ed6de Start 1.5 development
- ba5a4dc6f5c082c459f027fbacb9c1d9c0589d7c Round discounted price to cents
Text

A - means an equivalent change already exists on the release branch, and + means it doesn’t. git log --oneline --cherry-mark --left-right release/1.4...main gives the same answer for both sides, marking equivalent commits with =. If you resolved a conflict by hand and changed the diff, the patch IDs no longer match, which is another reason -x is useful.

Cherry-picking a merge commit

If the fix arrived on main as a merged pull request, you might try to pick the merge commit:

$ git cherry-pick 0f1cafc
error: commit 0f1cafc29d96ae80c759a9ecec64f8f7c4338d9c is a merge but no -m option was given.
fatal: cherry-pick failed
Text

A merge has two parents, so “the diff this commit introduced” is ambiguous. -m 1 says to diff against the first parent, the branch that was merged into, which gives you everything the pull request brought in as one commit. Often it’s cleaner to pick the pull request’s individual commits instead: git cherry-pick -x main^2~2..main^2 would pick the last two commits of the merged branch (the range excludes its left end).

When cherry-pick is the wrong tool

Cherry-picking is meant for a few commits going to a branch that will never be merged back, such as a release or hotfix branch. It’s a poor way to move work between active feature branches. Each pick creates a duplicate, and when both branches eventually merge you get the same change twice in the history, or conflicts where both copies were later edited differently. If you find yourself picking ten commits from a colleague’s branch, a merge or a rebase onto their branch is usually what you want.

A related habit that saves a lot of pain is fixing on main first and backporting, not the reverse. A fix made only on a release branch is easy to lose: the next release from main ships without it, and the bug comes back.

Interview practice

You need to backport a security fix (commit f41a2c9 on main) to release/2.3 and release/2.2. On 2.3 it applies cleanly. On 2.2 it conflicts in a file that was refactored between 2.2 and 2.3. What do you do, and how would someone later confirm both release branches have the fix?

Answer

On 2.3, run git cherry-pick -x f41a2c9, then run the tests on that branch, because applying cleanly says nothing about behaviour.

On 2.2, read the conflict before choosing. Run git log --oneline release/2.2..main -- <file> to see what changed in that file since 2.2 was cut. If the refactor is large or brings in features, don’t pick it just to make the fix apply. Resolve the conflict by writing the equivalent fix against 2.2’s code, then git add the file and git cherry-pick --continue. Keep the -x line, and add a note in the message that the change was adapted, since the patch now differs. Add or run a test that reproduces the vulnerability on 2.2 to show the adapted fix works.

To confirm later, search the release branches for the trailer: git log release/2.2 --grep='cherry picked from commit f41a2c9', and the same for 2.3. git cherry will mark the 2.3 copy as equivalent, but not the 2.2 one, because its diff is different. That’s exactly why the -x reference matters.

Further reading

More in Git & CI/CD

esc