Git Advanced Commands: Rebase, Cherry-Pick, and Stash
Once you have mastered basic Git add, commit, and push, a world of powerful commands awaits. Interactive rebase, cherry-pick, stash, and reflog give you fine-grained control over your commit history. These tools let you rewrite history (carefully), pick specific changes from other branches, and recover seemingly lost work.
This guide covers advanced Git operations that every experienced developer should know.
Interactive Rebase
Interactive rebase lets you modify, reorder, squash, or delete commits. It is the primary tool for cleaning up a messy branch before merging.
# Rebase the last 3 commits
git rebase -i HEAD~3
# Or rebase all commits since branching from main
git rebase -i mainRebase Actions
When you run interactive rebase, an editor opens with a list of commits and available actions:
pick a1b2c3d First commit
pick e4f5g6h Second commit
pick i7j8k9l Third commit
# Actions:
# pick = use commit as-is
# reword = change commit message
# edit = stop to amend the commit
# squash = combine with previous commit (keep message)
# fixup = combine with previous commit (discard message)
# drop = remove the commitCommon Rebase Scenarios
# 1. Squash multiple commits into one
pick a1b2c3d Add feature scaffolding
squash e4f5g6h Implement core logic
fixup i7j8k9l Fix typo
# Results in one commit: "Add feature scaffolding"
# 2. Reorder commits
pick i7j8k9l Third commit # ← Moved to first
pick a1b2c3d First commit
pick e4f5g6h Second commit
# 3. Edit a specific commit
edit a1b2c3d Add feature scaffolding
# Git stops at this commit — make changes, then:
git commit --amend
git rebase --continue
# 4. Drop a commit
pick a1b2c3d Add feature scaffolding
drop e4f5g6h Bad commit # ← Removed entirely
pick i7j8k9l Good commitRebase Safety
Golden rule: Never rebase commits that have been pushed to a shared branch.
Rebasing rewrites history — if someone else has based work on the old commits, they will have conflicts. Only rebase local or feature branches. If you absolutely must rebase a shared branch, coordinate with the entire team, use --force-with-lease instead of --force, and have everyone re-clone or rebase their local copies immediately.
Handling Rebase Conflicts
When a rebase hits a conflict, Git pauses and lets you resolve it:
git rebase main
# CONFLICT (content): Merge conflict in src/config.js
# Fix the conflict in your editor
git add src/config.js
git rebase --continue
# Or skip this commit
git rebase --skip
# Or abort the entire rebase
git rebase --abortIf the same conflict appears across multiple commits, you will have to resolve it repeatedly. This is the main practical downside of rebasing versus merging. When conflicts become overwhelming, git rebase --abort returns everything to the state before you started — you can always fall back to a regular merge.
Cherry-Pick
Cherry-pick applies a specific commit (or commits) from one branch to another without merging the entire branch.
# Cherry-pick a single commit
git cherry-pick a1b2c3d
# Cherry-pick a range of commits
git cherry-pick a1b2c3d..e4f5g6h
# Cherry-pick without committing (useful for review + amend)
git cherry-pick -n a1b2c3d
# Cherry-pick with original author info preserved
git cherry-pick -x a1b2c3d # Adds "(cherry picked from commit ...)" to messageReal-World Cherry-Pick Scenarios
# 1. Hotfix to multiple release branches
git checkout release/v1.0
git cherry-pick x1y2z3d # Apply hotfix to v1.0
git checkout release/v2.0
git cherry-pick x1y2z3d # Apply same hotfix to v2.0
# 2. Pick a specific feature commit to the main branch
git checkout main
git cherry-pick f6g7h8i # Just the feature commit, not the whole branch
# 3. Undo a cherry-pick that caused conflicts
git cherry-pick --abortCherry-pick is especially useful for backporting bug fixes to older release branches. Many open-source projects use this workflow: a fix lands on main, then gets cherry-picked to all active release branches.
Stash
Stash temporarily saves uncommitted changes so you can switch branches or pull changes without committing half-finished work.
# Save current changes
git stash
# Save with a descriptive message
git stash push -m "WIP: refactoring auth module"
# List all stashes
git stash list
# stash@{0}: On feature-x: WIP: refactoring auth module
# stash@{1}: On main: debug logging changes
# Apply the most recent stash
git stash pop # Apply and remove from stash list
git stash apply # Apply but keep in stash list
# Apply a specific stash
git stash apply stash@{1}
# Create a branch from a stash (useful if stash conflicts)
git stash branch new-branch stash@{0}
# Show what's in a stash
git stash show -p stash@{1}
# Drop a stash
git stash drop stash@{1}
# Clear all stashes
git stash clearStashing Untracked Files
# Stash tracked changes only
git stash
# Stash tracked + untracked files
git stash -u
# Stash everything (including ignored files)
git stash -aA useful pattern is to stash with -u as a routine precaution — it ensures you never lose new files that Git hasn’t seen before. The --all flag is better reserved for cases where you need a completely clean working directory, such as switching to a branch with a different build system or configuration files.
Reflog
Reflog records every movement of HEAD — including operations that seem to “lose” commits. It is your safety net for recovering from mistakes.
# View reflog
git reflog
# a1b2c3d HEAD@{0}: commit: Fix login bug
# e4f5g6h HEAD@{1}: rebase: finished
# i7j8k9l HEAD@{2}: rebase: checkout main
# m0n1o2p HEAD@{3}: commit: Add payment featureRecovering Lost Commits
# 1. Oops, I reset too far
git reset --hard HEAD~3 # Lost 3 commits
git reflog # Find the commit hash
git reset --hard a1b2c3d # Back to safety
# 2. Oops, I rebased and lost commits
git reflog # See the old branch tip
git checkout -b recovered i7j8k9l # Create a branch at the old state
# 3. Oops, I deleted a branch
git reflog --all # Shows ALL branches
git checkout -b recovered m0n1o2pThe reflog is local — it only exists on your machine and is never pushed to the remote. This means you can recover from almost any mistake as long as you act before Git’s garbage collection runs (usually 30 to 90 days). To see the reflog for all refs, not just HEAD, use git reflog --all.
Resolving Complex Merge Conflicts
# Start a merge
git merge feature-branch
# CONFLICT (content): Merge conflict in src/app.js
# See which files have conflicts
git status
# View the conflict
git diff
# Use merge strategies
# Accept theirs (keep the incoming branch version)
git checkout --theirs src/app.js
# Accept ours (keep the current branch version)
git checkout --ours src/app.js
# Use merge tool
git mergetool # Opens configured merge tool (vimdiff, VSCode, etc.)
# Abort the merge entirely
git merge --abortConflict Resolution Workflow
# 1. Understand both versions
# Conflict markers look like:
<<<<<<< HEAD
console.log('Our version');
=======
console.log('Their version');
>>>>>>> feature-branch
# 2. Choose the correct version or write a new one
console.log('Correct, resolved version');
# 3. Stage resolved files
git add src/app.js
# 4. Continue the merge
git merge --continue
# 5. Or abort if the merge was a mistake
git merge --abortFor especially complex conflicts involving dozens of files, consider using a visual merge tool like meld, kdiff3, or Beyond Compare. These tools present three-way diffs (base, ours, theirs) side by side, making it easier to understand what changed on each side.
Useful Advanced Commands
# Find which commit introduced a bug (binary search)
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# Git checks out the middle commit — test and mark good/bad
# Blame — see who changed each line
git blame src/app.js
# Show branches containing a specific commit
git branch --contains a1b2c3d
# Clean untracked files
git clean -fd # Remove untracked files and directories
git clean -fdn # Dry run (show what would be removed)
# Create an archive of the repo at a specific commit
git archive --format=zip HEAD > repo.zipGit Grep and Log Searching
Git includes powerful search tools that go beyond what standard Unix commands offer:
# Search commit messages
git log --grep="bug fix" --oneline
# Search file contents in any revision
git grep "TODO" HEAD~5
# Find when a specific string was introduced
git log -S "deprecatedFunction" --oneline
# Show the history of a specific file across renames
git log --follow -- src/legacy/util.jsThese search commands become essential when you are debugging regressions in a large codebase with thousands of commits. The -S flag (often called “pickaxe”) is particularly effective at finding when a specific code change was introduced because it searches for commits that added or removed the target string.
Submodules and Subtrees
Managing dependencies within a monorepo or across multiple projects often requires submodules or subtrees:
# Add a submodule
git submodule add https://github.com/user/library.git lib/library
# Clone a repo with submodules
git clone --recurse-submodules https://github.com/user/project.git
# Update all submodules
git submodule update --remote --recursive
# Add a subtree
git subtree add --prefix=lib/shared ../shared.git main
git subtree pull --prefix=lib/shared ../shared.git main
git subtree push --prefix=lib/shared ../shared.git mainSubmodules link to a specific commit in another repository, giving you precise version control over dependencies. Subtrees copy code from another repository into your own, which simplifies collaboration at the cost of larger repository size.
Conclusion
Advanced Git commands give you surgical control over your commit history. Use interactive rebase to keep history clean, cherry-pick to apply specific commits across branches, and stash for temporary work-in-progress. Remember the reflog when you make mistakes — it can recover almost anything. Always follow the golden rule: never rebase shared branches. As your team grows, mastering these commands will dramatically improve your productivity and confidence when using Git for complex workflows.
Related: Check our Git Workflows guide.
FAQ
Q: What is the difference between git stash pop and git stash apply?
A: pop applies the stash and removes it from the stash list. apply applies the stash but keeps it on the list, which is useful when you want to apply the same stash to multiple branches.
Q: Can I recover a dropped stash?
A: Yes, use git fsck --unreachable | grep commit | cut -d' ' -f3 | xargs git log --oneline to find orphaned stash commits, then cherry-pick the commit hash.
Q: Is it safe to use git rebase on a shared branch?
A: No — rebasing rewrites commit history and creates divergent histories for anyone else working on that branch. Always use git merge on shared branches.
Q: What does git cherry-pick -x do?
A: It appends “(cherry picked from commit …)” to the commit message, preserving traceability of where the change originated.
Q: How long does the reflog keep history?
A: By default, Git keeps reflog entries for 90 days (30 days for unreachable commits). You can configure this with gc.reflogExpire and gc.reflogExpireUnreachable.
Q: What is the safest way to undo a merge commit?
A: Use git revert -m 1 <merge-commit-hash> to revert the merge while preserving history. The -m 1 flag tells Git to keep the first parent (the mainline).
For a comprehensive overview, read our article on Git Aliases Guide.