Stackvora.tech

Git Rebase vs Merge: When to Use Each

Understanding the fundamental differences between git rebase and git merge is crucial for maintaining a clean, coherent, and manageable Git history in…

Understanding the fundamental differences between git rebase and git merge is crucial for maintaining a clean, coherent, and manageable Git history in collaborative development environments. While both commands integrate changes from one branch into another, they achieve this through distinct mechanisms, leading to different commit graph structures and implications for team workflows. Choosing the appropriate strategy depends on factors like branch longevity, team collaboration model, and desired history linearity.

Understanding Git Merge

git merge integrates changes by creating a new "merge commit" that has two (or more) parent commits. This commit explicitly records the act of combining two branches. The original commits from both branches remain intact, preserving the historical context of their development paths.

Fast-Forward Merge

A fast-forward merge occurs when the branch you are merging into (e.g., main) has not diverged from the branch you are merging from (e.g., feature-A). In this scenario, Git simply moves the pointer of the target branch forward to the tip of the source branch. No new merge commit is created. This is the default behavior if a fast-forward is possible.


git switch main
git merge feature-A # If main has no new commits since feature-A branched off

To explicitly prevent a fast-forward merge and always create a merge commit, even when a fast-forward is possible, use the --no-ff flag:


git switch main
git merge --no-ff feature-A

This is often preferred for release branches or integration into main to clearly mark the point of integration, making it easier to revert a complete feature if needed.

Three-Way Merge

When the target branch has new commits that are not present in the source branch (i.e., the branches have diverged), Git performs a three-way merge. It identifies a common ancestor commit and then combines the changes from both branches since that ancestor into a new merge commit. This new commit has two parents: the tip of the target branch and the tip of the source branch.


git switch main
git merge feature-B # If main has new commits not in feature-B

A three-way merge is Git's default behavior when a fast-forward is not possible. Merge conflicts are common in this scenario and must be resolved manually before the merge commit can be finalized.

Understanding Git Rebase

git rebase rewrites the commit history. Instead of creating a new merge commit, it takes a series of commits from one branch and reapplies them one by one onto another base branch. Conceptually, it moves the base of your feature branch to a new starting point.


git switch feature-X
git rebase main

When you run git rebase main from feature-X, Git does the following:

  1. Finds the common ancestor of feature-X and main.
  2. Stashes the changes from each commit on feature-X since that common ancestor.
  3. Moves the feature-X branch pointer to the tip of main.
  4. Reapplies each stashed commit from feature-X, one by one, onto the new base.

This results in a linear history where it appears as if you developed your feature-X branch directly from the latest main, even if you branched off much earlier. Each replayed commit gets a new SHA-1 hash because its parent commit has changed. This is the key reason why rebasing "rewrites history."

Interactive Rebase (git rebase -i)

Interactive rebase provides powerful capabilities to modify commits during the rebase process. This is particularly useful for cleaning up local history before sharing it with others.


git rebase -i HEAD~3 # Interactively rebase the last 3 commits

This command opens an editor with a list of commits and actions you can perform on each:

  • pick: Use the commit as is.
  • reword: Use the commit, but edit the commit message.
  • edit: Use the commit, but pause to amend the commit (e.g., add/remove files).
  • squash: Combine this commit with the previous one.
  • fixup: Combine this commit with the previous one, discarding this commit's log message.
  • drop: Remove the commit.

Interactive rebase is excellent for:

  • Squashing multiple small, "work-in-progress" commits into a single, meaningful commit.
  • Reordering commits to tell a clearer story.
  • Splitting commits that do too much.
  • Editing commit messages.
  • Removing unwanted commits.

When to Use Git Merge

Use git merge when:

  1. Integrating feature branches into long-lived branches (e.g., main, develop): A merge commit clearly indicates when a feature was fully integrated. This preserves the branch's history, showing the actual development path.
  2. Preserving a complete and accurate history: Merge commits act as markers in the commit graph, indicating exactly when a branch was incorporated. This can be crucial for auditing or debugging.
  3. You are integrating branches that have already been pushed to a shared remote repository: Merging is generally safer for shared history because it doesn't rewrite existing commits. Rewriting shared history with rebase can cause significant problems for collaborators.
  4. Your team prefers a non-linear history: Some teams appreciate the clear visibility of when and where branches diverged and converged, finding the merge commit graph easier to follow for project structure.

Example Merge Workflow:


# On your feature branch
git switch my-feature
git add .
git commit -m "Implement new UI component"
git push origin my-feature

