loading...
Cover image for Easily Squash, Reword, Amend, and Sort Commits with Git Rebase

Easily Squash, Reword, Amend, and Sort Commits with Git Rebase

jacobherrington profile image Jacob Herrington (he/him) ・4 min read

Git Guides - Jacob Herrington (9 Part Series)

1) 10 Git Tricks to Save Your Time and Sanity 2) Git Bisect is Easy (How to Initiate the Robot Uprising) 3 ... 7 3) A Quick Guide to Hunky Git 4) 10 More Git Tricks That You Should Know 5) 4 Useful Solutions to Common Git Problems 6) A Fool-Proof Way to Keep Your Fork Caught Up in Git 7) How to Write Useful Commit Messages (My Commit Message Template) 8) Git Rebase Explained Simply 9) Easily Squash, Reword, Amend, and Sort Commits with Git Rebase

Recently (not that recently), I wrote an article explaining the oft-misunderstood git command: git rebase.

There I tried to ensure that anyone could glean the purpose and basic application of git rebase.

In this article, I'm going to explain a more intimidating application of git rebase. Choosing to rebase in this way allows me to convey the purpose and reasoning behind commits with clarity.

I'm talking using git rebase in the optional interactive mode. It is my preferred method of organizing code changes; good organization makes git a more powerful tool.

If you're unfamiliar with rebase, I'd give the previous article a look before reading this one. This article will provide a glimpse into more powerful applications of the command.

Running git rebase -i invokes a rebase in interactive mode.

Why is it called the interactive mode? Glad you asked. Interactive mode allows the programmer to influence how rebase executes. In the previous article, we used rebase to bring upstream changes into our branch, but we didn't manipulate any commits in that process.

In many ways, the process we previously followed is ideal; we didn't have to do any work, git handled everything for us!

Unfortunately, the real world is rarely ideal.

When it comes to git, we frequently make incomplete commits, forget to include important changes, or make bad commit messages.

In our scenario, we can imagine that we've got a branch called some-feature which looks something like the following:

47a0a7008b Add a rea322ally cool feature
6dbc838bae WIP forgot something from that feature
f2ee0378f9 WIP fixing a bug
f0978af68d Fixed the very annoying bug
61b6f8ea3e Fix a typo from my doc changes
8340051649 Update documentation
f8a81956e2 Fix documentation formatting

There are a few things that we might not like about this git history. For example, we have a couple of WIP (work-in-progress) commits that could be removed, a typo in one of our commits, and a commit that fixes a typo that we introduced in a previous change on this branch.

We are planning to merge this branch into another branch called master.

To fix the obnoxious git mistakes in this branch, we can use git rebase -i master.

When you run git rebase -i with another branch you'll be dropped into a text editor with the following prompt:

pick 47a0a7008b Add a rea322ally cool feature
pick 6dbc838bae WIP forgot something from that feature
pick f2ee0378f9 WIP fixing a bug
pick f0978af68d Fixed the very annoying bug
pick 61b6f8ea3e Fix a typo from my doc changes
pick 8340051649 Update documentation
pick f8a81956e2 Fix documentation formatting

# Rebase a6305fd0a..4a5782397 onto a6305fd0a (1 command)
#
# 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

The prompt is extremely helpful, but sometimes a wall of text like this one can be intimidating.

In this view, git is showing you a list of the commits that exist in your branch, but not in master. They will be replayed on (or added on top of) the history in master. Each commit has a "command" alongside it.

For example, pick 47a0a7008b Add a rea322ally cool feature shows the command pick on the commit 47a0a7008b followed by its commit message.

Each command does something unique, you can read through the commands listed in the prompt if you like, but my most used commands are: squash, reword, and fixup.

To progress in the rebase you should replace the pick command with your preferred commands, then save and close the file.

I'd fix our example history like this:

reword 47a0a7008b Add a rea322ally cool feature
fixup 6dbc838bae WIP forgot something from that feature
pick f2ee0378f9 WIP fixing a bug
squash f0978af68d Fixed the very annoying bug
pick 8340051649 Update documentation
fixup 61b6f8ea3e Fix a typo from my doc changes
squash f8a81956e2 Fix documentation formatting

A few things happened.

First, I grouped the documentation commits together. This makes it easier to squash and follow the git history.

Secondly, I chose to squash or fixup several of the commits that were work in progress commits or fixes for issues I might have created while working on this branch (minor typos, formatting, work-in-progress commits).

Finally, I chose to reword the first commit message because it contains a typo.

Upon saving and closing this file, git will go to work. Each time a commit is squashed or reworded, I will have an opportunity to write a new commit message.

For example, when I hit the first commit I can reword it to "Add a really cool feature." When I hit one of the squash commits, I'll have the option to merge both commit messages or rewrite it entirely. Those commits with the fixup command are treated exactly like the ones with squash except that the commit message is completely dropped.

Assuming we don't have any merge conflicts, once the rebase has completed, the git history might read something like this:

47a0a7008b Add a really cool feature
f2ee0378f9 Fix a bug
8340051649 Update documentation

Those commits are much easier to follow and should make for a cleaner PR.

There's more...

I'm writing a lot of articles these days, I run a podcast, and I've started sending out a newsletter digest about all of the awesome stories I'm hearing.

You can also follow me on Twitter, where I make silly memes and talk about being a developer.

Git Guides - Jacob Herrington (9 Part Series)

1) 10 Git Tricks to Save Your Time and Sanity 2) Git Bisect is Easy (How to Initiate the Robot Uprising) 3 ... 7 3) A Quick Guide to Hunky Git 4) 10 More Git Tricks That You Should Know 5) 4 Useful Solutions to Common Git Problems 6) A Fool-Proof Way to Keep Your Fork Caught Up in Git 7) How to Write Useful Commit Messages (My Commit Message Template) 8) Git Rebase Explained Simply 9) Easily Squash, Reword, Amend, and Sort Commits with Git Rebase

Posted on Aug 20 '19 by:

Discussion

markdown guide
 

This is a great article!

For people learning rebase, I'd recommend to just spin up a small repository with, like, a plaintext file, even without pushing to any remote and just experiment for a bit, see what does what and not be afraid to break anything.

 

My thought currently is, prefer not to rebase, unless in your local branch. Commitizen and Commitlint instead. That is, do it well and meaningful from the beginning.