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 cd
ing
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