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:
- make the necessary changes
- test it again to make sure the changes work
- amend the commit (
git commit --amend --no-edit
if you're happy with your commit message) - continue on your merry rebasing way (
git rebase --continue
)
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
As an aside, I like to
git fetch
followed by a rebase ontoorigin/main
. I find that rebasing onto a local copy ofmain
can be fraught. Our human tendency is to not update our local copy ofmain
often enough to keep up with upstream changes.↩