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.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