One highly debated functionality I use multiple times a day would be the infamous
git rebase. In the past, I managed to make several teams comfortable with using rebase confidently, and they do not want to miss it anymore.
You can find voices arguing against rebase that consider it harmful or dangerous. I do feel that these statements, as many others, stem from not being familiar with what rebase does and what it can accomplish.
In this post, I want to introduce rebase, present a use case, and discuss why it is considered dangerous by some. And why it isn’t.
If you are relatively new to git, I recommend you to check out my german webinar ‚Git started – verteilte Versionsverwaltung mit Git‘.
Associated Screencast: Demystifying Git Rebase: Git in Practice
For those of you who want to see me in action with the git CLI, I have also recorded the essence of this article in a short screencast.
- Part 1: Demystifying git Rebase ⬅
- Part 2: Interactive Rebase
- Part 3: Rebase Onto – When Dropping Commits Makes Sense
What is Rebase?
Essentially, rebasing is the process of taking multiple commits and applying them on top of another base tip.
To make sense of that, we need to understand what applying a commit means. Without going too much into detail on how git stores its history, we can simply imagine a commit to be the difference from the previous commit.
Therefore, a series of commits is just a series of changes that has been done to a certain base state. In other words, to get to the code’s current state, we take a base state and apply the changes of the series of commits to it.
Let’s try to illustrate that with a graph:
``` A---B---C (feature) / D---E---F---G (main) ```
The capital letters represent commits. We also have two branches. One is called
feature, pointing to the commit named C, and the other is called
main and points to commit G.
Keeping things simple for the sake of explanation: the branch main refers to commits D, E, F, and G, while the branch
feature refers to the commits A, B, and C and is based on commit E on the
Now when we say we want to rebase the
feature branch onto
main, what we are pursuing is a history that looks like this:
``` A'--B'--C' (feature) / D---E---F---G (main) ```
This can be achieved by using the command:
# git rebase [ ] $ git rebase main feature
If the currently checked out branch is
feature, we can omit it from the command and just go with:
$ git rebase main
Then git will checkout
feature first and perform the rebase. But I like to remember the three-parameter-form to ensure that we rebase
<base>. The base goes first, and then what we want to apply on top of it.
After a successful rebase, the branch main is the new base for our feature branch. What happened is that the series of commits on the branch feature (A, B, C) have been applied to a new base, G, the main branch, thus rebasing feature onto main.
As you can see, I renamed the commits ABC to A’B’C‘. This is because they are not the same commit anymore. They are now commits that also include the changes of the commits made in F and G, as the base has moved from commit E to G.
But performing the above described rebase, our feature branch is now up to date with
main and includes all of
main’s changes, and that without merging
main into our
When or Why to Rebase?
Let’s first look at how we ended up in this situation:
``` A---B---C (feature) / D---E---F---G (main) ```
Essentially, at the time the
main branch was at commit E, we branched our
feature branch off of that. Once we branched off E, two more commits have been added to
main, and we added three commits to
feature. This results in the state that our base branch is ahead of our
We can see that the changes in commits F and G are not included in our
Now being in the state is not an issue. We can still integrate
feature back into
main by a simple merge, which would result in a history like this:
``` A---B---C / \ D---E---F---G---H (main) ```
The newly created merge commit H is the state that incorporates all the changes of the
feature branch and the
main branch. Great.
However, things do not always go like this. Imagine that while you are developing your feature, some other feature branch has been merged into
main or a really important API change or feature that your branch relies on.
In that case, you want to integrate the new changes into your feature branch so you can continue your work. One way of doing that would be merging the
main branch into your
feature, and then, in the end, merge your
feature back into
main. A graph for that situation might look like this (considering no fast-forward merge):
``` A---B---C---H---I---J (numbers) / / \ D---E---F-------G-----------K (main) ```
I and J denote the changes that you make after the main branch has been integrated into your feature.
But: A more elegant way would be to use rebase!
``` A'--B'--C'--I---J (numbers) / \ D---E---F---G-------------------K (main) ```
In this case, rebase allows us to adjust our feature branch to a state as if we branched off of G and started our work after all the changes we rely on are available to us.
The advantages of using rebase are that you can rebase your feature as often as you want or need, without polluting the history. Imagine that more changes are made to
main, while you are still working on your
feature. You can just update your base and have all the goodness that lies in the
main branch ready for you to use in your
Also, as your commits are replayed one after another, you can still see the incremental changes that you have made. That makes resolving conflicts easier, compared to having to fix all conflicts between the two states you are trying to bring together all at once.
Using rebase, we can easily keep our feature branches up to date with
main, without merging back and forth.
Handling Conflicts When Rebasing
When applying commits to a new base state, it is possible that the new base has made changes that are conflicting with the changes you are trying to apply. For example, if on
main someone edited the same file and line you did on your branch. The same thing happens when merging. We have two conflicting states, and we want to resolve them by changing the file to the version that incorporates the changes correctly.
Running into a conflict when rebasing looks like this:
$ git rebase master Auto-merging file.txt CONFLICT (content): Merge conflict in file.txt error: could not apply 050389b... Refactoring a method [...]
We now have a conflict in
file.txt we will find the usual conflict markers. Either using our editor or the merge tool of our choice, we will have to resolve the conflict by changing our file to the state we need it to be in, in our commit.
After successfully resolving all conflicts, we need to stage the file by using the
git add command, e.g.:
$ git add file.txt
and we can continue our rebase journey by applying all the other commits by using the command:
$ git rebase --continue
If we do not feel comfortable resolving the conflicts and going on with the rebase for whatever reason, we can always simple abort the rebase and go back to before we started it by using:
$ git rebase --abort
The Dangers of Rebase
If you are looking into rebasing, you might find opposing opinions on its use. The more significant arguments against rebase are:
- you can mess up your code when doing it wrong
- you have to force push
Addressing the first argument, yes, obviously, your result will not be what you expected it to be when handling the tool you are using in the wrong way. The same can be said about misconfiguring a service. Therefore, it is crucial to understand what we are doing, how our tools work, and to use them properly to achieve our goals.
Most of the time, people have issues with rebase because they either do not understand properly what it does, or they rebase the wrong way around (like the base onto the feature). I hope my explanation above rules both of these issues out for you at least, my dear reader (that makes us to rebase-buddies – welcome to the club of savvy rebasers!)
Addressing the second point, the fear of force push. When rebasing a branch, git takes the commits of that branch and applies them to the new base, resulting in a git history rewrite. If you have already pushed your branch to a remote, there will be a disparity between your local and the remote versions. To get the version on the remote to match yours, you will have to force push. However, I feel that force pushing your feature branch should never be an issue. If you are working on a feature branch with a team member, you should communicate first, but the moment your team member fetches, they will know about your forced push and can react accordingly. If you have not pushed your branch to a remote yet, you will not even have to force push. Force pushing a feature branch is completely fine, but please avoid doing so on your main or develop branches (of course, there are exceptions to that rule, in emergency cases) as members of your team have probably based their work on them.
Another issue that is brought up is that „after the rebase, something broke“. Here, also, the issue is not rebase. Of course, when applying changes to another base, it is possible to run into conflicts. These conflicts have to be resolved properly, as git cannot decide which of the two versions of a line of code is the one that is supposed to be correct when applying your changes. Resolving conflicts is also part of merging, or bringing two states together and should not be a problem to you as the author of the software you are authoring. It is part of our collaborative efforts. I feel that resolving conflicts when rebasing is easier, as I do not have to solve all conflicts at once, and rather incrementally, in the way I developed my feature. Therefore I have more contextual information on what change I did and what the correct version should look like considering the new base.
We took our time to look at what a git rebase does, what issues we can run into, and how to tackle them. I hope this explanation helps to illustrate rebase and motivates you to incorporate it into your everyday workflow.
Continue with Part 2: Interactive Rebase
In the next part of his series, Yannick Baron shows you how to step up your game with interactive rebase.