Let git commit --amend
reach into the mists of time
Copy into your $PATH.
One git workflow I use involves frequently rebasing. After a messy initial phase I have my PR shaped into roughly the commits that tell the story of the change I'm making, and hopefully made it as reviewable as possible, for example by cabining less interesting commits that mostly consist of vendored or autogenerated files into their own commits that can be ignor^H^H^H^H^Hcarefully given specific attention.
$ git log --oneline
5e6ccba Paperwork
06fb676 Deal with butterfly-effect test failures
51d0d53 Add metrics and deploy safety valves
6d08e46 Use the new useful thing in various places
f6c893f Introduce something useful
e16fcb4 Scaffolding and vendored files
Often this phase ends up lasting longer than expected, and I need to
make many changes to basically all of these commits. Since I care about
my carefully crafted commit log, of course that means I'm going to use
git-rebase
to apply those changes to the original commits so I don't
end up asking reviewers and archaeologists to wade through something
like
$ git log --oneline
3032029 total refactor
ed8d317 autocorrect everything
dda23ca wip
5c20c2f I love linters
9bdd483 Does it work now?
c75cde8 Once more
1a37271 Try to fix a test
e16fcb4 Nicely crafted commit
Commits like those are often a necessary part of the development process, but IMHO they don't belong in the final PR. So I rebase them away once they've settled.
Unfortunately if you're not careful doing this a lot can distort the
original commits and cause many git conflicts, especially if you're
changing files that have a lot of upstream activity that you're also
integrating with rebase. You make things a lot easier on yourself if you
use the --fixup
option when committing and then --autosquash
when
rebasing to apply each fixup on top of the most recent change to that
file.
$ git commit --fixup 06fb676 test/ -m 'few more test fixes'
$ git commit --fixup e16fcb4 vendor/ -m 'upgrade dep to latest version'
$ git commit --fixup f6c893f src/ -m 'comment typo in main change'
$ git commit --fixup 06fb676 test/ -m 'o no more tests'
$ git rebase origin/main -i --autosquash
1 pick e16fcb4 Scaffolding and vendored files
2 fixup 0000389 fixup! Scaffolding and vendored files # upgrade dep to latest version
3 pick f6c893f Introduce something useful
4 fixup 0000389 fixup! Scaffolding and vendored files # comment typo in main change
5 pick 6d08e46 Use the new useful thing in various places
6 pick 51d0d53 Add metrics and deploy safety valves
7 pick 06fb676 Deal with butterfly-effect test failures
8 fixup 05c38c8 fixup! Deal with butterfly-effect test failures # few more test fixes
9 fixup 05c38c8 fixup! Deal with butterfly-effect test failures # o no more tests
10 pick 5e6ccba Paperwork
11
12 # (10 commands)
.git/rebase-merge/git-rebase-todo [unix GITREBASE] [0001,0001,1][10%]
:wq
Successfully rebased and updated refs/heads/topic.
After that rebase, all the fixups are still there but my original commit structure is still intact.
This is a fun workflow! It feels like a generalization of repeatedly
fixing things with git commit --amend --no-edit .
, which I'm also a
big fan of.
So that works great, but it's kind of annoying to keep tracking down
what the most recent commit to have modified a file so you can pass it
to --fixup
. This script automates that process: for each modified file
in your working tree it creates a single commit marked as a fixup of the
last commit which modified that file. Doing this lets you flush all the pending
changes in your tree as amendments to the relevant commits in just two commands
with a guarantee of no conflicts (unless they came from upstream):
$ fixdown
# [info about commits being made]
$ git rebase --autosquash origin/main
# all done 🎉