Git: Fixup And Autosquash


Two features that were introduced in Git 1.7 that I only recently learned about are making my development life so much easier! They are autosquash and fixup.

What these two features do is let you do incremental commits that are automatically staged so that, at a later time, you can squash them all together into a single commit. That may not sound like a big deal, but when you're working with a lot of tiny changes and you want to eventually combine them, then it's a definite time saver.

MY OLD WAY

The way I used to work was to commit simple changes with messages like "merge with language binding" or "merge with SSL update" and similar messages. And these worked fine until you have a whole bunch of commits and you need to move them all around so that they're under the target commit. Not impossible, but somewhat unwieldy at least.

After I would get to a point where I was happy with the incremental changes, I would do an interactive rebase and move the commits around and squash them.

mcpierce@mcpierce-laptop:assembler (master) $ vi cpuid.s
mcpierce@mcpierce-laptop:assembler (master) $ git commit -a -m "merge with cpuid"
(do some more changes)
mcpierce@mcpierce-laptop:assembler (master) $ vi cpuid.s
mcpierce@mcpierce-laptop:assembler (master) $ git commit -a -m "merge with cpuid"
(now go and put them together)
mcpierce@mcpierce-laptop:assembler (master) $ git rebase -i HEAD~3

And at this point I have the commits and just need to change "pick" to "squash" for the commits I want to squash together.

pick dd1893c First assembler app: get CPUID information.
pick 5741c73 merge with cpuid
pick 8b5dffb merge with cpuid

# Rebase dd1893c..8b5dffb onto 04aad6e
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# 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

You can see in the above my two addition commits are lined up. At this point I have to change "pick" to "squash" in the last two and then save this in order for the squashing to occur. Not a huge effort, sure, but imagine if you had 15 or 20 such small commits and wanted them all to be ready to squash? You could use some vi search and replace magic, but how about leveraging git itself to do the job for you?

AUTOSQUASH

If you want to have git do the job for you, then all you need to do is modify the commit message when saving the incremental changes as such:

mcpierce@mcpierce-laptop:assembler (master) $ vi cpuid.s
mcpierce@mcpierce-laptop:assembler (master) $ git commit -a -m "squash! First assembler app: get CPUID information."
(do some more changes)
mcpierce@mcpierce-laptop:assembler (master) $ vi cpuid.s
mcpierce@mcpierce-laptop:assembler (master) $ git commit -a -m "squash! First assembler app: get CPUID information."
mcpierce@mcpierce-laptop:assembler (master) $ git rebase -i HEAD~3 --autosquash
Now when you're ready to commit you'll see:

pick dd1893c First assembler app: get CPUID information.
squash 5741c73 squash! merge with cpuid
squash 8b5dffb squash! merge with cpuid

# Rebase dd1893c..8b5dffb onto 04aad6e
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# 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

Git has already lined up the commits below the commit into which they will be squashed. Now when you save the rebase instructions, git will prompt you for the message on the squashed commit!


# This is a combination of 3 commits.
# The first commit's message is:
First assembler app: get CPUID information.

# This is the 2nd commit message:

merge with cpuid

# This is the 3rd commit message:

merge with cpuid

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   assembler/cpuid.s
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       assembler/cpuid
#       assembler/cpuid.o


But, wait! There's more! What if you don't want to change the original commit's message and just want to add these additional commits to? Here is where you'll use the fixup option.

FIXUP

If you don't want to bother with the commit message for the subsequent changes, then you can use the "fixup!" modifier instead of "squash!" as follows:


mcpierce@mcpierce-laptop:assembler (master) $ vi cpuid.s
mcpierce@mcpierce-laptop:assembler (master) $ git commit -a -m "fixup! First assembler app: get CPUID information."
mcpierce@mcpierce-laptop:assembler (master) $ git rebase -i HEAD~3 --autosquash

In these cases, during your rebase, you'll see:


pick dd1893c First assembler app: get CPUID information.
fixup 7032ee8 fixup! First assembler app: get CPUID information.
fixup 8320bbd fixup! First assembler app: get CPUID information.

# Rebase dd1893c..8320bbd onto 04aad6e
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# 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


Notice that, instead of squash, the lines now say fixup. When you save this rebase git won't prompt you for a commit message. Instead, it will just squash things together and save them using the original message.

Comments