Git Stash and Cherry-Pick Explained — Real-World Patterns and Pitfalls
- Git stash is a local-only, history-invisible parking lot for unfinished work — always use -u to include untracked files and always use -m to label your stashes or you'll regret it during a busy sprint.
- Cherry-pick creates a new commit with a new SHA — the original commit on the source branch is untouched, and Git may not recognise the two as 'the same change' during a future merge, which can cause unexpected conflicts.
- Use
git stash applyovergit stash popwhenever a conflict is possible — apply keeps the stash entry intact as a safety copy until you've confirmed the restoration succeeded cleanly.
- git stash push -u -m 'label': saves tracked + untracked changes with a description
- git stash pop: restores and removes from stack — deletes stash even on conflict
- git stash apply: restores but keeps in stack — safer when conflicts are possible
- git cherry-pick
: replays a commit's diff onto current branch with a new SHA - git cherry-pick -n
: applies changes without committing — combine multiple into one
Stash pop hit a merge conflict — stash is gone from the stack
git reflog | grep stash (find the stash application entry)git fsck --no-reflogs | grep dangling (find dangling commit objects)Cherry-pick conflict — 'could not apply'
git status (see conflicted files)git cherry-pick --continue (after resolving and staging)Cherry-pick failed on merge commit — 'no -m option given'
git cat-file -p <merge-sha> (see parent SHAs)git cherry-pick -m 1 <merge-sha> (use parent 1 as base)Cannot find which stash has my changes
git stash list (read messages — this is why you always use -m)git stash show -p stash@{N} | grep 'search-term' (search each stash diff)Stashed changes on wrong branch — working directory has wrong files
git reset --hard HEAD (undo the stash application)git checkout <correct-branch> (switch to where you need the stash)Production Incident
git cherry-pick --edit <sha> and append (cherry picked from commit abc1234 on main) to the commit message.
3. Team convention: added a commit message template that includes a 'Cherry-picked-from:' footer for all cherry-picks.
4. Considered git merge --strategy=ours for the future to let Git know the fix was already applied.Production Debug GuideSystematic recovery paths for lost stashes, cherry-pick conflicts, and wrong-branch operations.
git reflog — find the stash application entry.
3. The stash commit object still exists: git fsck --no-reflogs | grep dangling — find dangling commit objects.
4. Recover: git stash apply <hash> using a hash that looks like your stash.
5. Prevention: use git stash apply when conflicts are possible. Then git stash drop manually after resolving.git log --oneline --all --grep='fix'.
3. Resolve by keeping the existing fix (the cherry-picked version is already on the branch).
4. If this happens regularly: use git merge --strategy=ours to tell Git the changes are already applied.
5. Prevention: always annotate cherry-picks with source SHA for traceability.git stash list — read the messages (this is why you always use -m).
2. If messages are unhelpful: git stash show -p stash@{N} — full diff of each stash.
3. Search stashes for a specific change: git stash show -p stash@{N} | grep 'search-term' for each N.
4. Or: git log --all --oneline --grep='search-term' — stash entries appear in the log.git status to see which files have conflicts.
2. Resolve conflicts in each file — same as merge conflict resolution.
3. Stage resolved files: git add <file>.
4. Continue: git cherry-pick --continue.
5. If too messy: git cherry-pick --abort to return to pre-cherry-pick state.git cherry-pick -m 1 <merge-sha> — parent 1 is the main line.
3. If the result is too complex: consider whether a regular merge or rebase makes more sense.
4. Cherry-picking merge commits is advanced territory — avoid unless you understand the parent structure.Two commands solve the two most common context-switching problems in Git: stash and cherry-pick. Stash shelves uncommitted changes so you can switch branches cleanly. Cherry-pick copies specific commits from one branch to another without merging the entire branch.
Stash is a local-only stack — not a branch, not a commit in your history, not synced to the remote. It is ideal for short context switches but dangerous for anything you want to share or keep long-term. Cherry-pick creates a new commit with a new SHA on the target branch. The original commit is untouched. This matters because Git may not recognise the two as the same change during a future merge, causing duplicate conflicts.
Common misconceptions: that stash saves everything (it excludes untracked files by default), that cherry-pick moves commits (it copies them), and that stash pop is always safe (it deletes the stash even on merge conflicts).
Git Stash — Parking Your Work Without a Throwaway Commit
When you run git stash, Git takes everything in your working directory and staging area that differs from HEAD, bundles it into a special stash entry, and hands you back a perfectly clean working tree. The stash lives on a per-repo stack — last in, first out — and it persists across branch switches, which is the whole point.
The key mental model is this: a stash entry is NOT a branch, NOT a commit on your history, and NOT synced to the remote automatically. It's local scratch space. That makes it ideal for short context switches, but dangerous for anything you want to share with teammates or keep long-term.
People often assume git stash only captures tracked files. It doesn't touch untracked files by default. That silent omission is responsible for a lot of lost work. Use git stash push --include-untracked (or the short form -u) when you have new files that haven't been staged yet.
You can also give stashes a descriptive message with git stash push -m "wip: payment gateway refactor". Without messages, stashes pile up as cryptic stash@{0}, stash@{1} entries and become almost impossible to manage after a long sprint.
# io.thecodeforge — Git Stash Workflow # ─── SCENARIO ────────────────────────────────────────────────────────────── # You're halfway through refactoring the payment module when your lead # asks you to jump on branch 'hotfix/order-total-rounding' immediately. # ─────────────────────────────────────────────────────────────────────────── # Check what you currently have in progress git status # Output shows: modified payment_gateway.rb, new file: stripe_adapter.rb # Stash EVERYTHING — tracked modifications AND untracked new files # -u flag = --include-untracked (critical — stripe_adapter.rb is new) # -m flag = a human-readable label so you can find this stash later git stash push -u -m "wip: payment gateway refactor — stripe adapter half done" # Confirm the working tree is now clean git status # Output: nothing to commit, working tree clean # Switch to the hotfix branch safely git checkout hotfix/order-total-rounding # ... do your hotfix work, commit it, done ... # Come back to your feature branch git checkout feature/payment-gateway # List all stashes to find the right one (you might have multiple) git stash list # stash@{0}: On feature/payment-gateway: wip: payment gateway refactor — stripe adapter half done # Pop the stash — restores files AND removes the stash entry from the stack # Use 'apply' instead of 'pop' if you want to keep the stash as a safety copy git stash pop # Verify your files are back exactly where you left them git status # Output: modified: payment_gateway.rb, new file: stripe_adapter.rb
Saved working directory and index state On feature/payment-gateway: wip: payment gateway refactor — stripe adapter half done
# After git status (on the feature branch, post-stash)
On branch feature/payment-gateway
nothing to commit, working tree clean
# After git stash list
stash@{0}: On feature/payment-gateway: wip: payment gateway refactor — stripe adapter half done
# After git stash pop
On branch feature/payment-gateway
Changes not staged for commit:
modified: payment_gateway.rb
Untracked files:
stripe_adapter.rb
Dropped stash@{0} (a3f1c2d...)
git stash pop will hit a merge conflict — but it still drops the stash entry from the stack before you've resolved anything. Use git stash apply stash@{0} instead; it restores the changes WITHOUT removing the stash, giving you a safety net while you sort out the conflicts.Managing Multiple Stashes — When One Isn't Enough
Real projects generate real interrupt-driven work. You might be juggling an in-progress feature, an experimental spike, and a half-finished code review all at once. The stash stack can hold all of them — but only if you're disciplined about labelling and retrieving them correctly.
The stack is LIFO (last in, first out), so stash@{0} is always the most recent. If you stash three things without labels, you'll spend five minutes running git stash show -p stash@{2} trying to remember what each one contains. Don't do that to yourself.
To apply a specific stash without popping it — useful for inspecting before committing — use git stash apply stash@{1}. To permanently delete a stash entry you no longer need, use git stash drop stash@{1}. To nuke the entire stash stack: git stash clear. That last command is irreversible, so use it deliberately.
One underused power move: git stash branch <new-branch-name> stash@{0}. This creates a brand new branch from the commit where you originally stashed, then pops the stash onto it. It's the cleanest escape hatch when your stash has grown into something that deserves its own branch rather than a quick pop.
# io.th in gitecodeforge — Managing Multiple Stashes # ─── SCENARIO ────────────────────────────────────────────────────────────── # You have two separate in-progress tracks stashed. You need to # inspect, apply the right one, and clean up the other. # ─────────────────────────────────────────────────────────────────────────── # See all stashes with their index positions git stash list # stash@{0}: On feature/user-auth: wip: JWT refresh token logic # stash@{1}: On feature/user-auth: wip: email validation spike # Preview what's inside stash@{1} before applying it # -p flag shows the actual diff (patch format) git stash show -p stash@{1} # Apply ONLY the email validation spike (stash@{1}) without removing it yet git stash apply stash@{1} # After confirming the apply looks good, remove it from the stack explicitly git stash drop stash@{1} # List again — note that indexes shift after a drop git stash list # stash@{0}: On feature/user-auth: wip: JWT refresh token logic # ─── POWER MOVE ──────────────────────────────────────────────────────────── # The JWT stash has grown into a real feature — give it its own branch # This creates 'feature/jwt-refresh' from the original stash point # and pops stash@{0} onto it automatically git stash branch feature/jwt-refresh stash@{0} # Confirm the new branch has the stashed changes applied and stash is gone git status git stash list # Empty — stash was consumed by branch creation
stash@{0}: On feature/user-auth: wip: JWT refresh token logic
stash@{1}: On feature/user-auth: wip: email validation spike
# git stash show -p stash@{1} (abbreviated)
diff --git a/validators/email_validator.js b/validators/email_validator.js
index 3a2c1f0..8b4e9d2 100644
--- a/validators/email_validator.js
+++ b/validators/email_validator.js
@@ -14,6 +14,11 @@ function validateEmailFormat(email) {
+ // RFC 5322 compliant check added
+ const rfcPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
# git stash branch feature/jwt-refresh stash@{0}
Switched to a new branch 'feature/jwt-refresh'
On branch feature/jwt-refresh
Changes not staged for commit:
modified: auth/token_manager.js
Dropped stash@{0} (b7d3a1e...)
- git stash push -p: interactive hunk-by-hunk selector, like git add -p
- Stash only specific chunks of a file, leave the rest in working directory
- Use when a single file has two unrelated changes you want to separate
- Slow for large changesets — use git stash push -- <path> for file-level control
Git Cherry-Pick — Transplanting Exactly the Commit You Need
Cherry-pick takes one or more commits from anywhere in your Git history and replays them onto your current branch. It doesn't move the original commits — it creates new commits with the same changes but different commit SHAs. Think of it as 'apply this diff' rather than 'merge this branch.'
The most common real-world trigger is a hotfix workflow: a critical bug is fixed on main, and you need that fix on the release/2.4 branch without pulling in the three feature commits that came after it. One git cherry-pick <sha> and you're done.
The second trigger is salvaging work from an abandoned branch. Someone started a feature, it got cancelled, but buried in the commit history is a beautifully written utility function you can reuse. Cherry-pick extracts just that commit.
Crucially, cherry-pick creates a NEW commit SHA on the target branch. The two commits (original and cherry-picked) have the same diff but different identities. This matters for merge conflict detection — Git may not recognise they represent the same change, which can cause duplicate conflicts down the line if those branches ever merge. This is the main reason cherry-pick is a scalpel, not a Swiss Army knife.
# io.thecodeforge — Git Cherry-Pick Workflow # ─── SCENARIO ────────────────────────────────────────────────────────────── # A critical SQL injection fix was committed to 'main' as commit abc1234. # The 'release/3.1' branch is already cut and needs this fix NOW, # but the 2 commits after abc1234 on main are incomplete features # that must NOT go into the release. # ─────────────────────────────────────────────────────────────────────────── # First, find the exact SHA of the fix commit on main git log main --oneline # abc1234 fix: sanitize user input in search query builder # def5678 wip: new dashboard widget (incomplete) # ghi9012 wip: migrate analytics to ClickHouse (incomplete) # Switch to the release branch git checkout release/3.1 # Cherry-pick ONLY the security fix commit by its SHA # Git will replay that commit's diff onto the current branch git cherry-pick abc1234 # Verify the fix landed — note the NEW SHA (different from abc1234) git log --oneline -3 # xyz9999 fix: sanitize user input in search query builder <-- new SHA! # aaa1111 chore: bump version to 3.1.0 # bbb2222 feat: order confirmation email template # ─── CHERRY-PICKING A RANGE ──────────────────────────────────────────────── # If you need commits abc1234 AND def5678 (but NOT ghi9012), use dot-dot notation. # The range abc1234..def5678 means: commits AFTER abc1234 up to AND including def5678. # To INCLUDE abc1234 itself, use the caret on the start: abc1234^..def5678 git cherry-pick abc1234^..def5678 # ─── CHERRY-PICK WITH EDIT ───────────────────────────────────────────────── # --edit (-e) opens the commit message editor before finalising # Useful when you want to note it was cherry-picked from main git cherry-pick --edit abc1234 # Opens editor — you can append: "(cherry picked from commit abc1234 on main)"
[release/3.1 xyz9999] fix: sanitize user input in search query builder
Date: Mon Oct 14 09:23:41 2024 +0000
1 file changed, 4 insertions(+), 1 deletion(-)
# git log --oneline -3
xyz9999 fix: sanitize user input in search query builder
aaa1111 chore: bump version to 3.1.0
bbb2222 feat: order confirmation email template
# git cherry-pick abc1234^..def5678
[release/3.1 lmn1234] fix: sanitize user input in search query builder
[release/3.1 opq5678] wip: new dashboard widget (incomplete)
- Commit SHA = hash(content + author + timestamp + parent SHA)
- Cherry-pick changes the parent, so the SHA changes even with identical diff
- Same reason rebasing changes SHAs: different parent = different hash
- This is why Git may not recognise cherry-picked commits as the same change during future merges
git cherry-pick --edit <sha> and append (cherry picked from commit abc1234 on main) to the commit message.Handling Cherry-Pick Conflicts and Knowing When Not to Use It
Cherry-pick conflicts happen when the target branch has diverged enough from the source that Git can't cleanly apply the diff. The conflict resolution process is identical to a merge conflict — you edit the files, stage the resolution, then run git cherry-pick --continue. If you decide it's too messy, git cherry-pick --abort restores the branch to its pre-cherry-pick state cleanly.
There's also git cherry-pick --no-commit (or -n), which applies the changes from one or more commits to your working directory and staging area WITHOUT creating a commit. This is perfect when you want to cherry-pick changes from several commits and squash them into one clean commit with a better message.
Now the honest advice about when NOT to cherry-pick: if you find yourself regularly cherry-picking the same fix from one long-lived branch to another, that's a workflow smell. The underlying problem is usually branch strategy — your release branches are too isolated from main, or your hotfix process doesn't feed back into all active branches automatically. Cherry-pick is a patch for a workflow problem, not a solution to it. Use it for one-off emergencies, not as a standing process.
# io.thecodeforge — Cherry-Pick Conflict Resolution # ─── SCENARIO ────────────────────────────────────────────────────────────── # Cherry-picking commit abc1234 hits a conflict in search_query_builder.rb # because the release branch edited that same function independently. # ─────────────────────────────────────────────────────────────────────────── git cherry-pick abc1234 # Git stops and reports a conflict: # error: could not apply abc1234... fix: sanitize user input in search query builder # hint: After resolving the conflicts, mark them with # hint: git add <pathspec>, then run git cherry-pick --continue # See exactly which files have conflicts git status # both modified: app/queries/search_query_builder.rb # Open the file and resolve the conflict markers manually # <<<<<<< HEAD = your current release/3.1 version # ======= = separator # >>>>>>> abc1234... = the incoming cherry-picked version # After editing the file to the correct merged state: git add app/queries/search_query_builder.rb # Continue the cherry-pick — Git will create the commit git cherry-pick --continue # Editor opens for commit message — save and close to finalise # ─── ALTERNATIVE: BAIL OUT CLEANLY ───────────────────────────────────────── # If the conflict is too complex and you want to reconsider your approach: git cherry-pick --abort # Restores the branch exactly as it was before you started — no residue # ─── NO-COMMIT MODE: COMBINE MULTIPLE CHERRY-PICKS INTO ONE COMMIT ───────── # Apply changes from two commits without committing either one yet git cherry-pick --no-commit abc1234 git cherry-pick --no-commit def5678 # Now both sets of changes sit in the staging area git status # Changes to be committed: modified: search_query_builder.rb, modified: query_sanitizer.rb # Create one clean, well-described commit from both changes combined git commit -m "fix: apply comprehensive input sanitization across query layer (backport from main)"
error: could not apply abc1234... fix: sanitize user input in search query builder
hint: After resolving the conflicts, mark them with
hint: git add <pathspec>, then run git cherry-pick --continue
Conflict in app/queries/search_query_builder.rb
# After resolving and git add:
# git cherry-pick --continue
[release/3.1 xyz9999] fix: sanitize user input in search query builder
1 file changed, 5 insertions(+), 2 deletions(-)
# git cherry-pick --abort
On branch release/3.1
nothing to commit, working tree clean
# After --no-commit x2 then git commit:
[release/3.1 pqr3344] fix: apply comprehensive input sanitization across query layer (backport from main)
2 files changed, 9 insertions(+), 3 deletions(-)
git cherry-pick on a merge commit SHA will fail with 'is a merge but no -m option was given.' Merge commits have two parents, so Git doesn't know which parent's diff to replay. You need git cherry-pick -m 1 <merge-sha> where -m 1 means 'treat parent 1 (main line) as the base.' This is advanced territory — if you're here, strongly consider whether a regular merge or rebase makes more sense.| Feature / Aspect | git stash | git cherry-pick |
|---|---|---|
| Purpose | Temporarily park in-progress work | Copy a specific commit to another branch |
| Creates a commit | No — stores in a local stash stack | Yes — creates a new commit with a new SHA |
| Visible in git log | No — stash is hidden from history | Yes — appears as a regular commit |
| Synced to remote | No — stash is local only | Yes — cherry-picked commit pushes normally |
| Scope | Working directory + staging area | Entire commit (all changed files in that commit) |
| Reversible | Yes — pop, apply, or drop cleanly | Harder — requires a revert commit or reset |
| Primary use case | Context-switching mid-feature | Backporting a hotfix to a release branch |
| Conflict risk | Low (restoring to same branch) | Medium-High (branches may have diverged) |
| Survives branch deletion | Yes — stash outlives branches | N/A — cherry-pick is an action, not a storage |
| Works across repos | No — local repo only | No — but you can fetch a remote branch first |
🎯 Key Takeaways
- Git stash is a local-only, history-invisible parking lot for unfinished work — always use -u to include untracked files and always use -m to label your stashes or you'll regret it during a busy sprint.
- Cherry-pick creates a new commit with a new SHA — the original commit on the source branch is untouched, and Git may not recognise the two as 'the same change' during a future merge, which can cause unexpected conflicts.
- Use
git stash applyovergit stash popwhenever a conflict is possible — apply keeps the stash entry intact as a safety copy until you've confirmed the restoration succeeded cleanly. - Cherry-pick is a precision scalpel for one-off backports — if you're doing it repeatedly for the same fix across multiple branches, that's your workflow architecture asking to be redesigned, not a sign to cherry-pick more.
- Stashes > 3 days old should be converted to branches with git stash branch. Stashes are invisible to git branch and git log — if your machine dies, they are gone.
- Always annotate cherry-picked commits with source SHA using --edit. Without annotation, duplicate conflicts during future merges are untraceable.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QYou have uncommitted changes on the wrong branch and you need them on a different branch. Walk me through at least two approaches — one using stash and one without it. What are the trade-offs of each?
- QIf you cherry-pick a commit from branch A onto branch B, and later branch A is merged into branch B, what happens to the cherry-picked changes? Could you get a conflict, and why?
- QA teammate ran
git stash popand got a merge conflict, then said 'my stash is gone but the conflict isn't resolved yet.' What actually happened, and how would you recover? - QWhy does a cherry-picked commit get a new SHA even though the diff is identical? How does this affect future merges?
- QWhen should you NOT cherry-pick? What does regular cherry-picking across branches indicate about your workflow?
- QHow would you recover a stash that was accidentally dropped or lost after a git stash pop conflict?
Frequently Asked Questions
Does git stash work across branches?
Yes — a stash entry is stored in the repository, not tied to a specific branch. You can stash on branch A, switch to branch B, and apply the stash there. Just be aware that if the stashed changes touch files that differ significantly on branch B, you may hit merge conflicts when applying.
Can I cherry-pick multiple commits at once?
Yes. You can list individual SHAs separated by spaces — git cherry-pick abc1234 def5678 — or use a range with git cherry-pick abc1234^..def5678 to pick all commits from abc1234 to def5678 inclusive. Add --no-commit (-n) to stage all the changes first and squash them into a single commit manually.
What's the difference between git stash pop and git stash apply?
Both restore your stashed changes, but pop removes the stash entry from the stack after applying it, while apply leaves the stash entry intact. If pop hits a merge conflict, it still removes the stash entry — meaning your safety copy is gone while your working directory is in a conflicted state. Use apply when there's any chance of a conflict, and only drop the stash manually once you've confirmed everything looks right.
Can I recover a stash I accidentally dropped or lost after a pop conflict?
Sometimes. Run git fsck --no-reflogs | grep commit to find dangling commit objects (stash entries are commits internally). You can then run git stash apply <hash> on any hash that looks like your lost stash. This works within the gc.reflogExpire window (default 30 days) as long as you haven't run git gc.
Why does a cherry-picked commit get a new SHA?
A commit's SHA is a hash of its content, author, timestamp, AND its parent commit. Since the parent on the target branch is different from the parent on the source branch, the hash is different — even if the diff is byte-for-byte identical. This is the same reason rebasing changes SHAs.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.