The git rebase command has a reputation for being magical Git hocus pocus that beginners should stay away from, but it can actually make life much easier for a development team when used carefully. In this article, we’ll compare git rebase to the related git merge command and identify all potential opportunities to incorporate rebasing into the typical Git workflow.
The first thing to understand about git rebase is that it solves the same problem as git merge. Both commands are designed to integrate changes from one branch to another, they just do it in very different ways.
Consider what happens when you start working on a new feature in a dedicated branch, then another team member updates the main branch with new commits. This results in a forked story, which should be familiar to anyone who has used Git as a collaboration tool.
Now, let’s say the new primary commits are relevant to the feature you’re working on. To incorporate the new commits into the feature branch, you have two options: merge or overrun.
The merge option The easiest option is to merge the parent branch into the feature branch using something like the following: Or, you
can condense this into a single line
: This creates a new “
commit” in the feature branch that joins the histories of both branches, giving you a branch structure similar to this:
Merging is nice because it is a non-destructive operation. Existing branches are not changed in any way. This avoids all potential dangers of overshoot (discussed below).
On the other hand, this also means that the feature branch will have a weird merge confirmation whenever you need to incorporate upward changes. If main is very active, this can contaminate the history of its feature branch quite a bit. While it is possible to mitigate this problem with advanced git logging options, it can make it difficult for other developers to understand the history of the project.
The Rebase option
As an alternative to merging, you can override the feature branch in the parent branch
by using the following commands:
This moves the entire feature branch to start at the tip of the parent branch, effectively incorporating all new commits into primary. But instead of using a merge commit, the rebase rewrites the project history by creating new commits for each commit in the original branch.
The main benefit of overshoot is that you get a much cleaner project history. First, it removes unnecessary merge commits required by git merge. Secondly, as you can see in the diagram above, the overshoot also results in a perfectly linear project history: you can follow the tip of the feature to the beginning of the project without branches. This makes it easy to navigate the project with commands like git log, git bisect, and gitk.
But, there are two trade-offs to this pristine engagement story: security and traceability. If you don’t follow the golden rule of overshoot, rewriting project history can be potentially catastrophic for your collaboration workflow. Less importantly, the overshoot loses the context provided by a merge acknowledgement: you can’t see when upstream changes were incorporated into the feature.
Interactive Overshoot gives you the opportunity to modify commits as they move to the new branch. This is even more powerful than an automated overrun, as it offers complete control over the branch’s confirmation history. Typically, this is used to clean up a messy history before merging a feature branch into primary.
To start an interactive rebase session, pass the i option to the git rebase command
will open a text editor that lists all the commits that are about to move: This
list defines exactly what the branch will look like after the rebase. By changing the selection command and/or reordering entries, you can make the branch history look like the one you want. For example, if the 2nd commit fixes a small problem in the 1st commit, you can condense them into a single commit with the fixup command
When you save and close the file, Git will perform the overshoot according to your instructions, resulting in a project history similar to the following:
Removing insignificant commits like this makes the history of your feature much easier to understand. This is something git merge simply can’t do.
you understand what overshoot is, the most important thing to learn is when not to do it. The golden rule of git rebase is to never use it in public branches.
For example, think about what would happen if you changed the main base to its
Overshoot moves all commits in main to the feature tip. The problem is that this only happened in your repository. All other developers are still working with the original main. Since the refoundation results in new confirmations, Git will think that the history of its main branch has diverged from that of everyone else.
The only way to synchronize the two parent branches is to merge them again, resulting in an additional merge commit and two sets of commits containing the same changes (the original ones and those of your overshot branch). Needless to say, this is a very confusing situation.
So before you run git rebase, always ask yourself, “Is anyone else looking at this branch?” If the answer is yes, take your hands off the keyboard and start thinking of a non-destructive way to make your changes (for example, the git revert command). Otherwise, it’s safe to rewrite history as much as you want.
If you try to return the overstayed master branch to a remote repository, Git will prevent you from doing so because it conflicts with the remote parent branch. But, you can force the push by passing the -force flag, like this:
# Be very careful with this command! git push -force
This overwrites the remote main branch to match the overrun of your repository and makes things very confusing for the rest of your team. So, be very careful to use this command only when you know exactly what you are doing.
One of the only times you need to force the push is when you have performed a local cleanup after you have pushed a private feature branch to a remote repository (for example, for backup purposes). This is like saying, “Wow, I really didn’t want to push that original version of the feature branch. Take the current one instead.” Again, it is important that no one is working with the commits of the original version of the feature branch.
Overshoot can be incorporated into your existing Git workflow as much or as little as your team is comfortable with. In this section, we’ll take a look at the benefits that overshoot can offer at various stages of a feature’s development.
The first step in any workflow that leverages git rebase is to create a dedicated branch for each feature. This gives you the necessary branch structure to safely use
One of the
The best ways to incorporate overshoot into your workflow is to clean up ongoing local features. By periodically performing an interactive overshoot, you can ensure that every confirmation in your role is focused and meaningful. This allows you to write your code without worrying about breaking it into isolated commits; You can fix it after the fact.
When you call git rebase, you have two options for the new base: the main branch of the feature (for example, main) or a previous commit to your feature. We saw an example of the first option in the Interactive Overshoot section. The latter option is nice when you just need to fix the latest confirmations. For example, the following command starts an interactive base of only the last 3 commits.
git payment function git rebase -i HEAD~3
By specifying HEAD~3 as the new base, you are not actually moving the branch, you are just interactively rewriting the 3 commits that follow it. Note that this will not incorporate upward changes to the feature branch.
If you want to rewrite the entire entity with this method, the git merge-base command can be useful to find the original base of the feature branch. The commit ID of the original base is then returned, which can then be passed
to git rebase: git merge-base feature main
This use of interactive rebaseline is a great way to introduce git rebase into your workflow, as it only affects local branch offices. The only thing other developers will see is your finished product, which should be a clean, easy-to-follow feature branch history.
then again, this only works for private feature branches. If you are collaborating with other developers through the same feature branch, that branch is public and you are not allowed to rewrite its history.
There is no git merge alternative to clean up local commits with an interactive overdrive.
Embedding upstream changes
In the Conceptual Overview section, we saw how a feature branch can incorporate upstream changes from main using git merge or git rebase. Merge is a secure option that preserves all of your repository history, while rebase creates a linear history by moving your feature branch to the top of the main one.
This use of git rebase is similar to a local cleanup (and can be done simultaneously), but in the process incorporates those main upstream commits.
Keep in mind that it is perfectly legal to relocate to a remote branch instead of a main one. This can happen when you collaborate on the same role with another developer and need to incorporate your changes into your repository.
For example, if you and another developer named John added commits to the feature branch, your repository might look like this after you get the remote features branch from John’s repository:
You can resolve this branch in exactly the same way that you integrate main’s upward changes: merge your local feature with john/feature, or overshoot your local feature at the john/feature tip.
Note that this overshoot does not violate the Golden Rule of Overshoot because only your local feature confirmations are moved, all of the above are not touched. This is like saying, “Add my changes to what John has already done.” In most cases, this is more intuitive than synchronizing with the remote branch through a merge acknowledgement.
By default, the git pull command performs a merge, but you can force it to integrate the remote branch with a rebase by passing it the -rebase option.
feature with a
If you use pull requests as part of the code review process, you should avoid using git rebase after you create the pull request. As soon as you make the pull request, other developers will see your confirmations, which means it’s a public branch. Rewriting your history will make it impossible for Git and your teammates to track down any trace confirmations added to the feature.
Any changes from other developers should be incorporated with git merge instead of git rebase.
For this reason, it’s generally a good idea to clean up your code with an interactive overrun before submitting your pull request.
an approved feature
After a feature has been approved by your team, you have the option to re-base the feature on the tip of the main branch before using git merge to integrate the feature into the main
This is a similar situation to incorporating upstream changes into a feature branch, but since you are not allowed to rewrite commits to the main branch, you must eventually use Git Merge to integrate the feature. However, by performing a rebate before the merge, you are assured that the merge will advance quickly, resulting in a perfectly linear history. This also gives you the opportunity to crush any trace acknowledgments added during a pull request.
If you’re not entirely comfortable with git rebase, you can always overshoot in a temporary branch. That way, if you accidentally mess up your feature history, you can check the original branch and try again. For example:
And that’s all you really need to know to start overtaking your branches. If you prefer a clean, linear history free of unnecessary merge commits, you should look for git rebase instead of git merge when integrating changes from another branch.
On the other hand, if you want to preserve the complete history of your project and avoid the risk of rewriting public commits, you can stick with git merge. Either option is perfectly valid, but at least now you have the option to take advantage of the benefits of git rebase.