Merge vs Rebase
Merge joins histories. Rebase recreates commits on a different base.
Introduction
git merge and git rebase both bring a feature branch up to date or get it ready to land. Merge keeps the commits you have and records where two histories met. Rebase replaces a line of commits so it starts somewhere else.
The choice shapes your history and how you work with others. It has no bearing on whether the final files are right.
Understanding the Concept
A merge commit has two or more parents. In a normal two-branch merge, the first parent is where your branch was and the second is the tip you merged in. Every existing commit keeps its object ID.
Definition
Merge Commit
A commit with two or more parents that records the point where separate lines of history were combined.
A rebase computes patches from commits on the current branch, applies them to a new base in order, and moves the branch ref to the new commits. Their object IDs change because their parent history changes.
Definition
Fast-forward
A ref update that moves a branch pointer to a descendant commit without creating a merge commit.
| Merge | Rebase | |
|---|---|---|
| Existing commits | Preserved | Replayed commits are replaced |
| History | Retains branch topology | Produces a linearized branch |
| Collaboration | Safe for shared history | Requires coordination after sharing |
| Conflicts | Usually resolved in one merge result | Can recur as each commit is replayed |
| Integration record | Merge commit when not fast-forwarded | No integration commit from rebase itself |
A merge can fast-forward when your branch is an ancestor of the one you are merging. Git just slides the branch ref along and makes no merge commit. Use --no-ff when the project wants an explicit integration commit.
Applying It in Practice
Merge main into a shared feature branch without rewriting its commits:
git fetch origin
git switch feature/my-work
git merge origin/main
Rebase a private feature branch onto current main:
git fetch origin
git switch feature/my-work
git rebase origin/main
If that branch already exists on a remote and its rewrite is agreed, force push it with a lease:
git push --force-with-lease origin feature/my-work
Integrate a branch with an explicit merge commit:
git switch main
git merge --no-ff feature/my-work
A squash merge creates one new commit containing the branch's combined file changes. It does not create a merge commit, so Git does not record the feature branch as a parent:
git switch main
git merge --squash feature/my-work
git commit -m "Add user authentication"
Hosting platforms also offer rebase-and-merge. That option replays pull-request commits onto the target branch and usually creates new object IDs. Check the platform's exact behavior before making policy depend on it.
Engineering Considerations
Merge is the safe choice on a shared branch, because it replaces nothing. The graph keeps both the development history and the moment things joined. A first-parent log shows what landed on main without opening every side branch:
git log --first-parent --oneline main
Rebase is good for tidying private work and for moving a branch onto the base it should have had. A straight line is easier to scan, but you lose the shape of the original branches. Never rewrite commits colleagues have built on unless everyone agrees how to recover. A stack of dependent branches needs that agreement on every rebase.
git bisect works on straight and merged histories alike. A merge commit can muddy a regression when neither parent reproduces it on its own, and a squash can bury the step that caused the bug. What makes history useful is commits that build and stay focused, not the shape of the graph.
git blame also works across merges. Options such as -M and -C help detect moved or copied lines. Rebase does not automatically make blame more accurate.
Scaling and Operations
Set one integration policy for each protected branch. Common choices are:
- merge commits for an explicit first-parent release history
- squash merges for one target-branch commit per pull request
- rebase-and-merge for preserving individual pull-request commits in a linear history
Each option throws away something different. Merge commits keep the shape. Squash keeps the combined change but loses where each commit began and ended. Rebase-and-merge keeps those boundaries but gives every commit a new identity.
Block force pushes on shared branches. If feature branches can be rewritten, say so out loud, so reviewers know whether it is safe to build on one.
After a big rebase, use git range-diff to check the rewritten series still says what you meant. Run the full test suite after you resolve conflicts, whichever strategy you picked. When the same conflict keeps returning on a long-lived branch, git rerere can reuse the resolution you already chose.
Next Steps
- Cherry-pick vs Rebase: distinguish selected patch copying from branch rewriting
- What are Stacked PRs?: manage branches that depend on one another
- Stacked PR Workflow: apply rebasing to a pull-request stack
- Parallel Development Workflow: combine integration policy with concurrent branch work