At work the other day, a colleague mentioned they'd found my post on Git's
--update-refs option for working with stacked branches, and wondered if it worked with the
--autosquash option. Well,
--autosquash was news to me! In this post I look at what it does, why it's useful, and how to enable it by default!
I start this post by discussing interactive rebasing in general - if you already know how and why to use interactive rebase feel free to skip ahead!
A typical interactive rebase
I'm a big fan of git rebasing. I like to work away in a branch, creating lots of tiny self-contained commits, related to different aspects. Each commit deals with a small section of the feature.
At least, that's the idea. Inevitably it's never as simple as that—there's typos or bugs to fix. That's where rebasing comes in.
Let's say that "Add base types" has a typo I want to fix. I create a new commit that fixes the typo (and only fixes that typo) and commit it.
This is probably what most people's Git workflow looks like. The difference with a rebase-focused workflow is that instead of leaving this "messy" Git history, we can tidy this up by squashing/fixing up the typo commit with "Add base types".
squasha commit, you merge it with its parent (previous) commit, combining the commit messages of both (and optionally changing the message). When you
fixupa commit, you merge it with its parent in the same way as
squash, but you just reuse the commit message of the parent as-is, without changing it. There's a variation of
fixup -Cthat replaces the commit message with the fixup commit's message (and discards the parent commit message).
I'm a fan of doing this tidying up. It means each commit is a logical step in the implementation of the feature. I find that makes it easier to review, and essentially guides the reviewer through the changes. Rebasing is essential to create this "narrative", as you inevitably won't actually create your code in such a clean way. The alternative, just leaving the commits as they are, means reviewers have to view all the changes to your code at once, essentially only viewing the end-state, which can make it harder to review IMO.
OK, so we want to fixup our typo commit. Any time I want to squash or rearrange commits, I perform an "interactive rebase". You can initiate an interactive rebase from the command line by running:
git rebase -i main
main is the branch/commit that serves as the "root" of the rebase.
Only commits between the root and the tip of your current branch are included in the rebase. You can use
--rootto rebase your entire git tree.
When I run the above command, the default editor opens, and shows something like the following:
pick a43f263 Add initial interfaces pick a643ac3 Add base types pick a08d5fa Add implementation pick 01e156a fix typo in base types # Rebase fba8887..a08d5fa onto fba8887 (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 [-C | -c] <commit> = like "squash" but keep only the previous # commit's log message, unless -C is used, in which case # keep only this commit's message; -c is same as -C but # opens the editor
This lists all the potential commits for rebasing, their commit hash, and the title of the commit. You can edit this list by moving lines around and changing the
pick command to something else, as described in the comments (there are more potential commands, I've truncated it for brevity!).
Note that the commits are listed from earliest to latest, so the most recent commits are shown at the bottom of the list. This is the opposite direction to the way most git graph visualizations show the most recent commits at the top of the graph.
For my example I would move the last line, commit
01e156a up one, so that it's just after the "Add base types" commit, and change the command to
fixup (because I want to discard the message).
pick a43f263 Add initial interfaces pick a643ac3 Add base types fixup 01e156a fix typo in base types pick a08d5fa Add implementation
Once you close the editor, Git will apply your commits, making your changes, and assuming you don't have any merge conflicts, will display something like the following:
Successfully rebased and updated refs/heads/feature.
And you can see that the typo commit has been merged into the original:
This is the typical command-line process for performing an interactive rebase, and I use it occasionally, but as I described in a previous post, I do most of my rebasing with Rider's GUI. I could perform the exact same interactive rebase from inside Rider.
I'm a big fan of Rider's Git integration, but I spoke about it previously, so I won't go into detail here. Needless to say, you can essentially do the same things in Rider as you can from the command line, but with extra niceties like rewording commit messages in-line before you start the rebase and viewing the commit details on the right, it provides a smoother experience IMO.
That's a typical interactive rebase flow, so where does
--autosquash come in, and how does it change things?
Automatic re-ordering of interactive rebase with
When my colleague pointed out the existence of
--autosquash to me, I immediately went to the git documentation. If you're already familiar with Git, the documentation is a great place to learn about new options that are introduced all the time. The docs for
--autostash reveal the following:
When the commit log message begins with "squash! …" or "fixup! …" or "amend! …", and there is already a commit in the todo list that matches the same ..., automatically modify the todo list of
rebase -i, so that the commit marked for squashing comes right after the commit to be modified, and change the action of the moved commit from
fixup -Crespectively. A commit matches the ... if the commit subject matches, or if the ... refers to the commit’s hash.
So to summarise,
--autosquash uses the following workflow:
- You create a parent "target" commit. Let's say it has SHA
0ab12f32and the commit message is
My parent commit
- Some time later you create a commit that you want to fixup with the target. This "fixup" commit should have one of the following commit messages:
fixup! My parent commit
- When you subsequently run
git rebase -i --autosquash, the commits will be automatically rearranged to move your fixup commit below
My parent commit, and the action will be changed to
Lets explore this with the example from the previous section.
A worked example of using
We have this initial set of commits:
We realise there's a typo in a file committed in the
Add base types commit, so we make the change and prepare to create a commit. However, this time we don't choose an arbitrary commit message. Instead we use the defined format
fixup! Add base types (we could also use
If we're working from the command line, we can generate this commit message "automatically" by passing the commit SHA of the commit we want to update and using the
git add . git commit --fixup a643ac3
This is "easier" in one sense, in that it looks up the subject (the first line of the commit message) of the target commit, and creates a commit message with the correct format:
Of course, it's not necessarily convenient to find the SHA of the target commit, but you also probably don't want to make sure you get the target subject absolutely perfect. In that case you can use a "search" to find the commit instead:
git add . git commit --fixup ":/base types"
:/ prefix means "try to find a commit containing the phrase
base types". It will stop at the first commit it matches and use that. But be warned: the search is case-sensitive, and if the search doesn't match a commit it won't error, it will just use the provided string.
For example, if we used a search string that doesn't find any commit (in the following case I'm using the wrong case):
git commit --fixup :/Base
It will create a commit message like
fixup! Base which doesn't match anything!
If this all feels a bit cumbersome for creating commit messages, I agree. In the next section I'll show how Rider makes this all a lot nicer 😉
Ok, so we have committed our fix, time to rebase. If we run
git rebase -i --autosquash main
The editor pops-open, and our commit list is pre-filled!
pick a43f263 Add initial interfaces pick a643ac3 Add base types fixup 48009ba fixup! Add base types pick a08d5fa Add implementation
We can just close the editor, and the rebase is complete, without having to manually reorder any of the commits ourselves. Very nice.
Using Rider to generate
--autosquash commit messages
The most painful part of this process is generating the commit message for the fixup commit as you need to know the SHA of the target commit, or be sure that you can accurately find the target commit's subject with the search string. Luckily, Rider makes this all much simpler.
Inside Rider's commit message box, if you type
squash, Rider pops up a list of recent commits for you to choose from:
You can simply press Enter or Tab and a commit message with the correct format is inserted; no looking up of SHAs or fuzzy matching, just the correct commit message for autosquash!
Note that Rider doesn't support the
amend!prefix for doing
fixup -Ccommits, it only supports
squash!. If you want to use
amend!, I suggest typing
fixup, selecting the commit from the list, and then manually changing the
This is a very handy little feature, but unfortunately Rider doesn't currently support the
--autosquash flag in its rebase dialog 😢
Luckily, there's a workaround!
--autosquash by default
--autosquash only looks for "special" commit messages prefixed with
amend!, it seems pretty safe to always add this flag. Of course, that means you need to remember to append
--autosquash every time you call
git rebase -i, which could be a bit tedious.
Instead, you can enable
--autosquash by default in git config! Run the following command:
git config --global rebase.autosquash true
and this will update your global configuration to the following:
[rebase] autosquash = true
If you've chosen to enable
--update-refs for working with stacked branches, as I described in a previous post, your
[rebase] section will look like the following:
[rebase] autosquash = true updateRefs = true
Now you don't need to remember the
--autosquash flag. Instead, if you need to disable the functionality for a specific rebase, use the
As well as simplifying the CLI experience, updating the git config has the advantage that it fixes Rider's interactive rebase dialog to work with autosquash too! Open up the interactive rebase dialog in Rider and you'll see all the commits rearranged and marked for
fixup as you'd hope:
I'm definitely going to start using this feature in my standard rebase workflow now, especially as Rider makes it so smooth to create the commit messages! This is one area where Rider (and I assume other IntelliJ IDEs) really do seem miles ahead of the competition (Visual Studio, Visual Studio Code)
In this post I described a common workflow I use for developing that uses Git's interactive rebase feature to move and squash commits. I then introduced the
--autosquash rebase flag that removes a small amount of friction from this workflow, by automatically rearranging the commit list for you when you start a rebase. To use
--autosquash, you must create your "squash" commits with a
fixup! prefix, followed by the target commit's subject or SHA. Finally I showed how you can automatically use the
--autosquash flag for all rebases by setting the git config value