Technosorcery

Ramblings of Jacob Helwig.

Fun With the Upcoming 1.7 Release of Git: Rebase --interactive --autosquash

| Comments

The upcoming Git 1.7 has a lot of really nice improvements, and new features. One of the big new features is the --autosquash argument for git rebase --interactive.

If you’re anything like me, then you commit a lot, while you’re working on something, and use git rebase --interactive judiciously to clean up all these incremental commits into a presentable format. If you’re a bit more like me, then you’ll often end up doing multiple git rebase --interactive passes to split commits apart, and squash them back into other commits.

Git just gained the ability to make this a little faster. If you know what commit you want to squash something in to you can commit it with a message of “squash! $other_commit_subject”. Then if you run git rebase --interactive --autosquash commitish, the line will automatically be set as squash, and placed below the commit with the subject of $other_commit_subject.

For example:

Prepare commits for use with –autosquash
1
2
3
4
5
6
7
8
9
10
11
12
$ vim Foo.txt
$ git commit -am 'Change all the "Bar"s to "Foo"s'
[topic 8374d8e] Change all the "Bar"s to "Foo"s
 1 files changed, 2 insertions(+), 2 deletions(-)
$ vim Bar.txt
$ git commit -am 'Change all the "Foo"s to "Bar"s'
[topic 2d12ce8] Change all the "Foo"s to "Bar"s
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim Foo.txt
$ git commit -am 'squash! Change all the "Bar"s'
[topic 259a7e6] squash! Change all the "Bar"s
 1 files changed, 2 insertions(+), 1 deletions(-)

If we run git rebase --interactive --autosquash origin/master from here, the pick-list will look like this:

Generated picklist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pick 8374d8e Change all the "Bar"s to "Foo"s
squash 259a7e6 squash! Change all the "Bar"s
pick 2d12ce8 Change all the "Foo"s to "Bar"s

# Rebase b6bee12..259a7e6 onto b6bee12
#
# 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
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

When you get to the squash, you’ll have a commit message like:

Generated commit message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This is a combination of 2 commits.
# The first commit's message is:

Change all the "Bar"s to "Foo"s

# This is the 2nd commit message:

squash! Change all the "Bar"s

# Please enter the commit message for your ch anges. Lines starting
# with '#' will be ignored, and an empty mess age aborts the commit.
# Not currently on any branch.
# Changes to be committed:
# modified:   Foo.txt
#

If you were paying attention earlier to the pick-list, you’ll notice that there’s also a fixup command available. If we had specified fixup!, instead of squash! as the commit message’s prefix, then the pick list would have ended up as:

Generated picklist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pick 8374d8e Change all the "Bar"s to "Foo"s
fixup cfc6e54 fixup! Change all the "Bar"s
pick 2d12ce8 Change all the "Foo"s to "Bar"s

# Rebase b6bee12..cfc6e54 onto b6bee12
#
# 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
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

With the following in your editor for the combined commit message:

Generated commit message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This is a combination of 2 commits.
# The first commit's message is:

Change all the "Bar"s to "Foo"s

# The 2nd commit message will be skipped:

# fixup! Change all the "Bar"s

# 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:
# modified:   README.markdown
#

Notice that the fixup! commit’s message is already commented out. You can just save out the message as-is, and your original commit message will be kept. Very handy for including changes when you realize that you forgot to add part of an earlier commit.

Here’s a few aliases I have setup to make all this easier:

~/.gitconfig aliases
1
2
3
4
[alias]
    fixup = !sh -c 'git commit -m \"fixup! $(git log -1 --format='\\''%s'\\'' $@)\"' -
    squash = !sh -c 'git commit -m \"squash! $(git log -1 --format='\\''%s'\\'' $@)\"' -
    ri = rebase --interactive --autosquash

Here’s how they would be used in our previous example:

Example using aliases
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ vim Foo.txt
$ git commit -am 'Change all the "Bar"s to "Foo"s'
[topic 8374d8e] Change all the "Bar"s to "Foo"s
 1 files changed, 2 insertions(+), 2 deletions(-)
$ vim Bar.txt
$ git commit -am 'Change all the "Foo"s to "Bar"s'
[topic 2d12ce8] Change all the "Foo"s to "Bar"s
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim Foo.txt
$ git add Foo.txt
$ git squash HEAD~2
[topic 259a7e6] squash! Change all the "Bar"s to "Foo"s
 1 files changed, 2 insertions(+), 1 deletions(-)
$ git ri origin/master

Similarly, git fixup HEAD~2 would create a fixup! commit to be used with git rebase --interactive --autosquash (Aliased as: git ri).

Edit 2010-02-13: Fix alias examples.

Edit 2011-12-11: Change ‘Foo’s to “Foo”s to avoid grammar confusion.

Follow-up 2012-08-05: git commit gained flags to make the aliases much more simple.

Comments