The problem: diverged branches
Imagine you are working on a feature/login branch. While you were building the login page, a teammate pushed a security fix and a dependency update to main. Now the two branches have diverged — each has commits the other does not.
The graph above shows the situation. main moved forward with two commits (security fix, dependency update), and feature/login has its own two commits (style login form, add validation). They share a common ancestor at Release v1.0.
You need to bring these branches back together. Git gives you two strategies: merge and rebase. They achieve the same goal — combining work from two branches — but they do it in very different ways, and the resulting history looks different.
How merge works
A merge takes the two diverged branch tips and their common ancestor, and creates a new merge commit that ties everything together. This is called a three-way merge because Git looks at three snapshots: the common ancestor, the tip of the current branch, and the tip of the branch being merged.
Hover over each part to see what it does
After the merge, the history looks like this:
Notice the merge commit at the top. It has two parents — one from each branch. This is what makes it a merge commit. All the original commits remain exactly as they were: their hashes, dates, and messages are unchanged.
Key characteristics of merge
- Non-destructive — existing commits are never modified. Their hashes stay the same.
- Creates a merge commit — adds one extra commit to the history that records when the branches were combined.
- Preserves context — you can always see when a feature branch was integrated and trace both lines of development.
- Can make history noisy — in active repositories with many branches, frequent merge commits can clutter the graph.
How rebase works
Instead of creating a merge commit, rebase takes your feature branch commits and replays them on top of the target branch. The result is a perfectly linear history — as if the feature work was done after all the main branch changes, not in parallel.
Hover over each part to see what it does
After the rebase, the commit graph is linear:
The feature branch commits now sit on top of the latest main commit. Notice that their hashes have changed — c3d4e5f became w8x9y0z and d4e5f6a became x9y0z1a. This is because rebase creates new commits with the same changes but different parents, which means different hashes.
Handling conflicts during rebase
Because rebase replays commits one by one, you might encounter conflicts at each step. Git pauses the rebase and lets you resolve the conflict before continuing:
This is different from merge conflicts. With a merge, you resolve all conflicts at once in the merge commit. With a rebase, you might need to resolve conflicts multiple times — once for each commit being replayed. This can be more tedious, but it has an upside: each commit in the rebased branch is guaranteed to be a clean, working state.
Key characteristics of rebase
- Produces linear history — no merge commits, no forks in the graph. The history reads like a simple list.
- Rewrites commit hashes — every replayed commit gets a new hash. The content is the same, but the identity changes.
- Conflicts per commit — you resolve conflicts as each commit is replayed, which can mean more conflict resolutions but cleaner individual commits.
- Loses branch topology — after rebase, you cannot tell that the feature work was done in parallel. The history looks sequential.
Side-by-side comparison
Here is a direct comparison of the two strategies:
| Aspect | git merge | git rebase |
|---|---|---|
| History shape | Non-linear (diamond/fork pattern) | Linear (straight line) |
| Extra commits | Creates a merge commit | No extra commits |
| Original hashes | Preserved | Changed (new hashes) |
| Conflict resolution | Once, in the merge commit | Per replayed commit |
| Safe for shared branches | Yes | No (rewrites history) |
| Best for | Integrating completed features | Keeping local branches up to date |
Neither strategy is universally better. They solve different problems and are often used together in the same workflow.
When to use each
Use merge when...
- Integrating a completed feature into main — the merge commit serves as a clear marker that a feature was completed and integrated.
- Working on shared branches — if other people have pulled the branch, merge is the safe choice because it does not rewrite history.
- You want an audit trail — merge commits record exactly when branches were combined, which is useful for debugging and compliance.
- Pull request workflows — most teams merge PRs rather than rebase them, because it preserves the review context.
Use rebase when...
- Keeping a feature branch up to date — while you work on a feature, you can regularly rebase onto
mainto incorporate upstream changes and avoid a big conflict at the end. - Cleaning up before merging — use
git rebase -i(interactive rebase) to squash, reorder, or edit commits before submitting a pull request. - You prefer a linear history — some teams require a linear main branch for readability. Rebase before merge achieves this.
- Working on a local, unpushed branch — if no one else has seen your commits, rewriting history is perfectly safe.
A common workflow: rebase then merge
Many teams combine both strategies. While developing a feature, you rebase your branch onto main to stay up to date. When the feature is done, you merge it into main (often via a pull request). This gives you the best of both worlds: clean individual commits from rebase, plus a merge commit marking when the feature landed.
git checkout -b feature/login\n# ... write code, make commits ...git fetch origin\ngit rebase origin/maingit rebase -i origin/main# On GitHub / in Komitly:\n# Create PR -> Review -> MergeMerge preview in Komitly
One of the trickiest parts of merging is not knowing what will happen until you actually run the command. Will it be a clean merge? Will there be conflicts? Which files are affected?
Komitly solves this with a merge preview that runs a dry-run merge in memory before touching your working directory. When you initiate a merge, Komitly first shows you a prediction of the result:
The merge preview categorizes every affected file:
- Clean merge (green) — the file was modified in both branches but Git can combine the changes automatically
- Conflict (red) — the file has overlapping changes that require manual resolution
- Only in source (purple) — the file was only changed in the feature branch
- Only in target (blue) — the file was only changed in the target branch
If conflicts are detected, you can see exactly which files will conflict before running the merge. This lets you prepare — you might choose to resolve issues in the feature branch first, or switch to a rebase strategy to handle conflicts one commit at a time.
Komitly also supports visual rebase planning. You can drag and drop commits to reorder them, mark commits for squash, and see a conflict prediction for each step of the rebase — all before executing it.
The golden rule of rebase
Never rebase commits that have been pushed to a shared remote branch.
This is the single most important rule to internalize about git rebase. The reason comes down to what rebase actually does: it creates new commits with new hashes. If someone else has already pulled the original commits, their history and yours will diverge in a confusing way.
Here is what happens when you violate this rule:
After a rebase, git push fails because your local history no longer matches the remote. The only way to push is with --force (or the safer --force-with-lease). But if a teammate already has the old commits, force-pushing will cause them to see duplicate or missing commits, broken merges, and general confusion.
When force-push is acceptable
Force-pushing after a rebase is fine in exactly one scenario: when you are the only person working on the branch. This is common for personal feature branches that exist only as draft pull requests. In that case:
# Safe: use --force-with-lease instead of --force\n# It will fail if someone else pushed to the branch\ngit push --force-with-lease--force-with-lease is a safer alternative to --force. It checks that the remote branch is still where you expect it to be. If someone else pushed a commit since your last fetch, the push is rejected — protecting you from accidentally overwriting their work.
A simple decision framework
git rebase maingit rebase main\ngit push --force-with-leasegit merge mainMerge and rebase are not competitors — they are complementary tools. Rebase keeps your local work clean and up to date. Merge records the integration of completed work into shared branches. Use the right tool for each situation, respect the golden rule, and your Git history will be both clean and trustworthy.