Some years ago I was really bored of addressing review comments in a PR and doing the amend to the right commit. It also felt kind of silly to do it manually when the process is quite mechanical and 99% of the time we either amend to the last commit that changed a particular chunk or to the last commit that changed a file.
So, I created these git aliases that I ended up sharing with friends and co-workers quite some times already. In case they are useful for anyone else, here they are:
[rebase]
# So git rebase moves the fixup commit for us.
autosquash = true
[alias]
# Tries to guess the commit to fixup by just looking at the last commit
# that modified the first file shown by git-diff --cached.
z-guess-fixup-commit = ! file=$(git diff --cached --no-prefix HEAD | grep '^+++' | awk '{ print $2 }' | head -n 1) && \
echo $(git log -n 1 --pretty=format:%H -- "$file" | head -n 1)
# Tries to guess the commit we want to fixup, by checking the first
# chunk modified in git-diff --cached.
# It just prints the commit hash that last modified this chunk
z-guess-fixup-commit-harder = ! changed_line=$(git diff --cached | grep '^@@' | head -n 1 | awk '{ print $3 }') && \
line_start=$(echo "$changed_line" | cut -d ',' -f1 | tr -d '+') && \
line_len=$(echo "$changed_line" | cut -d ',' -f2 | tr -d '+') && \
line_end=$(( $line_start + $line_len)) && \
file=$(git diff --cached --no-prefix HEAD | grep '^+++' | awk '{ print $2 }' | head -n 1) && \
fixup_commit=$(git log -n 1 --pretty=format:%H -L "${line_start},${line_end}:${file}" | head -n 1) && \
echo $fixup_commit
# This creates a commit --fixup with the last commit that modified file/line.
cm-fixup = ! git commit --fixup=$(git z-guess-fixup-commit)
cm-fixup-harder = ! git commit --fixup=$(git z-guess-fixup-commit-harder)
show-fixup-commit = ! git show $(git z-guess-fixup-commit)
show-fixup-commit-harder = ! git show $(git z-guess-fixup-commit-harder)
NOTE: the
z-prefixed aliases are just internal helpers you shouldn’t call directly.
This adds two different ways to amend a diff in the working tree to a commit:
- cm-fixup: Amend to the latest commit that changed the file
- cm-fixup-harder: Amend to the latest commit that changed the same chunk
It just chooses the right commit to amend to, and does a git commit --fixup=<hash of commit to amend>. But it’s tricky to use, so let me tell you how you should use it.
Let’s say you have a bunch of things in your working tree and you want to squash each part to the right commit. Then, for each chunk in your working tree you do:
# You add ONLY ONE CHUNK. The aliases check only the first chunk in git-diff --cached
git add -p
git cm-fixup # If you want to fixup to the last commit that changed this file
git cm-fixup-harder # If you want to fixup to the last commit that changed this chunk
After doing it for each chunk in your working tree, you can do git rebase -i and, if you have rebase.autosquash = true in your gitconfig, then git just does the fixup automatically for you.
This works fine even if several chunks amend the same commit. It will look weird before you rebase,
because of how git commit --fixup works, but it works just fine.
I’ve learned there are some projects doing this, like git-absorb. To be honest, I didn’t try them. These aliases are very simple and work fine for me. They can fail when a commit adds or deletes files, but really 99% of the time I use them, or more, they do the right thing. This works well enough that I haven’t really needed to try an alternative. But maybe at some point I will :)
Bonus points if you automate this git add -p; git cm-fixup loop and mail me about it. I’ve been too lazy to do it so far.
Enjoy!
 on [Unsplash](https://unsplash.com)](https://blog.sdfg.com.ar/posts/git-fixup-to-the-right-commit/cover.jpg)