What is Rebasing?
Let's start off by distinguishing rebasing and merging, which are the two main ways of integrating commits between branches in Git. Using
git merge to integrate changes is relatively simple and harmless. Merging will preserve the history (along with merge commits) of a branch and will give an accurate depiction of the branch's timeline. Git accomplishes this via a three-way merge, which involves the two latest branch snapshots from each branch (
c4) in the merge and the nearest common ancestor (
c2). A new commit (
c5 the merge commit) will then be created to encompass the integration.
c4 / \ c0 - c1 - c2 - c3 - c5
Rebasing is the other option for integrating work between branches in Git and it works a little differently than merging. Rebasing is simply replaying commits from one branch onto another. Don't be intimidated by the term "replay" here. You'll see it in the console when doing a rebase and it basically means applying commits to another branch.
Rebasing and merging are similar in that the changes will both use the nearest common ancestor to begin, but the way the commits are integrated from one branch to another during a rebase is completely different. Git will follow these steps.
- Find the nearest common ancestor between the two branches
- Save the diff of the two branches to separate files
- Reset the current branch to the latest commit of the branch to rebase onto
- Replay the changes from the diffs in order via new commits
This results in a commit tree that may look something like the one below. In the example, we create a branch called
main. After creating a commit (
feature we rebase onto
main, which now has an additional commit created after the
feature branch was created (
c3). To rebase
feature on to
main we can run
git rebase main (with feature checked out) which will follow the steps we listed above and ultimately replay the changes in
c4, resulting in a new commit
c4'. One important thing to note is that since new commits are created in the process of a rebase, it is effectively rewriting a branch's history.
c4 (feature) / c0 - c1 - c2 - c3 - c4' (main)
That is how the standard rebase and merge features work. Next, we'll be going into the interactive rebase feature, which I think can greatly reduce the amount of anxiety around rebasing Git branches.
Using the Interactive Rebase
Rewriting a branch's history without knowing for sure what will be applied can be scary. This is where the interactive rebase comes in. The interactive rebase gives us a chance to see the commits that will be applied and even manipulate the commits before completing the rebase. The option to add to the rebase command is
--interactive. For example, from our previous scenario interactively rebasing
main would be done with
git rebase main -i. Running that command will open an editor (the default based on your Git config) and all of the commits to be rebased will be displayed in a list. From that list, we can drop, reorder, squash, and reword messages of the commits in the list. Let's take a closer look at all the available options.
Rebase Menu Options
Here is an example of the editor window when running
git rebase <branch> -i. As you can see commits to be rebased are listed first followed by several different commands that can be applied during the rebase. The default command for each commit is "pick". As noted in the menu, commits are applied from top to bottom. Reordering commits is as simple as changing their order in the given commit list. Once we have made the changes necessary, saving and closing the file will begin the rebase. Let's take a look at each of these commands and their purpose in the context of rebasing.
First up is the
pick command and it is probably the most straightforward. As mentioned before and as you can see from the command option list,
pick is the default command and it indicates that a commit will be applied in the rebase. So if we want to include a particular commit that would look like this.
pick 33bf560 Add third description heading
Next up is the
reword command. This command is used to indicate that a commit will be applied in the rebase, but the commit message is different from the original. This may be useful if there was a typo or if a correction needs to be made in the original message. If we want to reword and apply commit
33bf560 that we picked above, we would update it to the following.
reword 33bf560 This commit message is different from the original
edit command is up next and I think this one is especially interesting because it allows us to make more changes after a rebase has already been started. Let's say we use the
edit command on the third commit in the commit list. The rebase will apply all the commits up to and including the commit that we've indicated with
edit. Once the third commit has been applied the rebase will be paused. We can then make additional changes like update or add files and amend the commit we indicated to edit with the new changes. We can then run
git rebase --continue to continue the rebase.
In the example below commits
b499fc4 will all be applied before the rebase breaks to allow us to edit commit
b499fc4, which we specified would be edited.
pick 33bf560 Add third description heading pick 979e6c4 Create link to blog site edit b499fc4 Insert section to explain feature pick 5bd6691 Update numbered list to include more talking points
Squash and Fixup
In this section, we'll be touching on two commands that are very similar. The
fixup commands are used to combine the changes of two or more commits into a single commit. Since commits are executed from top to bottom we can only
squash/fixup the second and subsequent commits. When executing
squash/fixup the commits will be melded into previous commits. This means if we use the squash command on the third commit in the list, then that commit will become a single commit with the second in the list.
The difference between
fixup is what will happen to the commit messages. When using
squash the squashed commit(s)'s message(s) will be concatenated and included in the single squashed commit. When using
fixup the folded commit(s)'s message(s) will be discarded in favor of the previous commit's message. If commit authors differ, the previous commit's author will be the author for the resulting single commit.
In the example below commit
b499fc4 will be squashed into commit
979e6c4 and the commit message will be concatenated in the resulting commit. Commit
5bd6691 will also be melded into the resulting
979e6c4, but the commit message will be discarded.
pick 33bf560 Add third description heading pick 979e6c4 Create link to blog site squash b499fc4 Insert section to explain feature fixup 5bd6691 Update numbered list to include more talking points
exec command has maybe the most limited use case (as far I can see) of all of the interactive rebase menu options. Using
exec will essentially execute the indicated line in the shell. This means that we can run additional shell commands throughout the process of the rebase. For example, if we have some changes in the rebase that merit running the tests or compiling the code throughout, we can add add
exec dotnet test or
exec npm run build to run those commands during the rebase.
The example below will run the
dotnet build and
dotnet test commands during the rebase as the commits are applied from top to bottom.
pick 33bf560 Add third description heading pick 979e6c4 Create link to blog site edit b499fc4 Insert section to explain feature exec dotnet build pick 5bd6691 Update numbered list to include more talking points exec dotnet test
Last but not least we have the
drop command. This command is just as simple as
pick. We can use
drop when we want to completely drop or exclude a commit from the rebase. So if we want to exclude a particular commit, we can use the following command. Alternatively, we could just delete the line altogether and it would effectively be the same as using the
drop 33bf560 Add third description heading
That is a complete rundown of all the available options during an interactive rebase. My hope is that understanding how to use each of the commands will help reduce the anxiety that can come about from rebase changes from one branch onto another.