How to Use Git Rebase
March 16, 2025
Ethan Carter Edwards
Why do we care about Git Rebase?
While git rebase
is probably an intermediate to advanced git topic, every developer
should learn how to use it. This is even more so true for contributors to Nixpkgs and
the Linux Kernel. As I prepare to be an Outreachy mentor, I was thinking about ways I
can help my mentees with the developer soft skills that are not necessarily taught in
any Computer Science course or video but are essential and good software development
practices. Learning to use git rebase
is one of those skills!
What is Git Rebase?
Essentially, git rebase
is a way to manipulate and edit git histories. A common
use case is reordering a series of commits. In Nixpkgs this is commonly used for new
contributors who try to submit a package. They successfully create a derivation and
build the package but do not add themselves as the maintainer. Then, a reviewer comes
along and asks them to add themselves as a maintainer, and they do so, but as a
separate commit. However, according to the style guidelines (and to not break
git-bisect, but that is for another post), the maintainer commit needs to be first in
the series. I recently reviewed a PR with this exact problem, and it inspired me to
write this post!
Another use case in Nixpkgs is when someone submits a PR adding a package or updating
something, and a reviewer asks them to make a change. According to the style guidelines,
creating a package should happen in one commit with a commit message of anakron: init at 0.3.1
However, most new contributors will make subsequent commits, which in theory is a good idea!
It just is not what the style guide calls for. The preferred course of action is to make
the suggested changes locally and then use git rebase
to squash them and then force push
the branch for the reviewer to respond to.
This scenario applies to Linux Kernel development as well. I recently posted an 8 patch series
introducing APFS support to the Linux Kernel. There are over 26K lines of code within
the patch set. Inevitably, the code reviewers will want me to make changes to the code for
the next series. Instead of making a commit for each change and taking the series from 8 patches
to potentially 20 or more, I can use git rebase
and squash all the changes into the initial
commit where the files are introduced. It can get complicated and hard to track, but the
alternative, starting from scratch with a new series of commits, is worse!
How do I use Git Rebase?
Since this post is written with my Outreachy mentees in mind, I will provide an example
for using git rebase
within Nixpkgs. I recently packaged a TUI tetris clone called yetris
as part of my efforts to package TUI/retro games in Nixpkgs.
I had the program building and it could be run, but instead of changing the directory where
the package is installed I copied the package to $out/bin/
. While this solution works,
it is hacky in that two copies of the binary would be included in the final build, wasting
space, and that I could instead use makeFlags
to redirect the install directory. I had
already committed the package:
commit 18b222d5cba16d200a61f876a7d05db112bb1a3b (HEAD -> master)
Author: Ethan Carter Edwards <ethan@ethancedwards.com>
yetris: init at 2.1.0
Signed-off-by: Ethan Carter Edwards <ethan@ethancedwards.com>
A reviewer came by and suggested that I use makeFlags
to redirect it instead. I made
the changes and committed them, but then I had separate commits!
commit 21025f648cb730caa783954d063d3990f7d1ca1e (HEAD -> master)
Author: Ethan Carter Edwards <ethan@ethancedwards.com>
yetris: use makeFlags to redirect install location
Signed-off-by: Ethan Carter Edwards <ethan@ethancedwards.com>
commit 18b222d5cba16d200a61f876a7d05db112bb1a3b
Author: Ethan Carter Edwards <ethan@ethancedwards.com>
yetris: init at 2.1.0
Signed-off-by: Ethan Carter Edwards <ethan@ethancedwards.com>
Once I was confident that my package was building and my changes were correct, I ran
git rebase -i HEAD~2
. While this command may seem scary, it is pretty simple once
it is broken down and explained. git rebase
tells git we want to start rebasing.
-i
means we want to do it interactively. HEAD~2
means we want to start at the HEAD
of the repository (which means the most up-to-date commit, in this case), and go back 2
commits. Once running the command, we are presented the todo
screen.
pick 18b222d yetris: init at 2.1.0
pick 21025f6 yetris: use makeFlags to redirect install location
Here we see a few important pieces of information. Command pick
means we will keep the
commit after rebasing. It is followed by the commit ID. You will noticed that it
matches the longer commit IDs shown above. Then we have the commit messages, which
also match. Depending on your git
version, a series of comments about the available
commands should be below commits.
While there are tons of different ways to use rebase
, here we are going to focus
on pick
and squash
. Some of the different commands are simple and others are quite
complicated. A simple one is drop
. It just deletes the commit altogether.
In our case, we want to squash the two commits together.
This just means that we want to combine their changes into one commit. We can do this
by changing pick
on the second commit to squash
. We could technically squash additional
commits into this first commit as well. The most commits I have squashed together is
10, but Git supports much larger numbers than that.
pick 18b222d yetris: init at 2.1.0
squash 21025f6 yetris: use makeFlags to redirect install location
After saving and exiting (:wq
in vim), we are given the opportunity to edit our
squashed commits message and log. I recommend removing the commit logs from the squashed
commits, but it is not a hard requirement by any means. After editing the log to reflect
your wishes, save and exit again. Git will squash the commits:
commit 1398b15ecb5aa80d25ae202b44b09532a1ddfb49 (HEAD -> master)
Author: Ethan Carter Edwards <ethan@ethancedwards.com>
yetris: init at 2.1.0
Signed-off-by: Ethan Carter Edwards <ethan@ethancedwards.com>
Notice that our commit ID has changed! That is because this is a totally new commit with a new set of changes, despite the commit message and log being the same. Git generates IDs based off the hash of the changes, timestamp, author and committer info, tree info, and more. Because we created a new commit, the timestamp is different and we have different changes, resulting in a new commit ID.
If you are interested in learning more about git rebase
and its internals, I recommend
checking out https://git-rebase.io/.
As you grow as a developer, maintaining clean git histories is an important skill to have.
git rebase
is one of many ways to do that.
Extra bits
While git rebase
worked here, it was not completely necessary. We could have used
git commit --amend
to add any staged changes to the previous commit. However, this
only works to edit the previous commit and is not as powerful as rebasing (it allows squashing
n
many commits). I chose to focus on rebase here because my Outreachy mentees will
need it as a skill.
As a side tangent, you may have noticed the weird Signed-off-by:
tag on my commit
messages. While Nixpkgs does not formally require them, they are encouraged in the
Free and Open Source community and even required in some projects like the Linux Kernel.
This pattern of "signing off" on code is called the Developer Certificate of Origin. It
basically means that you are guaranteeing that you wrote the code and that you did not
steal it from anyone else. I recommend getting in the habit of adding it to your commits.