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
fiAwesome? 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
fiWhat 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
fiLeft 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