Rebasing in Git to maintain history’s health

Have you ever felt that the Git history of your project is becoming a big mess and when you need to find out why a change was made you encounter a lot of small commits that were just adding tiny bits of code that you forgot to add in previous commits? Well, this also happened to me.

I’m a passionate learner so I’m always trying to find out new ways of doing things and questioning the current knowledge I have about a tool. When I found out that the Git history of my project was having a lot of meaningless commits, I started asking myself if there was a better way to add those changes to previously made commits so they can maintain cohesion and in the future, another developer doesn’t need to solve the puzzle by searching through multiple commits to finding the reason of the change.

This is when I found about Rebasing in Git. There are two ways that we can use Rebase: as a “replacement” of git merge and change, drop, and squash previously made commits. Gotcha, you can see that one of the use cases of the Rebase command fits perfectly the use case I'm searching for.

I’m not going to talk about git rebase as a "replacement" of git merge in this article, but if you are curious how is that so, I recommend you to read this article from the people at Atlassian where they explain it.

To explain how to do Interactive Rebase in Git I’ve created this simple example repository that has some meaningless commits that I want to get rid of.

Tig showing the commits in the repository
Tig showing the commits in the repository

By using tig we can observe multiple things:

Let’s do it

First, we’ll take the SHA-1 of the oldest commit that we want to retain as-is. In this case, the last commit we want maintaining as-is is the Adding my initial description file commit. This is because we are going to be fusing the two “Forgot …” commits with the Adding my personal information commit, hence not retaining it as-is.

Tig showing that the hash of the oldest commit we want to retain is: 6c8e26c67111b0cab4ce388f4899b3b879152f55
Tig showing that the hash of the oldest commit we want to retain is: 6c8e26c67111b0cab4ce388f4899b3b879152f55
Tig showing that the hash of the oldest commit we want to retain is: 6c8e26c67111b0cab4ce388f4899b3b879152f55

Now that we have the hash of the commit, we’re going to execute the following command which is going to run Rebase in Interactive mode, so we can easily pick which actions we want to perform to the commits.

git rebase -i 6c8e26c^

You may notice two things in the command:

Executing this command will open the editor that we have configured in Git with the following text:

pick d7ce763 Adding my personal information
pick b049848 Forgot to add accent in my last name
pick b215b9e Forgot to add accent in my city
pick b544998 Adding software development skills
# Rebase 6c8e26c..b544998 onto b544998 (4 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
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

By reading at the commands, the best fit of actions for what we want to do is the following:

By using the Fixup action instead of the Squash action, we’re telling Git to move the changes of both commits to the above commit (Adding my personal information) and not ask us to rewrite the commit message or append their commit messages to the one we had originally.

This is how our file will look like after making the changes:

pick d7ce763 Adding my personal information
fixup b049848 Forgot to add accent in my last name
fixup b215b9e Forgot to add accent in my city
pick b544998 Adding software development skills
# Rebase 6c8e26c..b544998 onto b544998 (4 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
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Now it’s only necessary to save and quit the editor and Git will do these operations instantly.

This is the final result of running our rebase command with the fixup.

Tig showing that the meaningless commits are gone!”

There are no more meaningless commits, both have been fused with the commit that added the personal information. This way no one has to solve the puzzle by having multiple commits that fix stuff that we mistyped or forgot to do in previous commits that are not yet merged into the stable branch or shared with another coworker.

You can also use this Interactive Rebase to edit something in a previous commit that you just happened to see right now, you just have to use the edit action and Git will take you to that moment in history so you can modify it. The only difference when you finish making the change is that you don’t do a git commit but you do a git commit --amend.

Conclusion

Maintaining meaningful commits in your Git history is important, it helps other developers flow through changes in a more natural way and waste less time reading meaningless changes that were added just to comply with Git’s rule to always add a commit message.

Rebase will help us achieve this goal by giving us tools to traverse the history of our repository and do changes to our commits so we don’t need to create a single commit to fix something insignificant but we can edit the commit in which the problem it was introduced.

We may need to take care of rebasing though. It is good that we only use it in branches that are not shared with anyone else and the commits are not merged into a public branch. After our branch is merged in the main branch of the project is not a good idea to rebase it, we’ll have to create a new commit.

Software Engineer. Passionate about technology. Developer by passion. I talk about #development, #tech in general and nonsense.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store