A curiosity journal of math, physics, programming, astronomy, and more.

Two Git scripts to encourage good development habits

I've written about various practices I have when writing or reviewing code, from splitting branches using diff files and drafting pull requests locally to reviewing code locally and documenting exploratory testing. It's not always easy going those extra miles, so I try to make it as simple, reliable, and fun as possible by automating whatever I can. Here are two scripts I often use, both of which use git and fzf. One helps keep commits clean, the other I use for testing tests.

git-fixup

I make a lot of small, frequent commits, checking in every time the code works and has a meaningful change I wouldn't want to lose. Then, if new work gets the code in a tangle or breaks something I don't understand, I can clear the mess by resetting the working directory to latest commit. Tiny and always-working commits also helps git bisect.

Occasionally commits need amending. Some commit in a long list of commits broke a feature or introduced a bug that didn't get noticed. Rather than just add the fix to that long list, I prefer to update the commit that introduced the problem. To do that, I add a commit whose message starts with "fixup", then do an interactive rebase, which allows re-ordering commits and indicating that the fixup commit should be squashed into the commit before it. Then the original commit is cleaner.

While branches are often short-lived enough to rebase the whole branch, it's tricker to rebase when developing directly off the main trunk. One has to find the commit hash for the commit that needs to be amended then run git rebase -i <hash>^. To turn the process of finding a commit hash and rebasing into one step, I use this script:

git log --oneline -50 \
    | fzf \
    | cut -d' ' -f1 \
    | xargs -I {} git rebase -i {}^

That lists the last 50 commits with their short-form SHA and commit message, and pipes them into fzf where you can search for the commit message of the commit you want to rebase onto. Select a commit by moving up and down and/or searching and hitting Enter. Then git rebase -i <hash>^ will run with the commit you selected, which opens your default text editor where you can move the fixup commit to where it belongs in the list, mark it with an f for "fixup", then exit and apply the rebase.

git-regress

When reviewing code that includes tests, I run the tests on my local machine to surface any missing environment variables, potential OS incompatibilities, test flakiness, and sometimes even tests that aren't picked up by the test runner.

I also want to see that the tests can fail. Sometimes the tests run but the assertions are broken, such as the case of the .assertTrue that forgot the () after it, preventing the assertion from actually being invoked. The easiest way to see new tests fail is to undo the application changes the tests are checking.

To undo a branch's logical changes but not its tests, use git diff <some-branch>... -R -- <directories> | patch -p1. That creates a reverse diff of specific files, then applies the changes.

To simplify finding the code to undo then patching, I use this script that gives fzf all the changed files on a branch, which lets me select the files to revert then applies the reverse diff of those files:

mainBranch=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
toRevert=$(git diff $mainBranch... --name-only \
  | sort -u \
  | fzf --ansi \
      --disabled \
      --bind 'j:down,k:up,q:abort' \
      --preview="git diff $mainBranch... -- {}" \
      --preview-window=right:60%)

if [ -z "$toRevert" ]; then
  echo "No directories or files selected."
  exit 1
fi

git diff $mainBranch... -R -- $toRevert | patch -p1

You may want to add a step after git diff ... that takes the files changed on the branch and includes their parent directories, so you can revert the changes in a whole directory without having to select every file under it.

If you have suggestions for improvements to either of these scripts, or if you have your own scripts that help with your workflow, please email me.