Bundle While You Git
07 Feb 2011
Update: hookup does all this for you, plus migrates your database!
I recently discovered RVM’s ability to create a project .rvmrc when running rvm use --rvmrc. Commented out by default is a section for automatically running Bundler when cding to your project:
# Bundle while reducing excess noise.
printf "Bundling your gems. This may take a few minutes on a fresh clone.\n"
bundle | grep -v '^Using ' | grep -v ' is complete' | sed '/^$/d'
I excitedly enabled this, as the less I have to run the bundle command manually, the better. But it quickly became apparent that this is the wrong time to bundle. Nothing changes when I cd into a project, so why would the bundle be out of date? Except after the initial clone, this trick did nothing but eat valuable seconds (sometimes minutes). What I really needed was a way to bundle right after a git checkout or git pull --rebase (or throughout a git bisect, if we want to get really ambitious).
As luck would have it, Git provides a post-checkout hook that runs in all those cases. Let’s take a stab at it, shall we? Create .git/hooks/post-checkout with the following content and chmod +x it:
#!/bin/sh
if [ -f Gemfile ] && command -v bundle >/dev/null; then
# $GIT_DIR can cause chaos if Bundler in turn invokes Git.
# Unset it in a subshell so it remains set later in the hook.
(unset GIT_DIR; exec bundle)
# Even if bundling fails, exit from `git checkout` with a zero status.
true
fi
Awesome? You bet, but it runs on every HEAD switch, which means we still lose valuable seconds even when the Gemfile isn’t updated. Luckily, the post-checkout hook receives the old and new HEAD as arguments, so it’s easy to check for changes to the Gemfile, Gemfile.lock, or the project’s gem spec:
#!/bin/sh
if [ $1 = 0000000000000000000000000000000000000000 ]; then
# Special case for initial clone: compare to empty directory.
old=4b825dc642cb6eb9a060e54bf8d69288fbee4904
else
old=$1
fi
if [ -f Gemfile ] && command -v bundle >/dev/null &&
git diff --name-only $old $2 | egrep -q '^Gemfile|\.gemspec$'
then
(unset GIT_DIR; exec bundle)
true
fi
What else? Well, we could silence some of the noise like RVM does:
#!/bin/sh
if [ $1 = 0000000000000000000000000000000000000000 ]; then
old=4b825dc642cb6eb9a060e54bf8d69288fbee4904
else
old=$1
fi
if [ -f Gemfile ] && command -v bundle >/dev/null &&
git diff --name-only $old $2 | egrep -q '^Gemfile|\.gemspec$'
then
(unset GIT_DIR; exec bundle) | grep -v '^Using ' | grep -v ' is complete'
true
fi
Left as an exercise to the reader: installing Bundler if it’s not already installed (personally, I still do that from the .rvmrc), and configuring Git to install that hook into new and freshly cloned repositories.
Last but not least, here’s a complimentary, complementary pre-commit hook:
#!/bin/sh
git diff --exit-code --cached -- Gemfile Gemfile.lock >/dev/null || bundle check