Skip to content

git cherry-pick: Selective Backport & Hotfix Promotion

git cherry-pick applies the diff from a specific commit (or commits) onto the current HEAD as a new commit. It does not merge branches — it transplants individual patches across histories, regardless of branch topology.

For a DevOps engineer this is the primary tool for: backporting security fixes to release branches, cherry-picking a hotfix without pulling unrelated feature work, building patch trains across environments, and maintaining multiple release lines from a single mainline.

What it answers

"We fixed a critical bug on main. How do I get that exact fix onto release/2.x and release/1.x without merging everything else?"

"This patch passed CI on staging. I need to promote the same diff to production, but staging has extra commits that aren't ready yet."

"I need to build a release branch from three specific commits across two different feature branches."

Cherry-pick mechanics

A cherry-pick reads the source commit's diff (git diff A^..A), applies that patch to the current HEAD with a 3-way merge, and creates a new commit. The new commit has a different SHA — it is a copy, not a transplant.

Source branch:  A---B---C---D
                 \
Target branch:    E---F---C'  (cherry-pick of C)

Basic form

git cherry-pick <commit-sha>

Applies the diff and creates a new commit. Opens the editor to write the commit message (populated with the original message and a (cherry picked from commit ...) trailer by default).

Multiple commits

git cherry-pick A..D     # range: A is excluded, B C D are picked
git cherry-pick A^..D    # includes A as well
git cherry-pick C D E    # explicit list, oldest-first

Each is applied in order. If one fails, the sequence stops (see conflict resolution below).

Recording the source with -x

git cherry-pick -x <sha>

Appends (cherry picked from commit <sha>) to the commit message. This is invaluable for traceability — you can trace a backport to its exact origin commit. Use -x in every backport.

Preserving original author with --signoff

git cherry-pick --signoff -x <sha>

Adds a Signed-off-by trailer from the person performing the cherry-pick while preserving the original author and date. The combination of -x and --signoff gives both provenance and accountability.

Conflict resolution and recovery

Cherry-picks fail when the patch context no longer applies cleanly.

Detecting failure

Git exits with 1 on conflict and prints the path(s) with conflicts. The cherry-pick enters a paused state:

git status
# You are currently cherry-picking commit <sha>.
#   (fix conflicts and run "git cherry-pick --continue")

Resolving

# Edit conflicted files
vim src/file.c

# Mark resolved and continue
git add src/file.c
git cherry-pick --continue

This opens the editor so you can update the commit message. The resulting commit includes the original (cherry picked from ...) trailer.

Skipping a problematic commit

git cherry-pick --skip

Drops the problematic commit from the sequence and continues with the next one. Use sparingly — you are accepting that this change won't reach this branch.

Aborting the entire sequence

git cherry-pick --abort

Returns HEAD to the state before the cherry-pick started. No side effects.

Merge commits with -m

Cherry-picking a merge commit selects a single parent as the diff basis:

git cherry-pick -m 1 <merge-commit-sha>
  • -m 1 = diff against the first parent (what was on the target branch before the merge). The intended mainline.
  • -m 2 = diff against the second parent (the feature branch that was merged in). Useful for cherry-picking the entire feature without the merge metadata.

Use -m 1 when cherry-picking a merge that went into main. The resulting diff represents only the changes the merge brought in, not the full tree at the merge point.

Cherry-pick sequences: patch trains

A common DevOps pattern is promoting a verified commit through environment branches:

# Promotion pipeline: staging -> preprod -> prod
git checkout release/preprod
git cherry-pick -x <sha-verified-on-staging>

# After preprod CI passes
git checkout release/prod
git cherry-pick -x <sha-verified-on-preprod>

Each environment re-verifies the same logical patch with its own merge base. If the preprod cherry-pick introduces a conflict, the prod step uses that resolved version as its merge base — not the original.

Sequenced promotion

For a patch train (multiple commits promoted together):

# On staging branch after CI passes
git checkout release/prod
git cherry-pick -x staging~3..staging   # pick the last 3 staging commits

This promotes an exact set. Use range-diff afterward to confirm the promoted patches match:

git range-diff staging~3..staging HEAD~3..HEAD

DevOps patterns

Backport security hotfix

# Identify the fix on main
git log --oneline --grep="CVE-2024-" main

# Apply to release branches
for branch in release/1.x release/2.x release/3.x; do
  git checkout "$branch"
  git cherry-pick -x "$FIX_SHA"
