Recently, I was contributing to rustfmt. when I stumbled across this problem. I was tasked with performing 2 squashes - 1 squash on the feature implementation and 1 squash on CI update. Then, update the commit message of the squashes.

Now this was something I had not done before and thus, I tried to perform the task. I guess I partially did it correctly since the end result was 2 commits, though 1 was squashed.

What I did

The commits of the implementation was the first set of commits and the CI commits were in the second part.

I squashed the CI commits by only looking at HEAD~n, where n is the number of CI commits.

The tricky part was squashing the remaining commits while maintaining the squashed commit. This was where I was unsuccessful.

pick <commit_hash_1> <1st line from commit message> # this is the squashed CI commit
pick <commit_hash_2> <2nd line from commit message>
pick <commit_hash_3> <3rd line from commit message>
pick <commit_hash_4> <4th line from commit message>
pick <commit_hash_5> <5th line from commit message>
pick <commit_hash_6> <6th line from commit message>
pick <commit_hash_7> <7th line from commit message> # first impl commit

# Rebase <base_commit>..<commit_hash_7> onto <base_commit> (7 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
# 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.
#

What I did was I changed all the remaining commits from pick to squash except the first and last. This caused all the commits to from the implementation to be squashed BUT the CI commits were lost in the process.

Lucky for me, the CI commits was effectively a simple change so I just made a single commit.

What I should have done

Instead of performing interactive rebase twice, I could have done it once. I could have done something like this:

pick <commit_hash_1> <1st line from commit message> # squashes 1 to 5
squash <commit_hash_2> <2nd line from commit message>
squash <commit_hash_3> <3rd line from commit message>
squash <commit_hash_4> <4th line from commit message>
squash <commit_hash_5> <5th line from commit message>
pick <commit_hash_6> <6th line from commit message> # squashes 6 and 7
squash <commit_hash_7> <7th line from commit message>

Since Git reads the file topdown, what happens is the following:

  • Git sees pick and selects the commit
  • Git sees squash and squashes the commit into the currently picked one
  • Git sees pick and updates the currently picked one

This way, the commits will be nicely squashed.

Editing the messages on squash commits

Once the squashes are done, another interactive rebase has to be done. This time, change pick to edit. Upon exiting the console, this message pops up in the terminal:

Stopped at <commit hash> ...  <commit message>
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue

This allows editing of the commit with <commit hash>. Then, running git commit --amend, another window will be open to let us edit the commit message. Once done, we can just continue the rebase.

Conclusion

This was quite an interesting experience amending commits and squashing them. Even though I have used Git before but interactive rebase was something I rarely touch. I guess it is really true that contributing to open-source lets one pick up a thing or two.


<
Previous Post
First Blog Post!
>
Next Post
Brief Introduction to Mutual Exclusion