Spot an error? Know how to make this page better? I appreciate pull requests.

A faster way to amend a git commit

Making life in the shell a bit nicer

tldr: git amend-to <REF>.

The problem

The way I’ve grown to use git is to iteratively build up a collection, typically 2 to 4, commits as I work. I know the goal I am working towards may be best describes as a few distinct commits. This work flow requires me to amend commits behind HEAD fairly often. In the past I had two ways of achieving this.

The first method I’ve used since my early days of git is to use git stash and interactive rebase. This way does work, but involves a fair amount of trying and ruins any flow that you may have had going.

This is cumbersome. This is slow. It feels like there should be a better way.

A few months ago I decided there must be a better way. I began to just mark the changes I wanted to amend to a previews commit as a fixup be making a quick git commit -m "f: <some description that lets me know what is being fixed up>". I could continue to do this, working for a while without ever having my find drift to far. Then after a while I could do another interactive rebase and if I was good with naming everything, could reorder the commits and set the commits starting with f: to be fixedup.

This worked ok, sometimes, until it didn’t. I would forget which commit each fixup was associated with, causing me to end up with commits that were not atomic.

This work flow failed me due to my inability to reconcile conflicts. Some commit that was being fixed up would have a conflict, and it would always seem to snowball out of control.

The quest for a better amend

I knew there had to be a better way. This past weekend I set some time aside to explicitly look for a solution to the problem.

As I alluded to above my work flow is now as follows.

This magic is a combination of a git alias and a zsh widget.

the git alias

Here is the alias amend-to that I use.

amend-to = "!f() { git commit --fixup \"$1\" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash \"$1^\";}; f"

The magic of this alias is the GIT_SEQUENCE_EDITOR. The excerpts of the man pages are duplicated below. Committing with --fixup words the commit to be compatible with --autosquash. Adding --autosquash will reorder the rebase todo list and place the fixup’s in the proper place.

This alone would be a significant improvement to the work flow.

Setting GIT_SEQUENCE_EDITOR=true will suppress the instruction sheet completely by exiting immediately with an exit code of 0. This provides a zero interaction amend to any commit, assuming no conflicts. Now if a conflict does arise only dealing with a single change is easer to shepard though.

This alias is fully functionally. You do need to look up commit hashes manually. You need to be constancy referencing git log and copy and pasting. With the history being rewritten you are unable to rerun the same command from history to edit the same logical commit.

The zsh widget

To avoid all this work, I’ve written a small zsh widget that allows me to interactively select a commit form a list after hitting <ctrl>y. I’ve been a big fan of fzf using it for finding file within vim since at least Feb 2006. By piping in the results of git log into fzf I can easily select a commit to amend to.

This zsh function just echo’s out the git REF, which is not quite what I need. I need that value to be inserted into the ZSH input environment. This is done by adding the commit value to the end of the cursor position by appending to LBUFFER.

To enable the zsh binding I create a zsh widget.

All together

A nicer way to work.

External resources

Like almost everything I do, its on the shoulders of giants. Here are some posts I discovered that led me to my final solution, in no specific order.