When working on projects both personally and professionally, I use Git for version control. More often than not, If I’m working on a fix or a new feature, I will work from a new branch which has a single, specific focus and then merge it into the main branch when the work is complete. This means that if the branch I was working on had multiple commits, It’s reasonable to squash them as they’ll all relate to a single piece of work.

This article will explain how to squash your own commits to tidy up your commit history. I’ll also show you how to restore/reset your branch to a previous point in time should you have any troubles with your interactive rebase.

Using Git Interactive Rebase to Squash Old Commits

Rebase previous commits

To squash previous commits into a single one, you can use the rebase command in interactive mode. Here’s a quick example where n is the number of historic commits you’d like to view interactively:

git rebase -i HEAD~n

If you’re not sure how many commits you’d like to use in the rebase, you can use the reflog command to view a commit history:

git reflog

Once you’ve started the interactive rebase, you’ll be presented with a text editor. The exact editor will depend on your terminal session’s default. My preference is Nano, but the other common ones you’ll likely see are Vi/Vim.

Here’s an example of what you’ll see in the terminal text editor, this is using 5 commits:

pick 852gc11 ADD: Migration to convert website_fetches body field to longtext
pick a35s64a ADD: Ability to add websites via the dashboard
pick 12d5727 UPDATE: Websites URL structure. Added Classes/Methods for editing and deleting too
pick 564g123 ADD: Test suite beginings and tests for some existing features
pick a6e2dg3 ADD: Full WebsiteRelation test coverage and started User tests

# 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.

On the left-hand side, you’ll notice each commit is labelled pick. This is the part you’ll want to look at changing. The list underneath explains each option’s function. Primarily, I find that squash is the main one you’ll want to use. This squashes the given commit so that its changes are merged into the commit before it.

Per my previous example, if I wanted to squash all of my 5 commits into 1 single commit, I would edit the top part to the following:

pick 852gc11 ADD: Migration to convert website_fetches body field to longtext
s a35s64a ADD: Ability to add websites via the dashboard
s 12d5727 UPDATE: Websites URL structure. Added Classes/Methods for editing and deleting too
s 564g123 ADD: Test suite beginings and tests for some existing features
s a6e2dg3 ADD: Full WebsiteRelation test coverage and started User tests

I’ve used the shorthand version of squash: s. This is just a bit faster as it’s less typing, but does the same thing.

Once you’ve made these changes you can save and exit the text editor. On Nano you would use ctrl + o to save, then ctrl + x to exit. On Vi/Vim you can use :wq to ‘write and quit’.

Update the new commit message

You’ll then be given a new text editor window so you can tailor the commit messages from squashed commits. The squashed commit messages will be brought into the notes for the new main commit. This is the last step in making changes to your commit history.

# This is a combination of 2 commits.
# This is the 1st commit message:

My first commit message goes here

# This is the commit message #2:

My second commit message goes here

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.

Following the guidance provided by the text editor, you can edit the commit messages as required before then saving your changes as per my instructions from the end of the previous section.

Push and overwrite changes

To push your changes, you have to use the force flag on your push as follows:

git push -f

The -f flag forces the remote to accept the given push, overwriting the commits/changes in place on the branch already.

Note: Using the -f flag can be dangerous and cause potential loss of work. You must be very careful and certain before issuing this command.

If you’ve carried out all of the steps correctly, when you visit your branch on GitHub/GitLab, you should now see your commits have been squashed and the history will reflect your rebase.

Using Git Reset to Restore Your Branch

Let’s say you’ve done an interactive rebase and aren’t happy with the changes which you’ve pushed to your remote repository. Thankfully, you can still salvage the work pretty reliably using git reset.

Find Which Commit to Restore

As with the previous instructions for using an interactive rebase, you’ll first want to review your history using the reflog command:

git reflog

Assuming you’re trying to undo an interactive rebase, you’ll be looking for a commit that shows a rebase (start) action. The output of the reflog command will list your git changes as a list. Here’s an example of what you’re looking for:

HEAD@{6}: rebase (start): checkout HEAD~5

Reset to the desired commit

Looking at the desired commit we’d like to restore, we can see it’s referenced as HEAD@{6}. To reset back to this commit, you’ll use the following command:

git reset --hard HEAD@{6}

Your local session will now be restored to this point in time, undoing any changes carried out since then. If you’re happy, as with the previous instructions, it’s now time to push this to the remote repository using the -f flag. This will overwrite the commits on the remote repository. The -f flag must be used for this to work as desired.

git push -f

Note: Using the -f flag can be dangerous and cause potential loss of work. You must be very careful and certain before issuing this command.