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

rebasemerge
History shapeLinear — no merge commitsGraph with merge commits
Commit SHAsRewritten — new SHAs for replayed commitsPreserved — original SHAs intact
Safe for shared branches?No — rewriting SHAs causes problemsYes — doesn't rewrite history
Conflict resolutionPer commit, one at a timeAll at once
Best forLocal cleanup before pushing; keeping feature branches currentIntegrating 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

References