Git provides two commands for integrating changes from one branch into another,
rebase. In this post I am going to slightly explain both commands but mainly focus on rebasing and how to use it.
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
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
A---B---C---E <- main \ D <- feature
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?
For this purpose we could use
$ git checkout feature $ git merge main
This command will replay all commits inside
main on top of
feature and create a merge commit
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.
As an alternative to merging, you can
main branch onto
$ git checkout feature $ git rebase main
feature branch is moved to the top of
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
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)
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.
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
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
rebase because for them a clear history was not in proportion to the risk of something being deleted via a forceful push.