Main differences between git merge and git rebase

Merging two branches

I’ve been using git merge every time I need to merge two branches. Then I knew an architecture member guy that said: “Everyone should use git rebase instead of git merge, it is easier to track changes using rebase than using merge”. I was curious and I tried to figure out what it means.

Beyond that, I noticed that many developers don’t know the main differences between git merge and git rebase and which they should use for certain scenarios.

Both git rebase and git merge are used to “join” or “put together” two different branches, it can be two local branches or one local branch with one remote branch, for example, local master and origin master branch.

When we run “git pull” in master branch, actually we are running “git fetch” and “git merge master origin/master”. We are merging our local branch with its remote upstream branch.

If there are no conflict the commits from the remote branch will be added at the end of the local branch. If there’s a conflict a new hash commit will be created. It happens by default and it is called “fast forward” merge. If we want to avoid this behavior we can use the option “ — no-ff”, this option allows us to create a new commit always.

But we can use “git pull — rebase”, with this command we are actually running “git fetch” and “git rebase origin/master” (in case you are in your master branch), instead of just adding the commits from remote to local branch at the end, it will do a series of steps:

First, it will save the local master commits in a temporary area.

The current branch is reset to upstream, in this case to origin/master. This step brings the new commits from origin/master together.

Then the commits previously saved into the temporary area is reapplied to the current branch, one by one, in the current order.

With that there’s no new merge commits and the previous code is preserved, but in case of conflicts, the commits that are reapplied can be modified with no history.

Git rebase

For example, let’s consider we have a branch “feature/example-feature” and we want to rebase from the origin “origin/feature/example-feature”.

git log

The git log result above shows local branch “feature/example-feature” has 1 commit “change 4” that is not pushed to origin. Be in mind that this change was done in 2019 Oct 17 15:17:23–05:00 and the commit starts with “e65d12f8” because after the rebase this commit will be reapplied.

Commits in origin/feature/example-feature

The remote origin “feature/example-feature” branch has a commit called “Update index.html” that is not in local “feature/example-feature”, so before we delivery our “change 4” we need to get the “Update index.html” commit, right? So we can use git pull — rebase.

git pull — rebase

Now we added our “change 4” on top of “change 3” plus all the new commits, in this case only “Update index.html”. Check out the result below.

git log after rebase

Look that the commit name “change 4” changed from “e65d12f8” to “939f082f”, because it was reapplied, but the date-time remained the same.

git push
git log after push

Now we can push the code without force option because our commit “change 4” was not pushed before.

Sometimes we rebase from other branch and in this case, we can reapply commits that was already pushed to our origin, at that time the force will be necessary. Let’s watch another example.

Git rebase from other branches

The git rebase can also be used with two local branches, for example, “git rebase master branch-b”. In this case:

First the actual branch is gonna switch to branch-b, as you run “git checkout branch-b” then “git rebase master”. It means the branch-b will be rebuild from master.

Then it will save the branch-b commits to temporary area.

The branch-b is reset to upstream, in this case to master.

Finally, the commits previously saved into temporary area is reapplied to branch-b, one by one, in the current order.

Let’s consider we have a local and origin master with changes a, b and c.

master and origin/master with the changes a, b and c

We will create a branch “feature/example” from master, also we will create “change d” commit and push to “origin/feature/example”.

git log of the pretty new branch

Now, before we send the “change d” to master back, we realized that the other team delivered a change on master, so we need to checkout to master branch and pull that change called “other team change”.

git log master with a new change

So now we can check out to our “feature/example” branch back and rebase from master, adding “change d” on top of “other team change”.

git rebase master

Let’s check what happens…

git log

The “change d” was reapplied and if we want to push it to our “origin/feature/example” we will face an error, because our origin has a commit that we don’t have (the old “change d”).

git push error

And we can check the details using git status.

git status

It happens because we have two new commits: “change d” and “other team change” locally and on the remote branch we have the old “change d” commit.

So, in this case, we will need to force the push with git push — force or git push -f. We can’t use git pull, if we do, it will create a new commit with a new merge (and it is exactly we wanted to avoid using rebase).

git push -f

Conclusion

Both git merge and git rebase has the same objective. If you decide to use git merge you will keep the previous commit as it is but probably you will create a new commit only for merge. If you use git rebase you will let the timeline more elegant, but probably you will have to rewrite some commits. Also, if the commits were already pushed, you will have to force the push because you changed the timeline history.

For the last case, the branch you are working should be only for you (not public branch like master but a personal feature-branch) because if another developer is working in this branch you will replace his commits by yours when force pushing.

Sofware Engineer Lead, focused on development and maintenance of products