Rebasing & Merging
When it comes to managing changes in a Git repository, two of the most commonly used commands are
git merge and
git rebase. These are two ways of solving the same problem - integrating changes from one branch into another branch.
While both commands are used to combine changes, they work in different ways and can have different effects on the repository’s history.
Understanding the differences between git merge and git rebase can help you choose the right command for your workflow and avoid potential conflicts and complications.
Simplest example - fast-forward merge
Suppose you have a feature branch
feature created from the
master branch, and there were no new changes in the
master branch since the creation of
$ git init # ... $ git commit -m "A1" $ git switch -c feature # ... $ git commit -m "B1" # the same with B2 and B3
In that case, a fast-forward merge can be used to integrate the changes in
master. The result will be a linear history with all the changes from
feature on top of
master, and no new merge commit will be created.
$ git switch master $ git merge feature Updating 9505fd5.-ae9636f Fast-forward
Not all merges are fast-forward!
Picture this: You’ve been working hard on a new feature for your project, and you’re nearly finished. You’ve been making all your changes on a separate branch, so you’re not interfering with the master branch.
However, just when you’re about to wrap things up, you find out that someone else has merged their own new feature into the master branch without telling you.
Now you have a problem.
You can’t simply merge your changes into master with a fast-forward merge because the master branch has changed since you created your feature branch.
Instead, you’ll have to use a non-fast-forward merge, which will create a new merge commit and potentially cause conflicts if your changes overlap with the other feature that was merged into master.
$ git switch master $ git merge feature Merge branch 'feature' # Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
It’s important to note that Git merges are forward-focused, meaning they don’t alter existing history in any way. When you merge two branches, Git creates a new commit that includes the changes from both branches, but the existing commits on each branch remain intact.
This means that if you make a mistake during the merge, you can always go back to the original commits and start over.
It also means that you can merge a branch multiple times without altering the existing commits.
Problems with merging
If you’re working on a feature branch while other team members push changes to the master branch, it’s important to keep your branch up to date with those changes.
One way to do this is to merge the master branch into your feature branch, which can result in multiple merge commits and potentially cluttered commit history.
Merging the master branch into your feature branch multiple times can result in a significant number of merge commits, which can make your Git history difficult to follow and understand.
$ git checkout feature $ git merge master # done multiple times to have feature branch updated
As a result, it’s important to consider alternative strategies, such as rebasing or squashing commits, to keep your commit history clean and organized while still incorporating changes from the master branch.
Rebasing is another option
An alternative to merging is rebasing your feature branch onto the master branch.
According to the Git documentation,
git rebase is a command used to apply changes from one branch onto another. The basic idea is to take the changes that were made on one branch (called the “source” branch) and replay them on top of another branch (called the “target” branch). This can be useful when you want to incorporate changes made in one branch into another branch, while maintaining a clean and linear history.
git rebase command works by finding the common ancestor commit of the two branches (i.e., the point at which they diverged), and then creating a new series of commits that apply the changes made in the source branch on top of the target branch. The process involves the following steps:
- Git identifies the common ancestor commit of the two branches.
- Git creates a new temporary branch based on the target branch.
- Git applies the changes made in the source branch one by one, in the order they were originally made.
- Git creates new commits for each of the changes, applying them to the temporary branch.
- Finally, Git moves the target branch pointer to the new commits, effectively incorporating the changes from the source branch.
It’s worth noting that during this process, conflicts can arise if the same changes were made in both the source and target branches. In these cases, Git will pause the rebase process and allow you to manually resolve the conflicts before continuing.
…one potential disadvantage of using git rebase is that it rewrites history by moving the entire feature branch to begin at the tip of the master branch, resulting in the creation of new commits for each of the original feature branch commits.
While this approach can eliminate merge commits and keep the commit history linear, it can also make it difficult to track changes over time and potentially cause conflicts if other team members are working on the same branch.
$ git switch feature
$ git rebase master
⛔️ Warning ⛔️
It’s important to avoid rebasing commits that have been shared with other team members.
If you’ve already pushed commits to a shared repository, it’s generally not advisable to rewrite history by rebasing those commits, as doing so can create conflicts and make it difficult for other team members to understand and track changes.
- fully traceable history,
- beginner friendly,
- maintains the original context of the source branch,
- the commits on the source branch are separated from other branch commits
- history can become intensely polluted by lots of merge commits
- simplified, linear, and readable code history
- manipulating a single commit history is easier than a history of many separate feature branches with additional commits
- lowering the feature down to a handful of commits can hide context
- more careful handling is required when using rebasing than when merging
- requires more work to deal with conflicts, as similar conflicts need to be resolved again and again to keep the feature branch updated, and conflicts need to be resolved in the order they were created to continue the rebase