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 ontorelease/2.xandrelease/1.xwithout 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.
Basic form¶
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¶
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¶
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¶
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¶
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:
-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:
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:
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 branchessequence— apply a range to the current branch with progress reportingpromote— advance verified commits through environment stages (staging → preprod → prod)conflicts— list conflicted files in a paused cherry-pickverify— use range-diff to validate cherry-pick fidelitycleanup— list/reset stale cherry-pick states in the working tree
Creating a cherry-pick-based release workflow¶
- Identify the commits for the release (bugfixes, features, security patches).
- Create the release branch from the last stable tag.
- Cherry-pick each commit with
-x, oldest first. - Resolve conflicts per commit; do not batch-resolve.
- Run CI on the release branch.
- Use
git range-diffbetween the source range and the cherry-pick range to confirm no drift. - Tag and push.
This avoids merge commits, keeps the release branch linear, and gives every commit a direct link back to its origin.