Cherry-pick & Interactive Rebase
Two Tools for Precise History Surgery
Merging and rebasing move whole branches. Sometimes you need finer control:
git cherry-pick— copy one specific commit onto another branchgit rebase -i— rewrite a series of commits: squash them together, reword messages, reorder, or drop them entirely
I use cherry-pick a few times a month (usually for hotfixes) and interactive rebase almost daily (to clean up my own branch before opening a PR).
Cherry-pick: Copy a Commit Across Branches
Cherry-pick takes the changes introduced by a commit and replays them as a new commit on your current branch. The original stays where it was; the copy gets a new hash.
The classic scenario: a hotfix
Rizwan is deep into a feature branch when Hafsa reports a production bug. He fixes it with one commit on his feature branch — but the fix needs to ship from main now, not whenever the feature merges.
# Find the hash of the fix commit
git log --oneline
# e4f5a6b Fix crash when invoice list is empty <- this one
# c3d4e5f Add invoice PDF export
# b2c3d4e Start invoice redesign
# Copy just the fix onto main
git switch main
git cherry-pick e4f5a6b
git push origin main
The fix now exists twice: once on feature and once (with a new hash) on main. When the feature branch eventually merges, Git is usually smart enough to notice the duplicate change and not conflict — but if it does conflict, the resolution is trivial because both sides are identical.
Useful cherry-pick variations
git cherry-pick a1b2c3d e4f5a6b
git cherry-pick a1b2c3d..e4f5a6b
git cherry-pick -x e4f5a6b
The -x flag appends (cherry picked from commit e4f5a6b) to the message — I always use it when back-porting fixes to release branches, so the trail is auditable later.
git cherry-pick --no-commit e4f5a6b
When cherry-pick conflicts
Like merge and rebase, cherry-pick pauses on conflicts. Fix the files, then:
git add .
git cherry-pick --continue # finish
# or
git cherry-pick --abort # give up, back to the starting state
Every cherry-pick creates a second copy of a change with a different hash. That's fine for hotfixes and back-ports, but if you find yourself cherry-picking constantly between long-lived branches, the branching strategy is the real problem.
Interactive Rebase: Rewrite Your Branch's Story
My branches while I'm working look like this:
9f8e7d6 fix lint
8e7d6c5 actually fix the test
7d6c5b4 fix test
6c5b4a3 wip
5b4a3f2 Add password reset endpoint
Nobody reviewing my PR needs to see that archaeology. Interactive rebase lets me rewrite those five commits into one or two meaningful ones before pushing.
git rebase -i HEAD~5
Git opens your editor with a todo list — one line per commit, oldest first:
pick 5b4a3f2 Add password reset endpoint
pick 6c5b4a3 wip
pick 7d6c5b4 fix test
pick 8e7d6c5 actually fix the test
pick 9f8e7d6 fix lint
You edit the list, save, and close — Git then replays the commits according to your instructions.
The commands you'll actually use
| Command | Short | What it does |
|---|---|---|
pick | p | Keep the commit as-is |
reword | r | Keep the commit, edit its message |
squash | s | Melt into the previous commit, combine messages |
fixup | f | Melt into the previous commit, discard this message |
edit | e | Pause here so you can amend the commit |
drop | d | Delete the commit entirely |
Example: squash the mess into one commit
pick 5b4a3f2 Add password reset endpoint
fixup 6c5b4a3 wip
fixup 7d6c5b4 fix test
fixup 8e7d6c5 actually fix the test
fixup 9f8e7d6 fix lint
Save and close. Result:
git log --oneline
# 3a2b1c0 Add password reset endpoint
Five commits became one, keeping only the first message. Had I used squash instead of fixup, Git would have opened the editor to let me combine all five messages into a new one.
Example: reorder commits
The todo list is replayed top to bottom, so reordering lines reorders history. Moving a commit line up or down in the file moves it in the branch. Just be aware that reordering commits that touch the same files can cause conflicts — Git will pause and let you resolve them, same as any rebase.
Fixup commits and --autosquash
The workflow I use most: a reviewer asks for a change to a specific commit in my PR. Instead of a address review comments commit, I make a fixup commit targeted at the original:
git add .
git commit --fixup 5b4a3f2
This creates a commit titled fixup! Add password reset endpoint. Later, one command folds every fixup into its target automatically:
git rebase -i --autosquash main
Git pre-arranges the todo list — fixups already moved under their targets and marked fixup. I just save and close.
git config --global rebase.autosquash true
Then plain git rebase -i main handles fixup commits automatically.
Pushing After a Rewrite
Both cherry-pick cleanup and interactive rebase rewrite commits, so if the branch was already pushed, a normal git push gets rejected. Update the remote with:
git push --force-with-lease
Never a bare --force. The --force-with-lease version refuses to push if someone else added commits to the branch since you last fetched — it protects teammates' work from being silently wiped out.
Only rewrite commits on your own feature branches. Never interactively rebase main, develop, or any branch other people build on. Rewriting shared history forces everyone else into painful recovery work.
Quick Reference
# Cherry-pick
git cherry-pick <hash> # copy a commit here
git cherry-pick -x <hash> # copy and record the source
git cherry-pick A..B # copy a range
git cherry-pick --continue # after resolving conflicts
git cherry-pick --abort # bail out
# Interactive rebase
git rebase -i HEAD~5 # rewrite the last 5 commits
git rebase -i main # rewrite everything since main
git commit --fixup <hash> # targeted fix for an earlier commit
git rebase -i --autosquash main # fold fixups automatically
git rebase --abort # bail out mid-rebase
# Afterwards, if the branch was already pushed
git push --force-with-lease