Whether you’re working in a large team or working alone on your personal git repository, you have probably pushed changes in a commit that you later wished to change. Therefore, you may want to change previous git commits that you have perhaps already pushed.
This is very common when you’re working with git repositories. For example, you may have created and pushed a C++ / CMake executable in your project, but forgot a newline, or simply mistype a variable name which imminently causes a build failure. For this reason, knowing when to use git rebase -i
, or rebase interactive, is important.
Simply put, rebase interactive lets you change previous git commits, whether you’ve already pushed them or not. In this post, we look at how to use it to add little changes to previously pushed commits.
Video – Using Git Rebase Interactive To Alter Previous, Already-Pushed Commits
For visual learners, I’ve recorded the video below to demonstrate how to use git rebase -i
to reorder and change previous commits.
Understanding The Command For Git’s Rebase Interactive
Briefly speaking, if you want to edit, reorder or reword the last N
commits, you can use the following command.
git rebase -i HEAD~N
Interestingly, once you enter the command above, git shows you the last N
commits and a list of options. For demonstration purposes, you can see the output to git rebase -i HEAD~2
from my test branch below. However, you can find more information on the git rebase interactive documentation page.
pick a70ddd1 first commit
pick 622a242 Adding python code.
# Rebase d28df9e..622a242 onto d28df9e (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
...
From here, you can modify the commit lines (1-2 in this case) and change the history of your branch as you wish!
The commits that show up at the top are in chronological order. Specifically, the bottom commit is the most recent, and the top one is the oldest commit.
Options To Git’s Rebase interactive For Fixing Up Commits
As you can see, git is actually quite useful for showing all the available “modification” options. For the purposes of this post, here are the options that could help you change previous commits.
p, pick
essentially leaves the commit as it was. Therefore, if you don’t want to change anything about a certain commit, then you should leave the option aspick
.s, squash
merges the commits into the one above. Once you save the changes togit rebase -i ...
, git will prompt you to enter a new commit message.f, fixup
will merge the commit into the previous one. The only difference tosquash
is that it will discard the selected commits message and use the previous message as the merged commit.
In addition, to edit the commits you need to change the option on the relevant line. For example, pick 622a242 Adding python code.
can be changes to s 622a242 Adding python code.
to be merged into the previous commit a70ddd1
.
A Practical Approach To Fixing Up Previous Git Commits
For the purposes of this tutorial, I created a dummy git repository with only two pushes:
a70ddd1 first commit
, where I added aREADME.md
file.622a242 Adding python code
, that adds another file to the repo.
Now, let’s say that I’ve noticed a mistake in my README.md
file, and I forgot to add a space in one of the lines in that file. The block below shows the before and after.
---> before
#Hello World
--> after
# Hello World
Briefly speaking, the commit a70ddd1
should have included that extra space before Hello World
so it properly displays the markdown text as a title.
Naturally, you could commit your changes by typing git add README.md
, then git commit -m "fixup README.md space
. However, if you push the change, your history will look a little off with a random commit that fixes up a previous commit with a simple space change. Nothing wrong with this, but if you would like to keep your git history
cleaner, you can use rebase interactive to merge your new “fixup” commit into the previous commit that added README.md
in the first place.
Reordering And Fixing Up with Git Rebase Interactive
Assuming that you added and committed (and even pushed) the whitespace change above, we will use git rebase -i HEAD~3
to “edit” our git history. Specifically, I am using HEAD~3
to show the latest three commits, as these include my latest README.md
space change, the intermediate Python file commit, and the initial README.md
commit.
Following that, you can then change the history from the following
pick a70ddd1 first commit
pick 622a242 Adding python code.
pick 8733b3s fixup README.md space
...
To this
pick a70ddd1 first commit
f 8733b3s fixup README.md space
pick 622a242 Adding python code.
...
In other words, we grouped the latest README.md
changes with the initial one (reordering), and also chose to “fixup” the latest change to README.md
. This will essentially “merge” the latest README.md
change into the old one, discarding the latest commit message.
My history now looks like the following.
commit a25eb8a1ea79fe747edc59f21ef3d2b4b507c419 (HEAD -> main)
Author: matheusgomes28 <matheusgarcia28@gmail.com>
Date: Mon Jul 11 20:49:42 2022 +0100
Adding python code.
commit 9f4f2faefb5c3bcb3ef065353d4e7f5559f17fb0
Author: matheusgomes28 <matheusgarcia28@gmail.com>
Date: Mon Jul 11 20:27:30 2022 +0100
first commit
To summarise: I’ve merged my latest README.md
changes into the previously pushed README.md
commit. Furthermore, git has discarded the latest commit message, and the new “old” commit has the same message as the initial README.md
change.
Since the history hashes have changed, you will need to force push. I’d recommend using git push --force-with-lease
, but read the following section for more information.
Force Pushing When You’ve Changed The History Tree
When using git rebase -i
, you will change your git history tree in some cases. For example, wen squashing, fixing up or amending commit messages of previously committed changes.
In this case, you will need to force push your changes to your branch, as you are overriding the previous history.
This can be a downside to rebase interactive. For example, if you’re working in a branch with multiple people, you run the risk of overriding their work if you’re force pushing your local out-of-date branch. Therefore, I’d recommend always using git push --force-with-lease
to force push, as --force-with-lease
will tell git to reject the changes in case your branch is out of date.
If you’re only modifying commits that you haven’t yet pushed, you will not need to force push! This is another advantage of only pushing your work when you believe you’ve completed it.
TL;DR Using Git Rebase Interactive To Change Previous Commits
git rebase -i HEAD~N
will let you edit the N
most recent commits in your branch.
Stick to only “fixing up” commits with smaller changes, that should have been in the commit in the first place. Having complicated fix ups can make the process a lot more cumbersome than an easy fix.
Rebase interactive can change the history hashes, meaning you will need to force push it. If you’re only changing unpushed commits, you can just git push
, without force push.