Automatic change RVM environment when switching Git branch

I use RVM all the time when I develop Ruby (and Rails) applications. It’s great for isolating Ruby environments and gem packages for a specific project. I also use git extensively – especially branching when developing new features for an app.

Sometimes a branch works with new gems, that I do not want to pollute my main project-specific gemset with. So I create a new RVM gemset for this particularly branch. This has had its own problems because a “git checkout” now also needed to be followed by a “rvm use” statement. Also having a .ruby-gemset file in the project root led to RVM resetting the current gemset as soon as I changed directory.

One solution could be to check-in the .ruby-version/.ruby-gemset files in each branch, but I don’t want to annoy fellow developers with my RVM files. So I came up with the solution below instead.

First for this example, I assume that you have a git repository containing a master branch and another branch. If not, here’s a quickstart to make this happen:

$ mkdir myproject && cd myproject

$ git init .

$ touch 1.txt

$ git add -A

$ git commit -m 'First master commit'
[master (root-commit) b578056] First master commit
 2 files changed, 2 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 1.txt

$ git checkout -b somebranch
Switched to a new branch 'somebranch'

$ touch 2.txt

$ git add -A

$ git commit -m 'First somebranch commit'
[somebranch d10e2c1] First somebranch commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 2.txt

$ git checkout master
Switched to branch 'master'

$ git branch
* master
  somebranch

Okay with that squared away, let’s get on with the actual steps. You need to create a RVM gemset for each branch (master and somebranch) with the appropiate .ruby-version/.ruby-gemset files. The principle is, that you rename them, so they have a postfix named after the branch.

First create for the master branch:

$ rvm --create --ruby-version 2.0.0@myproject-master
ruby-2.0.0-p643 - #gemset created /home/carsten/.rvm/gems/ruby-2.0.0-p643@myproject-master
ruby-2.0.0-p643 - #generating myproject-master wrappers..........

$ ls -a
.  ..  .git  .ruby-gemset  .ruby-version

Then rename the files:

$ mv .ruby-gemset .ruby-gemset-master
$ mv .ruby-version .ruby-version-master

$ ls -a
.  ..  .git  .ruby-gemset-master  .ruby-version-master

Now do the same for the branch “somebranch”:

$ rvm --create --ruby-version 2.0.0@myproject-somebranch
ruby-2.0.0-p643 - #gemset created /home/carsten/.rvm/gems/ruby-2.0.0-p643@myproject-somebranch
ruby-2.0.0-p643 - #generating myproject-somebranch wrappers..........

$ ls -a
.  ..  .git  .ruby-gemset  .ruby-gemset-master  .ruby-version  .ruby-version-master

$ mv .ruby-version .ruby-version-somebranch
$ mv .ruby-gemset .ruby-gemset-somebranch

Since the files are renamed as shown, they will not by themselves trigger an RVM gemset switch, if you enter the directory.

If you look at your git repo now, you will see the files as untracked.

$ git status
On branch master
Untracked files:
  (use "git add ..." to include in what will be committed)

	.ruby-gemset-master
	.ruby-gemset-somebranch
	.ruby-version-master
	.ruby-version-somebranch

nothing added to commit but untracked files present (use "git add" to track)

As stated in the beginning, we don’t want these files in the repository, so create a .gitignore file and add this:

.gitignore


.ruby-version*
.ruby-gemset*

Now comes the fun part. We are going to make a hook in our git config. Create this file

.git/hooks/post-checkout

#!/bin/sh

# Find current branch name
BRANCH=`git rev-parse --abbrev-ref HEAD`

# Copy branch-specific RVM files, if available - default to master-branch
if [ -f .ruby-gemset-$BRANCH ];
then
  cp -f .ruby-gemset-$BRANCH .ruby-gemset
else
  cp -f .ruby-gemset-master .ruby-gemset
fi

if [ -f .ruby-version-$BRANCH ];
then
  cp -f .ruby-version-$BRANCH .ruby-version
else
  cp -f .ruby-version-master .ruby-version
fi

And make sure, that the file is executable, since it is just a script:

chmod +x .git/hooks/post-checkout

After you have done this, everytime you checkout a branch, this script will be called. If you checkout somebranch, it will look for a .ruby-version-somebranch and .ruby-gemset-somebranch and copy them to .ruby-version and .ruby-gemset respectively. If you create a new branch but do not make a specific gemset for this branch, the script will instead copy .ruby-version-master/.ruby-gemset-master. So always as a minimum create these.

You might think that now you a all done. However if you try to checkout a “somebranch” now, things will seem strange. The .ruby-gemset file will be an exact copy of .ruby-gemset-somebranch, but if you call “rvm current”, you will still be on the master gemset. Why is this so?

The thing is: .ruby-gemset is now placed correctly, but will not be read by RVM until you change into the actual directory. Try this:

$ git checkout somebranch 
Switched to branch 'somebranch'

$ rvm current
ruby-2.0.0-p643@myproject-master      <== Not what we expected

$ cd .

$ rvm current
ruby-2.0.0-p643@myproject-somebranch  <== Much better

But this is still an extra manual step, that complicates things. You WILL forget this at some point, so let's get rid of it.

You need to edit your $HOME/.bashrc file and add this line:

$HOME/.bashrc

git () { /usr/bin/git "$@"; cd .; }

This changes the git command to a function, which calls the executable git in /usr/bin with all arguments, and afterwards does the otherwise harmless "cd ." which will then make RVM reload the .ruby-version/.ruby-gemset files.

Exit the shell and open a new to reload .bashrc, and you're good to go.

Please let me know, if you have any trouble with the above. Everything has been tested, but evil typos may have creeped into the text.

Have fun!

3 thoughts on Automatic change RVM environment when switching Git branch

  1. Cool tip!

    For what it’s worth, I’d recommend checking in .ruby-version to the repository. This is not just an RVM file, but the de facto way to specify your apps required Ruby version supported by all Ruby switchers.

  2. Yeah true. However when making .ruby-version files with RVM, the version number always also contains the actual build-number, eg. 2.0.0-p481 instead of just 2.0.0. When checking into your repo, the latter form should be used.

  3. I set up rvm aliases for rubies and put the alias names in my .ruby-version files. So the .ruby-version might be ‘use-ruby-two’ and on my local machine the alias use-ruby-two might be ruby-2.2.1, but on a server I’m deploying to, the use-ruby-two alias might be ruby-2.1.5.

    Also, minimal naming will work, if the .ruby-version is set to 2.0.0, rvm will find 2.0.0-p481.

    And for fun, and because I’d rather use ruby everywhere, (and using symlinks instead of copying):

    #!/usr/bin/env ruby
    require ‘fileutils’

    branch = `git rev-parse –abbrev-ref HEAD`.chomp

    # Link branch-specific RVM files, if available
    if File.exist?(“ruby-gemset-#{branch}”)
    FileUtils.ln_sf “ruby-gemset-#{branch}”, “.ruby-gemset”
    else
    FileUtils.ln_sf “ruby-gemset-master”, “.ruby-gemset”
    end

Skriv et svar

Din e-mailadresse vil ikke blive offentliggjort. Krævede felter er markeret med *