Kaushik's Blog

Small units of work that work

Creating small git commits is a good development practice. Preferably, with a clear commit message as well. It simplifies code review and can help tell a clean story of the feature.

There is a flip side though - sometimes a small commit can simply not work. It may introduce a mistake that causes the code to not build or not pass tests. In your particular project, this may not matter so long as the entire branch ultimately works and passes tests.

Yet, you may want to have commits that work.

Perhaps out of a sense of aesthetic beauty, where each commit is a perfectly fashioned addition to a code base. Each commit could be cherry-picked by someone who needs that particular change.

I haven't had a reason to use git bisect in my experience, but should I one day need to, having a clean, buildable history of atomic commits would prove useful to identify the source of a regression1.

How to do it: Recipes

These are a few common recipes I use for Docker-based applications written in Python. I use pytest. All of the test configuration lives in a pyproject.toml file.

1.

While working on a branch and rewriting it to simplify commits, I might want to run my unit tests on each commit so that I can verify that each commit is, indeed, atomic and doesn't break things.

git rebase has the --exec argument that helps achieve this. You can run a specified command after every single commit has been applied.

Here's a one-liner I use often, tweaked from this StackOverflow suggestion2:

git rebase -i --exec "docker run -it --rm  -v path/to/my/code/folder:/opt/code -v ./pyproject.toml:/opt/pyproject.toml registry.gitlab.com/registry-name/image-name:TAG pytest" origin/main

If, at any point, the test fails, the rebase stops at that point. This would be a good opportunity to:

2.

This has an issue when a commit actually changes the Dockerfile itself. You'd want to run tests on that commit with a Docker image built from the Dockerfile as of that commit.

To do so, you can build the Dockerfile with

docker build -q -f docker/server/Dockerfile .

-q suppresses all build outputs and just returns the image hash.

Plug that into the rebase recipe above, and you get:

git rebase -i --exec "docker run -it --rm  -v path/to/my/code/folder:/opt/code -v ./pyproject.toml:/opt/pyproject.toml $(docker build -q -f path/to/Dockerfile .) pytest" origin/main

  1. https://andrealmeid.com/post/2022-07-31-keep-bisect/

  2. As an aside, I like to git fetch followed by a rebase onto origin/main. I find that rebasing onto a local copy of main can be fraught. Our human tendency is to not update our local copy of main often enough to keep up with upstream changes.