Git Merge vs. Git Rebase: Understanding the Differences and Best Practices
In software development, developers often work independently on different branches in Git. Once a feature has been developed or a bug has been fixed, the changes need to be integrated into the main codebase. This process is known as branch integration.
When integrating code from one branch into another, Git provides two commands that serve a similar purpose—combining work from multiple developers into a unified codebase—but take fundamentally different approaches: Git Merge and Git Rebase.
In this article, we'll examine how each mechanism works, compare the advantages and disadvantages of both approaches, and develop a clear understanding of their benefits and potential risks when applied to different real-world scenarios.
What Is Git Merge?
Let's start with the most common workflow, which most developers are already familiar with when integrating changes across branches: Git Merge.
Suppose we want to integrate the
feature branch into the master branch. We can perform the merge using the following commands:git checkout master git merge feature
The result is a new commit on
master that connects the histories of both master and feature, representing the integration of the two branches.Merge is a straightforward, safe, and relatively convenient way to combine changes. Existing branches retain their commit histories without being rewritten. This avoids the potential pitfalls associated with rewriting commit history through rebasing.
On the other hand, merge commits can accumulate as changes are repeatedly integrated between branches. If the main branch is particularly active, the history of a
feature branch can become cluttered with merge commits. Although advanced git log options can help mitigate this issue, the resulting history may still be harder for other developers to understand.The diagram below illustrates the final result of a merge. As you can see, the development history on the
feature branch remains intact, with an additional merge commit, C7, connecting the histories.In simple terms, Git Merge combines multiple commit histories by creating a new commit that represents the integration. The original histories continue to coexist across the branches, resulting in a diamond-shaped Git graph.
As the graph becomes more complex and less linear, tracking project progress and navigating the commit history can become increasingly difficult.
What Is Git Rebase?
The second approach to integrating branches is Git Rebase. Although it serves a similar purpose to merge, it takes a fundamentally different approach and requires more care because it rewrites commit history.
Suppose we want to rebase the
feature branch onto master. We can do so using the following commands:git checkout feature git rebase master
After execution, Git takes the commits created on the
feature branch and replays them on top of the latest commit in master. Instead of creating a merge commit, Git rewrites the project's history by creating new commits corresponding to the original commits on feature.The main advantage of rebasing is a cleaner, more readable, and more linear commit history. It eliminates unnecessary merge commits, making it easier to navigate and inspect project history using commands and tools such as
git log, git bisect, and gitk.However, this approach comes with two important trade-offs: safety and traceability.
If the safety principles of rebasing aren't followed, rewriting project history can create serious problems for collaborative development workflows. Another drawback is that rebasing removes the contextual information provided by merge commits, making it harder to determine exactly when changes from an integration branch were incorporated into a feature branch.
The diagram below illustrates the final result of a rebase. Notice how commits C5 and C6 have been replayed directly on top of C4, rewriting the development history and replacing the original commits with new ones.
Comparing Git Rebase and Git Merge
The diagram below provides a visual comparison of the two approaches. Both Merge and Rebase achieve the same general goal: integrating changes between two branches.
With Merge, the total number of commits increases by one due to the additional merge commit, producing a diamond-shaped Git graph.
With Rebase, the original feature commits are replaced by new commits, resulting in a linear history without an additional merge commit.
Advantages of Merge
- Non-destructive: Merge is a non-destructive operation because it doesn't rewrite existing commits. Instead, it adds an additional commit, known as a merge commit, when necessary.
- Change integration: Merge allows developers to integrate changes from one branch into another. This is especially useful when multiple developers are working on different features that eventually need to be incorporated into the main branch.
- Multiple codebase versions: Merge allows developers to maintain multiple versions of a codebase. This is useful when older versions need to be preserved or when a separate branch is required for testing a feature.
- Change tracking: Merge preserves the history of changes made to the codebase, which is valuable for debugging, auditing, and reviewing the evolution of a project.
- Conflict resolution: Merge provides a mechanism for reconciling changes made by multiple developers to the same files.
Disadvantages of Merge
- Merge conflicts: One of the main drawbacks of Git Merge is the possibility of conflicts when multiple changes affect the same files. Resolving these conflicts can sometimes be time-consuming and difficult.
- Loss of context: When changes from two branches are merged, understanding the origin and context of individual changes can become more difficult, particularly in a history containing many merge commits.
- Complexity: As the number of branches and merge commits increases, the Git graph can become more complicated, making the project harder to maintain and branch relationships more difficult to understand.
- Dependencies: Integrating multiple branches can introduce dependencies between different parts of the codebase. This may complicate further testing and deployment because changes in one area can affect other areas.
Advantages of Rebase
- Linear project history: The primary benefit of Git Rebase is a cleaner history with fewer unnecessary merge commits. The resulting commit graph is more linear and easier to follow.
- Simplified codebase history: A linear history makes it easier to understand the evolution of the codebase and trace the origins of specific changes.
- Conflict resolution: Rebase replays commits from one branch onto another. Although conflicts can still occur, they can be addressed commit by commit, potentially making the process easier to manage in some workflows.
- Independent feature branches: Rebase allows feature branches to be updated with the latest changes from the main branch without introducing additional merge commits. This helps keep feature branches up to date while maintaining a linear history.
- Flexibility: Git Rebase provides flexibility when managing commits. Developers can reorder commits, edit commit messages, squash multiple commits into one, and make other changes to the commit history.
Disadvantages of Rebase
- Potential merge conflicts: Rebasing can require resolving conflicts as commits are replayed, particularly when a long-lived branch has diverged significantly from the main branch. Keeping feature branches regularly updated can help reduce the complexity of these conflicts.
- Risk of losing commits: Using interactive rebase commands to drop or modify commits can make those commits disappear from the branch's immediate history. However, they can often be recovered using
git reflog, provided the relevant objects haven't been pruned.
- Reduced historical context: After rebasing, it may be difficult to determine precisely when upstream changes were incorporated into a feature branch because the original merge commits are no longer present.
Git Rebase Workflow
We've seen how Rebase rewrites commit history while Merge preserves it. But what does this mean in practice?
Rebase offers considerable flexibility, but it also introduces risks and limitations. Therefore, it's important to understand the appropriate workflow and follow best practices when using Git Rebase.
1. Local Cleanup
While developing a feature on a separate branch, developers may create multiple commits. To keep the Git history clean and concise, we can squash related commits using interactive rebase on the feature branch itself.
For example, if you have three consecutive commits that you want to combine into one, run:
git switch feature git rebase -i HEAD~3
Git opens an editor displaying the selected commit history. You can modify the commands according to the desired outcome.
Common options include:
p—pick: Keep the commit.
r—reword: Keep the commit but edit its message.
s—squash: Combine the commit with the preceding commit and incorporate its commit message.
f—fixup: Combine the commit with the preceding commit and discard its commit message.
e—edit: Stop at the commit so you can amend it.
For example, an interactive rebase file might look like this:
pick 1fc6c95 Patch A pick 6b2481b Patch B pick dd1475d something I want to split pick c619268 A fix for Patch B pick fa39187 something to add to patch A pick 4ca2acc i cant' typ goods pick 7b36971 something to move before patch B # Rebase 41a72e6..7b36971 onto 41a72e6 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted.
To combine all selected commits into a single commit, keep the first commit as
pick and change the subsequent commits to squash:pick 1fc6c95 Patch A squash 6b2481b Patch B squash dd1475d something I want to split
Save the file and exit the editor. Git will then let you edit the resulting commit message if necessary.
Note:
HEAD~3 selects the three commits immediately preceding HEAD. If you intend to squash all seven commits in the example above, use the appropriate range, such as HEAD~7, instead.2. Rebasing onto Main
After consolidating your commits, rebase the feature branch onto the latest version of the
main branch.First, switch to
main and pull the latest changes from the remote repository. Then switch back to feature and perform the rebase:git switch main git pull origin main git switch feature git rebase -i main
This reapplies the commits on
feature onto the latest commit in main. The -i option allows you to review and modify the commits during the process.Resolve any conflicts that arise, then continue the rebase:
git add <resolved-files> git rebase --continue
If you decide to abandon the operation, you can usually return to the pre-rebase state with:
git rebase --abort
3. Force-Push the Feature Branch
After resolving conflicts and consolidating the desired commits, the feature branch will contain a rewritten history based on the latest
main branch.If the rebase changes commits that have already been pushed to the remote, a normal push may be rejected because the remote history no longer matches your local history.
You can update the remote feature branch using:
git push origin feature --force-with-lease
Note:
- Rebasing changes the commit history of your local
featurebranch. If the remote branch contains the original commits, updating it may require a force push.
- Prefer
-force-with-leaseover-force. It helps prevent accidentally overwriting remote changes that you haven't fetched yet.
- Avoid rebasing shared branches without coordinating with other developers, as rewriting published history can disrupt their work.
4. Create a Merge Request or Pull Request
Once the feature branch has been rebased and pushed, you can create a Merge Request (MR) or Pull Request (PR).
Recommended practices:
- Ensure that the PR/MR passes the required checks and receives approval before merging.
- When several feature branches depend on changes that are about to be merged into
main, coordinate their integration order.
- After a dependent PR/MR is merged into
main, rebase the remaining dependent branches when necessary to incorporate the latest changes and minimize conflicts.
Conclusion
Both approaches help integrate changes from one branch into another as part of a collaborative development workflow. By understanding their different mechanisms, we can choose the approach that best fits our project's needs.
If your priority is a clean, concise, and linear Git history that makes project progress easy to follow without unnecessary merge commits, Git Rebase can be an excellent choice.
On the other hand, if your priority is preserving the complete history of the project, avoiding unnecessary history rewriting, and retaining the context of branch integrations, Git Merge is a straightforward and effective option—even if the resulting Git graph contains more complex, diamond-shaped structures.
This article was also published by the author on 200lab.io.