Easier Atomic Commits
How do you keep your commits atomic easily? Let's explore one possible approach.
Problem
As a practitioner of good source control, you and your team have decided to make all of your git commits atomic within your projects. That is, every commit has a green test suite, and you prefer small, incremental commits to large, monolithic ones. Keeping commits small and atomic has tons of benefits, from more consistent continuous integration results, to better team cohesion (have you ever gotten upset with another team member for committing red?). But in practice, keeping all of your commits atomic can present some challenges.
After doing a bunch of work, making incremental, atomic commits along
the way, it's time to push your work up. However, when you run git pull
--rebase
, you find that another team member has made changes since you
last pushed. Your commits are now sitting on top of a different git
history. Are all of your commits still atomic? Short of checking out
every single commit and running the suite, how can you be certain that
every commit is atomic? What a pain! I don't want to check out every
commit by hand.
Solution
Enter atomically
, a simple shell script designed to take the pain out
of checking every commit between your upstream and you. Before pushing,
you can ensure every commit is atomic by running the script.
To use, just pass atomically the command as arguments:
$ atomically rake
The above command will start at the current branch's HEAD and run rake. After that, it will check out the previous commit and run the command again. It will do so for all commits between you and origin.
If you are confident that nothing in your spec suite changed, you can run only your cucumber features the same way:
$ atomically cucumber
Or just your spec suite:
$ atomically rspec
Regardless, keeping atomic commits is a vital part of good source control, and this tool makes it slightly easier to do so.
Here's the source of atomically
:
#!/bin/bash
if [ -n "$(git status --porcelain)" ]; then
echo "ERROR: You have a dirty working copy. This command would remove any files not already checked in"
exit 1
fi
b="$(git symbolic-ref HEAD 2>/dev/null)"
branch="`basename $b`"
program=$*
reset_branch() {
git co $branch --quiet
}
git rev-list origin/${branch-master}..${branch-master} | while read rev; do
trap "exit 1" SIGINT SIGTERM
echo
echo "Running at revision $rev"
echo
git co $rev --quiet && git clean -fd && $program
echo
trap - SIGINT SIGTERM
done
reset_branch
To use, just drop that in a file in your $PATH
, and make sure it is executable.
Thanks to Gary Bernhardt for the scripts' inspiration,
run-command-on-git-revisions
, which you can see in his
dotfiles.