Skip to main content

Merge, Rebase & Cherry-Pick

Integrating Changes and Rewriting History the Right Way

merge, rebase, and cherry-pick are the three primary mechanisms for moving commits between branches in Git. Understanding when and how to use each is essential for maintaining a clean, readable commit history in collaborative projects.


Quick Comparison

Aspectmergerebasecherry-pick
What it doesCombines branches with a merge commitReplays commits onto a new baseApplies specific commits to another branch
HistoryPreserves branch topologyCreates a linear historyCopies individual commits
Rewrites historyNoYesNo (creates new commits)
Safe on shared branchesYesNoYes
Best forIntegrating feature branchesCleaning up local commitsHotfix porting, selective backports

Git Merge

Merge joins two development histories together. It is the most common way to integrate changes from one branch into another.

Fast-Forward Merge

When the target branch has no new commits since the source branch diverged, Git performs a fast-forward merge — it simply moves the branch pointer forward.

git checkout main
git merge feature
# Result: main now points to D, no merge commit created

Three-Way Merge (No Fast-Forward)

When both branches have diverged, Git creates a merge commit that ties the two histories together. A merge commit has two parent commits.

git checkout main
git merge feature
# Creates a merge commit with parents B (main) and D (feature)

Force a Merge Commit with --no-ff

Even when a fast-forward is possible, you can force a merge commit to preserve the branch topology.

git merge --no-ff feature
Why --no-ff?

A merge commit acts as a bookmark that groups all commits from a feature branch. This makes git revert easier — you can revert an entire feature by reverting a single merge commit:

git revert -m 1 <merge-commit-hash>

Resolving Merge Conflicts

When both branches modify the same lines, Git cannot automatically merge and will pause for manual resolution.

git merge feature
# CONFLICT (content): Merge conflict in src/app.py
# Automatic merge failed; fix conflicts and then commit.

Resolution steps:

  1. Open the conflicted file and look for conflict markers:
<<<<<<< HEAD (main)
def greet(name):
return f"Hello, {name}!"
=======
def greet(name, title):
return f"Hello, {title} {name}!"
>>>>>>> feature
  1. Edit the file to the desired final state, removing the markers.
  2. Stage and complete the merge:
git add src/app.py
git merge --continue
# Or: git commit (older Git versions)

Abort a merge if things go wrong:

git merge --abort

Git Rebase

Rebase re-applies your commits on top of another branch, producing a linear history. Instead of creating a merge commit, it moves the entire branch to begin at a new base.

Basic Rebase

# Before rebase: feature diverges from B
git checkout feature
git rebase main

# After rebase: feature commits C', D' are replayed on top of F
# The branch is now linear with main
Golden Rule of Rebase

Never rebase commits that have been pushed to a shared branch.

Rebasing rewrites commit hashes. If others have based work on the original commits, their history will diverge, causing confusion and duplicate commits. Only rebase your local, unpublished branches.

Interactive Rebase (-i)

Interactive rebase is a powerful tool for cleaning up commits before merging. It lets you reorder, squash, edit, or drop commits.

git rebase -i HEAD~4

This opens an editor with the last 4 commits:

pick a1b2c3d feat: add login form
pick e4f5g6h fix: typo in login form
pick i7j8k9l feat: add form validation
pick m0n1o2p fix: validation edge case

Available Operations

CommandEffect
pickKeep the commit as-is
rewordKeep the commit but edit the message
editPause at this commit for amending
squashMerge into the previous commit (combine messages)
fixupMerge into the previous commit (discard this message)
dropRemove the commit entirely
execRun a shell command after applying the commit

Common Patterns

Squash related fix commits:

pick a1b2c3d feat: add login form
fixup e4f5g6h fix: typo in login form
pick i7j8k9l feat: add form validation
fixup m0n1o2p fix: validation edge case

Result: Two clean commits — feat: add login form and feat: add form validation.

Reorder commits:

pick i7j8k9l feat: add form validation
pick a1b2c3d feat: add login form

Just reorder the lines in the editor.

Conflict During Rebase

Interactive rebase can produce conflicts when commits are reordered or squashed. Resolve each conflict, then:

git add <file>
git rebase --continue

To abort at any point:

git rebase --abort

Rebase vs Merge: When to Use Which

ScenarioRecommendation
Feature branch → main (team uses merge)merge --no-ff
Feature branch → main (team uses rebase/squash)rebase -i then merge
Updating feature branch with latest mainrebase main (local only)
Shared/public branch integrationmerge (never rebase)
Preserving exact history for auditmerge
Clean, linear history desiredrebase

Git Cherry-Pick

Cherry-pick applies the changes from a specific commit onto your current branch. It creates a new commit with a new hash, even though the changes are identical.

Basic Usage

# Apply a single commit to the current branch
git cherry-pick <commit-hash>

# Apply multiple commits
git cherry-pick <hash1> <hash2> <hash3>

# Apply a range of commits
git cherry-pick <start-hash>..<end-hash>

Use Cases

1. Hotfix backport

A bug fix was committed to main but also needed in the release/v1.0 branch:

git checkout release/v1.0
git cherry-pick <hotfix-commit-hash>

2. Pull a single feature to another branch

A commit from one feature branch is needed in another, without merging the entire branch:

git checkout feature/payment
git cherry-pick <useful-commit-from-feature-auth>

3. Recover a lost commit

If you accidentally deleted a branch or dropped a commit during rebase:

git reflog
# Find the lost commit hash
git cherry-pick <lost-hash>

Cherry-Pick Options

FlagPurpose
-n / --no-commitApply changes to the working tree without creating a commit
-xAdd (cherry picked from commit ...) to the commit message
--edit / -eOpen editor to modify the commit message before committing
-m <parent>Specify the parent number when cherry-picking a merge commit
Use -x for Traceability

When cherry-picking between shared branches, always use -x to record where the commit originated. This helps future debugging:

git cherry-pick -x <hash>
# Commit message will include:
# (cherry picked from commit abc1234)

Resolving Cherry-Pick Conflicts

Cherry-pick can produce conflicts just like merge:

git cherry-pick <hash>
# error: could not apply abc1234...

Resolution steps:

  1. Resolve conflicts in the affected files.
  2. Stage and continue:
git add <file>
git cherry-pick --continue

Or abort entirely:

git cherry-pick --abort

Decision Flowchart

Not sure which command to use? Follow this guide:


Practical Recipes

Update Feature Branch with Latest main

# Option A: Rebase (clean history, local branch only)
git checkout feature/my-feature
git fetch origin
git rebase origin/main

# Option B: Merge (preserves history, safe for shared branches)
git checkout feature/my-feature
git fetch origin
git merge origin/main

Squash a Feature Branch Before Merging

git checkout main
git merge --squash feature/my-feature
git commit -m "feat: add user authentication system"

This takes all commits from feature/my-feature and stages them as a single changeset.

Cherry-Pick a Merge Commit

Merge commits have two parents. You must specify which parent to use with -m:

# -m 1 means "the changes relative to the first parent (main)"
git cherry-pick -m 1 <merge-commit-hash>

Summary

CommandCore PurposeKey Rule
mergeIntegrate branches, preserving topologyDefault choice for shared branches
rebaseReapply commits for linear historyOnly rebase local/unpublished commits
cherry-pickApply specific commits to another branchUse -x for traceability

Choose based on your workflow: merge for safety and traceability, rebase for cleanliness and readability, cherry-pick for surgical precision.