documentation/blog/2025-07-09-git-merge.md
As a developer, you live and breathe branches. You create them for new features, bug fixes, and experiments. But at some point, that isolated work needs to be integrated back into the main line of development. In the world of Git, two powerful commands stand ready to help: git merge and git rebase.
Choosing between them is one of the most common points of debate and confusion among Git users. Both can achieve the goal of combining work from different branches, but they do so with fundamentally different philosophies and results. Understanding this difference is not just academic—it directly impacts your project's history, your team's workflow, and your ability to debug issues down the line.
This guide will provide a deep dive into both commands. We'll explore how they work under the hood, visualize their impact on your commit history, discuss how to handle conflicts, and introduce powerful related techniques like interactive rebase. By the end, you'll be able to confidently choose the right tool for the right job.
git merge - The Story Preservergit merge is the workhorse of branch integration. Its primary goal is to take the commits from a source branch and weave them into a target branch. The key takeaway for merge is that it preserves history as it happened.
git merge WorksLet's imagine you're working on a new feature. You create a branch called feature/login from the main branch.
Initial State:
Your main branch has two commits.
A---B (main)
You create your feature branch from here.
A---B (main)
\
(feature/login)
Now, you add a commit to your feature branch.
A---B (main)
\
C (feature/login)
While you were working, a teammate pushed a new commit (D) to the main branch. The project history has now diverged.
D (main)
/
A---B
\
C (feature/login)
To integrate your feature, you switch to the target branch (main) and run the merge command:
# Switch to the branch you want to merge INTO
git switch main
# Merge the feature branch INTO main
git merge feature/login
Git performs a three-way merge. It looks at three commits to make its decision:
main branch (D)feature/login branch (C)B)It then creates a brand new commit, called a merge commit. This special commit has two parents (C and D) and represents the reconciliation of these two divergent histories.
The Result:
The history now looks like this, with the new merge commit E.
D-------E (main)
/ /
A---B---------C (feature/login)
What if the main branch had not changed while you were working on your feature?
A---B (main)
\
C (feature/login)
In this case, when you run git merge feature/login, Git sees that main is a direct ancestor of feature/login. It doesn't need to create a merge commit. It simply "fast-forwards" the main pointer to point to the same commit as your feature branch.
A---B---C (main, feature/login)
This results in a perfectly linear history.
Conflicts happen when both branches have changed the same part of the same file. When you try to merge, Git will pause and tell you there's a conflict.
$ git merge feature/login
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.
To resolve it:
git status will show you which files are in conflict.<<<<<<< HEAD
// Code from main branch
const greeting = 'Hello, World!';
=======
// Code from your feature branch
const greeting = 'Hi, Universe!';
>>>>>>> feature/login
<<<<<<<, =======, and >>>>>>> markers.git add <conflicted-file-name>git commit. Git will see you're in the middle of a merge and create the merge commit for you, completing the process.Pros of git merge:
Cons of git merge:
git log --graph can become a complex, branching mess of merge commits that is hard to read.git rebase - The History Rewritergit rebase offers a different way to combine work. Instead of creating a merge commit, its goal is to create a clean, linear history. It achieves this by rewriting commits.
git rebase WorksLet's use the same divergent history scenario:
D (main)
/
A---B
\
C (feature/login)
To use rebase, you check out the branch you want to move (feature/login) and rebase it onto the target (main).
# Switch to the branch with your work
git switch feature/login
# Rebase it onto the main branch
git rebase main
Git does the following:
B).C) as temporary patches.feature/login branch to the latest commit on main (D).main.The Result:
The history becomes perfectly linear. Notice that C has become C', indicating it's a new commit with the same changes.
A---B---D---C' (feature/login)
\
(main)
The old commit C is now orphaned and will eventually be garbage collected by Git. Now, feature/login can be fast-forward merged into main.
git switch main
git merge feature/login # This will be a simple fast-forward
Because rebase rewrites history by creating new commits, there is one critical rule you must always follow:
Do not rebase a branch that is shared publicly with others.
If you rebase a branch that your teammates have pulled, you are creating a divergent history. Their repository still has the old commits, while yours has the new, rewritten ones. When you both try to push, Git will become hopelessly confused. Only rebase branches that exist locally on your machine.
One of the most powerful features of rebase is its interactive mode: git rebase -i. This allows you to clean up your own local commit history before you share your work with others.
Imagine you're on your feature branch and your history looks messy:
A---B---C---D---E (feature/login)
C: "WIP"D: "fixed a typo"E: "actually implement the feature"Before you merge this, you can clean it up.
# Rebase interactively against the commit you branched from (B's hash, or main)
git rebase -i main
This opens a text editor with a list of your commits:
pick <hash_C> WIP
pick <hash_D> fixed a typo
pick <hash_E> actually implement the feature
# Commands:
# p, pick = use commit
# s, squash = use commit, but meld into previous commit
# ... and others like reword, edit, drop
You can change pick to squash to combine the first two commits into the third one.
pick <hash_C> WIP
squash <hash_D> fixed a typo
squash <hash_E> actually implement the feature
Save and close. Git will then prompt you to write a new, clean commit message for the combined commit. Your history now looks pristine:
A---B---F (feature/login)
Where F is a single, clean commit titled "Implement User Login Feature".
Pros of git rebase:
Cons of git rebase:
| Feature | git merge | git rebase |
|---|---|---|
| History Shape | Branching, graph-like (non-linear) | Perfectly linear |
| History Integrity | Preserves original commits; adds a merge commit | Rewrites commits, creating new ones |
| Collaboration | Safe for public, shared branches | Safe only for local, private branches |
| Ease of Use | Simpler concept, fewer risks | More complex, requires understanding the "Golden Rule" |
| Conflict Resolution | Resolve all conflicts at once, then commit | Resolve conflicts one commit at a time as they are replayed |
There is no single "better" command. The choice depends entirely on your team's workflow and preferences.
Choose git merge when:
Choose git rebase when:
A popular and effective workflow is to use rebase locally to clean up your own work and then merge it into the shared branch. This gives you the best of both worlds: a clean feature history combined with a merge commit that signals the integration of a complete feature.
The best way to learn is to try. Create a test repository, make some branches, and see for yourself how merge and rebase shape the history. Gaining mastery over these two commands will make you a more effective and collaborative developer.