done

Each branch gets the same patch. Conflicts will differ per branch. Resolve independently, then verify with git range-diff that the resolved patches produce the same diff.

Create a release from cherry-picked commits

git checkout -b release/v2.5.0 v2.4.0
git cherry-pick -x \
  abc123 \  # critical fix
  def456 \  # security patch
  789abc    # feature needed for release
git tag -s v2.5.0
git push origin v2.5.0

Selective environment promotion

When CI passes on staging but only some commits should go to prod:

# Get the commit range that staging CI just validated
git log --oneline prod..staging

# Pick only the safe commits
git checkout prod
git cherry-pick -x staging~2..staging   # last two staging commits only

Cherry-pick with build CI gate

In a Forgejo/GitHub Actions pipeline, gate the cherry-pick on the source commits' CI status:

- name: Backport if main CI passed
  env:
    COMMIT: ${{ github.sha }}
  run: |
    if git log --oneline -1 --format=%H | xargs gh run list --commit | grep -q success; then
      git checkout release/stable
      git cherry-pick -x "$COMMIT"
      git push
    else
      echo "Source commit CI did not pass — not backporting"
      exit 1
    fi

Cherry-pick vs. rebase vs. merge

Operation When to use History shape
cherry-pick Transplant specific commits across branches Flat copies, new SHAs
rebase Move an entire branch onto a new base Rewrites history of all commits
merge Join two divergent histories Preserves topology, no new SHAs for existing commits
format-patch + am Air-gapped transport, cross-org contribution Patch files applied as new commits

Cherry-pick is the most surgical. It is the right tool when you need a specific subset of changes, not a branch relationship.

Common pitfalls

Cherry-picking a cherry-pick

Cherry-picking a commit that was itself cherry-picked from the same source creates duplicate diff. Git's patch-identity detection solves this:

git log --cherry-pick --oneline main...release/2.x
# Shows commits unique to each side; equivalent patches are suppressed

Cherry-pick reorders history

Commits are applied in the order you list them. If you cherry-pick B A (out of chronological order), the new commits will have parent-child relationships in that order. The diff is the same, but the history reads differently. Always cherry-pick in chronological (oldest first) order unless you have a specific reason not to.

Cherry-pick and LFS pointers

If the source commit includes LFS objects, the cherry-pick copies the pointer file. The actual LFS content must exist on the remote. On a fresh clone of the target branch, run git lfs pull after cherry-picking to materialize the content.

Cherry-pick and signed commits

Cherry-pick creates a new commit with a new SHA. The original GPG signature is not carried over. Use git commit --amend -S after cherry-picking to re-sign, or use --gpg-sign in CI:

git cherry-pick -x <sha>
git commit --amend --no-edit -S

Reference: key flags

Flag Purpose
-x Append (cherry picked from ...) trailer
-n / --no-commit Apply diff without committing (staging only)
-e / --edit Force editor even with -x
-s / --signoff Add Signed-off-by trailer
-m <n> Cherry-pick a merge commit using parent <n>
--ff If current HEAD matches the cherry-pick's result, do nothing
--strategy=recursive -X theirs Auto-resolve conflicts in favour of the cherry-picked diff
--allow-empty Allow empty cherry-picks (useful for metadata-only commits)
--keep-redc Keep empty commits that became empty due to previous picks
--strategy-option=ours Keep the target branch's version on conflict

Scripted cherry-pick automation

The companion script at scripts/cherry-pick-manage.sh provides:

  • backport — cherry-pick a single commit across multiple branches
  • sequence — apply a range to the current branch with progress reporting
  • promote — advance verified commits through environment stages (staging → preprod → prod)
  • conflicts — list conflicted files in a paused cherry-pick
  • verify — use range-diff to validate cherry-pick fidelity
  • cleanup — list/reset stale cherry-pick states in the working tree

Creating a cherry-pick-based release workflow

  1. Identify the commits for the release (bugfixes, features, security patches).
  2. Create the release branch from the last stable tag.
  3. Cherry-pick each commit with -x, oldest first.
  4. Resolve conflicts per commit; do not batch-resolve.
  5. Run CI on the release branch.
  6. Use git range-diff between the source range and the cherry-pick range to confirm no drift.
  7. Tag and push.

This avoids merge commits, keeps the release branch linear, and gives every commit a direct link back to its origin.