Git: Rebasing and Cherry-picking
Rebase and cherry-pick are tools for rewriting history — placing commits onto a new base, or selectively applying commits from one branch to another. They produce cleaner history than merge in many cases, but they rewrite commit SHAs. Never rebase commits that others have already pulled.
Commands Covered
git rebase · git cherry-pick · git range-diff · git am · git format-patch
git rebase
Moves or replays commits from one branch on top of another. The result is a linear history without merge commits.
# Rebase current branch onto main $ git switch feature-login $ git rebase main # Takes all commits on feature-login (since it diverged from main) # and replays them one-by-one on top of current main tip. # Rebase onto a specific commit $ git rebase abc1234 # Rebase a specific branch onto another without switching $ git rebase main feature-login # Replays feature-login commits on top of main # Interactive rebase — edit, squash, reorder, drop commits $ git rebase -i HEAD~5 # edit the last 5 commits $ git rebase -i main # edit all commits since diverging from main
Interactive Rebase
Opens your editor with a list of commits. Change the verb at the start of each line:
# Example interactive rebase editor: pick abc1234 Add login form pick def5678 Fix typo in login form pick ghi9012 Add session handling pick jkl3456 WIP: half-done pick mno7890 Fix session bug # Available commands: # pick = use the commit as-is # reword = use the commit, but edit the message # edit = use commit, then pause to amend it # squash = meld into the previous commit (keeps both messages) # fixup = meld into previous commit, discard this commit's message # drop = remove this commit entirely # exec = run a shell command after this commit # Squash the typo fix into the login form commit: pick abc1234 Add login form squash def5678 Fix typo in login form # <-- change pick to squash pick ghi9012 Add session handling drop jkl3456 WIP: half-done # <-- drop the WIP commit pick mno7890 Fix session bug
Handling Rebase Conflicts
# When rebase hits a conflict: $ git rebase main # CONFLICT (content): Merge conflict in src/auth.c # error: could not apply abc1234... Add login form # Fix the conflict in the file, then: $ git add src/auth.c $ git rebase --continue # Skip this commit (use carefully): $ git rebase --skip # Abort — go back to where you started: $ git rebase --abort
rebase vs. merge
| rebase | merge | |
|---|---|---|
| History shape | Linear — no merge commits | Graph with merge commits |
| Commit SHAs | Rewritten — new SHAs for replayed commits | Preserved — original SHAs intact |
| Safe for shared branches? | No — rewriting SHAs causes problems | Yes — doesn't rewrite history |
| Conflict resolution | Per commit, one at a time | All at once |
| Best for | Local cleanup before pushing; keeping feature branches current | Integrating finished work; preserving true history |
git cherry-pick
Applies the changes introduced by specific commits onto the current branch. Useful for backporting a fix to a release branch, or pulling a single commit from another branch.
# Apply a specific commit to the current branch $ git cherry-pick abc1234 # Apply multiple commits $ git cherry-pick abc1234 def5678 ghi9012 # Apply a range of commits (exclusive of abc1234) $ git cherry-pick abc1234..ghi9012 # Cherry-pick without committing (stage the changes for review) $ git cherry-pick --no-commit abc1234 $ git cherry-pick -n abc1234 # Cherry-pick a merge commit (must specify the parent) $ git cherry-pick -m 1 abc1234 # -m 1 = take the first parent's side # Cherry-pick and edit the commit message $ git cherry-pick -e abc1234 # Continue after resolving a conflict during cherry-pick $ git cherry-pick --continue # Abort a cherry-pick in progress $ git cherry-pick --abort # Typical backport workflow: $ git switch release-1.0 # switch to release branch $ git cherry-pick abc1234 # apply the bugfix commit from main $ git push origin release-1.0 # push the fix
git format-patch
Creates patch files from commits. Each commit becomes a .patch file that can be sent via email or applied elsewhere. The traditional way to contribute to projects that use mailing list workflows (Linux kernel, Git itself).
# Create patches for the last 3 commits $ git format-patch -3 # Creates: 0001-First-commit-message.patch # 0002-Second-commit-message.patch # 0003-Third-commit-message.patch # Create patches for commits on current branch not in main $ git format-patch main # Output to a directory $ git format-patch -o /tmp/patches main # Create a single file containing all patches $ git format-patch --stdout main > all.patch # Include cover letter (summary email for the patch series) $ git format-patch --cover-letter main # Edit 0000-cover-letter.patch with the summary # Number patches in a series (e.g., "v2 of a patch series") $ git format-patch -v2 main # Creates: v2-0001-Fix-login-bug.patch
git am
Applies patches created by git format-patch. "am" = apply mailbox. It recreates commits from the patch files, preserving the original author and message.
# Apply a single patch file $ git am fix-login-bug.patch # Apply all patches in a directory $ git am /tmp/patches/*.patch # Apply patches from stdin (e.g. from a pipe or email) $ git am < all.patch # Continue after resolving a conflict $ git am --continue # Skip a patch that fails (use carefully) $ git am --skip # Abort and go back to original state $ git am --abort # Apply with 3-way merge (more robust conflict handling) $ git am --3way fix-bug.patch $ git am -3 fix-bug.patch
git range-diff
Compares two commit ranges — for example, two versions of a patch series. Shows what changed between v1 and v2 of a proposed feature. Used heavily in the Git and Linux kernel mailing list workflows.
# Compare two branches that have the same base $ git range-diff main feature-v1 feature-v2 # Compare the last 5 commits before and after a rebase $ git range-diff origin/main~5..origin/main main~5..main # Show the diff of diffs (what changed in each patch) $ git range-diff --creation-factor=50 main v1 v2 # --creation-factor: how aggressively to pair new commits (0-100%) # Example output: # 1: abc1234 = 1: def5678 First commit (identical) # 2: ghi9012 ! 2: jkl3456 Second commit (changed) # ...diff of what changed in the commit... # -: ------- > 3: mno7890 New commit added in v2
dispelled