# Once feature is complete and reviewed, switch to main
git switch main
git pull origin main # Ensure main is up-to-date
git merge --no-ff my-feature # Merge feature into main, always creating a merge commit
# Resolve conflicts if any
git push origin main
git branch -d my-feature # Delete local feature branch
git push origin --delete my-feature # Delete remote feature branch

When to Use Git Rebase

Use git rebase when:

  1. Cleaning up local feature branches before pushing them: Before sharing your work, rebase can condense messy work-in-progress commits into a clean, linear series of meaningful commits. This makes your feature branch's history much easier for others to review and understand.
  2. Keeping your feature branch up-to-date with main/develop: Regularly rebasing your local feature branch onto the latest main helps you resolve conflicts early and keep your branch's changes relevant to the current codebase. This prevents a "big bang" merge conflict at the end of development.
  3. You desire a linear, "clean" project history: Rebasing eliminates extraneous merge commits, resulting in a straight line of commits, which some teams find aesthetically pleasing and easier to navigate with commands like git log --oneline.
  4. Working on private branches: Because rebase rewrites history, it's safest to use it only on branches that have not been pushed to a shared remote, or on branches that only you are working on.

Example Rebase Workflow (Local Cleanup and Update):


# On your feature branch, before pushing
git switch my-feature
git fetch origin main # Get the latest main without switching
git rebase -i origin/main # Interactively rebase onto the remote main branch
# In the editor, squash/reword/fixup commits to clean up history
# Resolve conflicts as they appear for each replayed commit

# After cleaning up, ensure main is updated
git switch main
git pull origin main

# Switch back to your feature branch and rebase again to pick up any latest main changes
git switch my-feature
git rebase main # Rebase onto the now-up-to-date local main

# Now, push your clean feature branch (if it's the first push, or if you force push if history changed)
git push -u origin my-feature # First push
# If you rebased a branch that was already pushed, you'd need a force push:
# git push --force-with-lease origin my-feature

Warning: Never rebase a public/shared branch. If you rebase a branch that others have already pulled, you will create a divergent history. When others try to pull, Git will show an error, and they will need to reset their local branch or use git pull --rebase, which can be confusing and lead to lost work. The only exception is if everyone involved explicitly agrees to reset their history.

Choosing Between Rebase and Merge: A Comparison

Feature Git Merge Git Rebase
History Linearity Non-linear, preserves original branch structure. Linear, rewrites history to appear sequential.
Commit IDs Original commit IDs are preserved. A new merge commit is created. New commit IDs generated for replayed commits. No merge commits.
Safety for Shared Branches Safe for shared/public branches. Unsafe for shared/public branches (rewrites history).
Conflict Resolution Resolve conflicts once, for the merge commit. May resolve conflicts multiple times, for each replayed commit.
Use Case (Typical) Integrating completed features into main, preserving historical context. Cleaning up local feature branch history, keeping feature branch updated.
Rollback/Revert Easy to revert a feature using git revert -m 1 <merge-commit-hash>. More complex to revert a series of rebased commits (requires reverting each replayed commit).

Rule of Thumb

The established best practice for many teams can be summarized as:

  • Rebase your local feature branch frequently onto main (or develop) to keep it up-to-date and maintain a clean, linear history *before* pushing or making a pull request. Use git rebase -i to clean up your own local commits.
  • Merge into main (or develop) when integrating a feature that is complete, reviewed, and ready to be part of the mainline history. Use --no-ff for important merges to explicitly record the integration point. This ensures that the history of your main branches accurately reflects the points where features were integrated.

This approach combines the benefits of a clean, linear feature branch history (thanks to rebase) with a clear, auditable integration history on the main branch (thanks to merge commits).

Common Pitfalls

  • Rebasing a Shared Branch: As stressed, never rebase a branch that others have already pulled from. This creates divergent histories and forces collaborators into complex recovery steps, potentially leading to lost work. If you accidentally rebase and force push a shared branch, communicate immediately with your team.
  • Ignoring Conflicts During Rebase: When rebasing, Git replays commits one by one. If a conflict occurs during one of these replays, you must resolve it, git add the resolved files, and then git rebase --continue. Forgetting to git add or attempting to git commit instead of git rebase --continue will cause issues. If you get lost, git rebase --abort is your friend.
  • Losing Commits with Interactive Rebase: Be careful with drop or aggressive squash/fixup operations during interactive rebase. If you accidentally discard commits you need, git reflog can help you find them, but it's better to be cautious.
  • Not Updating Remote Before Rebase: Always ensure your local tracking branch for main (or whatever base branch you're rebasing onto) is up-to-date with the remote before starting a rebase. For example, git fetch origin main followed by git rebase origin/main (or git pull --rebase origin main) is safer than just git rebase main if your local main isn't current.

Back to the knowledge base · Ask the AI assistant