Hashrocket.com / blog

Bg default article large

Easier Atomic Commits

posted on and written by Joshua Davey in

Image 100x100 josh davey

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.

Posted in Development and tagged with Git