Getting Geeky with Git

Interactive Rebase

Marcin Wanago
Git

In the previous part of this series, we’ve looked into how rebase can improve our merges. It is a lot more powerful, though, and can come in handy in other situations. In this article, we explore how to perform interactive rebasing.

Introduction to the interactive rebase tool

When doing an interactive rebase, we can modify existing commits. This includes those that we’ve already pushed to our remote repository.

The above can prove to be useful in various scenarios. One of the most simple ones is fixing a typo in a commit message. Another might be adding a file that we forgot about. We might have also made a mistake in our code that we would like to fix without creating a new commit.

No matter what the cause is, we need to remember the golden rule of rebasing. Since it involves overwriting history, we should avoid doing that to branches used by other developers. Not following this principle will cause various issues to your teammates.

To start an interactive rebase, we need to use the  -i flag. The second thing to indicate is how far we want to go back when rebasing.

In the third part of this series, we’ve learned that usually, HEAD points to the same commit that the branch that we currently use. We can use this piece of information when performing an interactive rebase.

1git rebase -i HEAD~4

The above means that we will be able to modify four last commits.

You can also use  git rebase --root to rebase all of your commits

Modifying existing commits

Let’s try the above on the repository for the NestJS series. When we do that, a text editor opens up.

1pick d5e397d feat(Files): configure AWS SDK
2pick 535fa30 feat(Files): upload files to Amazon S3
3pick b54aa70 feat(Users): link uploaded Files to the user
4pick a1d4bc6 feat(Users): add the possibility to delete avatars

An important thing to notice is that the above commits are in the reverse order. This is because when we run the above command, Git removes those commits and applies the included changes once again from the oldest to the newest. The useful thing is that we can change the commits in this process.

If we lave the  pick word at the beginning of the line, the commit will not change in the process of rebasing. We have a few options here, some of them being:

1p, pick <commit> = use commit
2r, reword <commit> = use commit, but edit the commit message
3e, edit <commit> = use commit, but stop for amending
4s, squash <commit> = use commit, but meld into previous commitf
Aside from choosing one of the possibile keywords, we can also remove commits or change their order

The most straightforward of the above is the  reword. If we replace one of the  pick keywords with it, we can edit the commit message.

Above, you can see that Git opened the  .git/COMMIT_EDITMSG file in an editor called nano. You can change it to any text editor that fits your needs. To use Visual Studio Code instead, for example, run this:

1git config --global core.editor "code --wait"
The  --wait command makes sure that Git will wait for the VSC window to be closed before continuing

Sometimes we would like to undo the rebase that we’ve just performed. The most straightforward way to do so is to run  git reset --hard origin/[branch-name]. By doing so, we reset our local branch to the state that is on our remote branch.

Editing a commit

Editing the contents of an already pushed commit might sometimes be necessary. Imagine committing the  .env file containing some sensitive data that we don’t want to be in our Git history.

1edit d5e397d feat(Files): configure AWS SDK
Stopped at 8e7b938… feat(Files): configure AWS SDK You can amend the commit now, with git commit –amend Once you are satisfied with your changes, run git rebase –continue

Now we can modify the commit in a way we see fit.

1git rm --cached ./.env
2echo ".env" >> .gitignore
3git add ./.gitignore
With  git rm --cached the file does not disappear from our drive

Once we modified all that we need, we can amend our commit.

1git commit --amend
2git rebase --continue

All that’s left is to push our changes with the  --force-with-lease flag.

1git push --force-with-lease origin [branch-name]
In the previous part of this series we’ve learned that the above flag is a safer option that refuses to update the branch if somebody updated the remote branch.

Squashing commits

It is a good practice to commit our work frequently. It allows us to revert in a straightforward way and avoid the loss of our progress if something goes wrong.

We might want our commit history to be more straightforward, though. This is why we might want to squash our commits before merging our work.

1pick d5e397d feat(Files): configure AWS SDK
2pick 535fa30 feat(Files): upload files to Amazon S3
3pick b54aa70 feat(Users): link uploaded Files to the user
4squash a1d4bc6 feat(Users): add the possibility to delete avatars

The above begins the process of squashing. To finish it up we need to provide a single commit message for all of the work we want to squash.

Instead of multiple commit messages, we need to come up with a single one.

Summary

Rebasing is quite a powerful tool that lets us clean up our mess. With it, we can maintain a clean project history and correct our mistakes. While you might not feel the need to use it every day, it might come in handy in some cases. It is a good idea to be aware of what we can do with it.