Simon Emms

Software Engineer, Technical Leader, Solutions Designer

An introduction to Git Rebase

Published:

This post assumes some familiarity with Git. This is an advanced concept and shouldn't be tried as your first foray into Git and version control.

During development on a feature branch, there will be times when we need to update our branch because the master branch has received an update. In order to keep a linear Git history, avoid using the git merge master command.

Having a linear history is more work, but it offers some advantages over a merge:

  • much easier to find the true source of a commit
  • no "merge commit" messages taking up space
  • reduced opportunity for conflicts
  • easier to revert a commit that's not been introduced as part of a pull request

Introducing Git Rebase

The git rebase master command is what we need to use instead.

# Assume you're in a branch feature/my-wonderful-feature
git checkout master
git pull
git checkout - # or git checkout feature/my-wonderful-feature
git rebase master
git push --force

If there are any issues, you can always use git rebase --abort.

What is Git Rebase

"This is the way it should have gone"

Git rebase is a way of rewriting your history. At a simple level, it unwinds all your commits to the last common commit with your branch, applies the master commits and then puts your commits after them.

By contrast, git merge master would create a merge commit AFTER your commits. When your feature branch gets promoted to the master branch, you will end up with merge commits polluting the history.

Using Git Rebase to squash commits

In a feature branch, there will often be commits in your log like this (git log --oneline):

067c88e gah, I'm stupid. I can see why CI broke
bf2a09e erm, not sure why CI has broken so another go
7fa9388 (feature/my-wonderful-feature): feat(some-brilliant-feat): this is a brilliant feature I've worked hard on
d4193f5 (master) fix(some-fix): some fix

We've all been there. CI/CD pipelines can be a pain to get right. These commits are fine in a feature branch, but we don't want these littering the history in master - a feature branch should be the work we did, logically separated into "good" commits (it makes finding problems later much, much easier).

It's a good idea to create a backup branch. With a Git Rebase, you are changing the branch irreparably.Run git checkout -b backup/my-wonderful-feature to create a backup.

Run git rebase -i HEAD~3 to get the 3 latest commits - the 3 can be changed to anything, but you shouldn't go beyond the last common commit (in this case d4193f5)

This will present an interactive screen that looks like this:

NB. The latest commit is at the bottom.

pick 7fa9388 (feature/my-wonderful-feature): feat(some-brilliant-feat): this is a brilliant feature I've worked hard on
pick bf2a09e erm, not sure why CI has broken so another go
pick 067c88e gah, I'm stupid. I can see why CI broke

# Rebase f4b6d01..9249bd0 onto f4b6d01 (3 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.

You can now change your history. In our example, we want to combine all the commits together - this is the fixup command. Change the file so that it looks like this:

pick 7fa9388 (feature/my-wonderful-feature): feat(some-brilliant-feat): this is a brilliant feature I've worked hard on
f bf2a09e erm, not sure why CI has broken so another go
f 067c88e gah, I'm stupid. I can see why CI broke

Now save your changes and exit. If you now look at git log --oneline, you will see that only the first commit exists. You can now run git push --force to replace your remote branch with your local branch.

You can also use this method for doing other things. Generally, I find that I use the pick, reword and fixup commands the most, although I also find edit and squash useful for editing my history before submitting a change.


Credits

Photo by Lance Grandahl

Do you like this article?