As I work on a branch I often issue fixup commits for changes that I would have wanted to belong to an earlier commit in that branch. For example if I add some tests and then later tweak something small such as the description for the test then I’d like to just fix up that original commit rather than add a new commit just to record the improvement to the test description.
One reason for this is that the branch is that if I instead have a commit for each change I have made then before sending the branch for review I will want to manually squash all those commits.
Sometimes this is what I want (e.g. I may later decide to drop this change), but for minor changes this overhead is unnecessary.
For a long time now my workflow has been the following:
- Grab the SHA of the commit that I want to fix up. This is generally the SHA
of the line that needs to be changed, or a neighbouring line which I know is
part of the same commit.
:G blame
to open the vim-fugitive blame viewyw
to yank the SHA under the cursor
- Make the changes and stage them (bound to
<leader>hs
in my setup) :G commit --fixup=<c-r>"
to issue a fixup, pasting the SHA from the default register.
This is relatively quick but does involve lots of repeated work over the course of a working day, just to issue fixup commits.
Here are some vim mappings and supporting function to reduce the repetitive elements of this workflow:
func! YankLastShaForLine()
:Git blame
:normal! yiw
" Need to make blame buffer modifiable to be able to delete it
:set ma
:bdelete
:wincmd p
endfunc
" Workflow for quick fixups:
" Use ys to yank the last SHA for the current line
" Use yf to form a fixup command
nmap <leader>ys :call YankLastShaForLine()<CR>
nmap <leader>yf :Git commit --fixup=<c-r>"
Now to fixup an earlier commit the flow is:
- While on the line to change, hit
<leader>ys
to yank the latest SHA for this line - Make the changes and stage them
- Hit
<leader>yf
1 to prepare the fixup command - After a visual check of the command, hit enter to commit
This reduces the bulk of the work, making it fairly effortless to fixup the commit of a given line. Note that any amount of changes could be staged, this is not limited to changes made to a single line.
The other part of this workflow is to enable autosquash
in your
~/.gitconfig
:
[rebase]
autosquash = true
When running git rebase -i main
all the fixup commits will be presented as
fixup
rather than pick
, and will therefore be listed under the commit that
they alter. After reviewing this briefly, saving and exiting will cause all the
fixups to be applied.
This workflow was tested in Neovim 0.7, but I expect it will work without modification in vim.
There is an open PR for
vim-fugitive which adds
mappings for fixing up a commit identified in the :G blame
pane. It hasn’t
been merged in 3 years at this point in time, so I decided to add a
function/mapping in my own configuration instead.
-
yf
is a poor choice of mapping in terms of the vim editing language (we are not yanking anything here) but I find this to pair nicely withys
, and will give some thought to a better pair of mappings that will be as memorable and intuitive for me. ↩︎