Git: Rebasing

René Kulik on 18.01.2021

Git provides two commands for integrating changes from one branch into another, merge and rebase. In this post I am going to slightly explain both commands but mainly focus on rebasing and how to use it.

Introduction

When developing software in a team it is common to use a version control and an associated workflow. This allows developers to work independently on different features and bring them together at the end of a development cycle.

Let’s say we have this initial situation:

We have a main and a feature branch. main is the default branch in which every feature gets merged when finished.

A---B---C    <- main
         \
          D  <- feature

When working together with other developers it occurs that main evolves and is ahead of feature:

A---B---C---E  <- main
         \
          D    <- feature

As commit E introduces a very cool functionality you might want to use it in your feature, therefore you have to update it with the current main. How can we do this?

merge

For this purpose we could use merge:

$ git checkout feature
$ git merge main

This command will replay all commits inside main on top of feature and create a merge commit F:

A---B---C---E    <- main
         \   \
          D---F  <- feature

Merging is quite straight forward and the easiest way to integrate changes. The downside is that it creates a merge commit which is shown in the history.

rebase

As an alternative to merging, you can rebase the main branch onto feature:

$ git checkout feature
$ git rebase main

The entire feature branch is moved to the top of main:

A---B---C---E     <- main
             \
              D'  <- feature

As rebasing does not create a merge commit, it leads to a clearer history. But it should be used with caution. Rebasing is a powerful tool and should only be used if you know what you are doing, as it allows you to modify the history of your current branch.

It also needs be mentioned that only local branches should be rebased. As soon as a branch gets pushed to a remote repository and someone else might depend on published code, rebasing should be avoided as you potentially overwrite other developers work.

Pushing to a published branch after rebase

In my experience the following is the most common problem someone will encounter when working with rebase. You rebased main onto your feature:

A---B---C---E     <- main
             \
              D'  <- feature

feature is already published to a remote repository and now you want to push your changes. When executing push you will get an error:

 ! [rejected]        HEAD -> feature-1 (non-fast-forward)

As the commit D is applied to a new base (E instead of C), git can not fast-forward the remote branch. Therefore a normal push is not sufficient. What you have to do in this case is to execute a forceful push. I can not emphasize this enough: Make sure you know what you are doing!

To forcefully push use the --force-with-lease flag. This will check the upstream branch for changes and refuse to update if you are about to damage others work:

$ git push --force-with-lease

In case the upstream branch changed, you will receive this error:

 ! [rejected]        HEAD -> feature (stale info)

Perform a pull to update your local branch before pushing forcefully again:

$ git pull --rebase

In addition to the danger of unintentionally deleting code, you might also encounter some problems using git management tools such as Bitbucket. If you forcefully push a rebased branch with an open pull request, comments in the pull request might be gone. Those comments are always bound to specific commits. As rebase changes the history, and thereby also the commit hashes, comments can no longer be displayed correctly.

Conclusion

rebase is very handy for maintaining a clean history on local environments. But it should be used with caution (or completely avoided) on published branches.

If a clear history is not that important for you, go with merge.

Whether or not to use rebase sometimes also depends on the company or team you are currently working for. I have worked for several bigger and smaller companies and different teams and none of them ever looked at the history. Some even explicitly agreed on using merge over rebase because for them a clear history was not in proportion to the risk of something being deleted via a forceful push.