Generating Thousands of PDFs on EC2 with Ruby

December 23 2009 by Sean Cribbs

The Problem

For about two months, we’ve been working on a static website that exposes the results of complicated economics model to non-economists. We decided to make the site static because of the overhead involved in computing the results and the proprietary nature of the model. We would simply pre-generate the output for all valid permutations of the inputs. The visitor could then choose her inputs from a questionnaire, click a button and immediately be shown the results.

The caveat of this decision is that in addition to the numerical outputs, three graphs and a summary (both in HTML and PDF) would need to be generated for each permutation. Since there were 3600 permutations, this would amount to 18000 files in total. Initial local runs of our generation process took about 30 seconds for each permutation, mostly due to embedding the graph images into the PDF. On a single machine, that would take 30 hours of uninterrupted processing! Clearly, this was a job for “the cloud”.

The Tools

Before we get into a discussion of the process of configuring and running the jobs, here’s overview of the tools we used to tackle the problem.

We initially considered using Amazon’s Elastic MapReduce to run the generation jobs, but it requires Java and Hadoop, we had already invested a lot of time in our Ruby tool chain. It is nigh impossible to automatically install Ruby and ImageMagick on an EMR node. Thus, we decided to use vanilla EC2 with the tools shown below.

Prawn

Prawn is the new kid in town for generating PDF in Ruby. Prawn is pretty well-written and easy to start using, and greatly improves on PDF::Writer.

Gruff

Gruff was not the most obvious choice for this project. We liked the flexibility and hackability of Scruffy, but translating its output to PDF was a nightmare and there were some strange inconsistencies in it. In the end, Gruff proved fast, reliable, and simple. The major caveat, as described above, is that embedding images in Prawn is orders of magnitude slower than simply drawing on the canvas.

Haml, Sass, Compass

Haml has been around for 3 years now. Many people cringe at the indentation-sensitive syntax, but it prevents so much frustration that it was a good fit for the project. Naturally, we also used its cousin Sass, and the new-ish CSS/Sass meta-framework Compass. The combination of the these three made it really quick to get started with the static site and make design changes as we iterated.

Chef

You may have already heard of the awesome configuration management tool, Chef. Chef allows you to ensure consistent configuration of your servers using a nice Ruby DSL and a huge library of community-developed “cookbooks” that covers many common use-cases. We were given the chance to try out an alpha of their “Chef Platform”, which is essentially a scalable, hosted, multi-tenant version of the server component of Chef and uses the pre-release version of Chef 0.8. With that, “knife”–the new CLI tool for interacting with the Chef server API–and the custom Opscode AMI, we were well-equipped to quickly deploy a bunch of EC2 nodes. We’ll talk more about the details of the Chef recipes below.

AMQP and RabbitMQ

What’s the best way to distribute a bunch of one-time jobs to a slew of independent machines? A message queue, of course! Despite the version packaged with Ubuntu 9.04 being pretty old, we chose RabbitMQ, having used it on another project. AMQP is also well supported in Ruby.

The Process

Preparing

The first step to start our processing job was to get the data up to S3. You could do this any number of ways, but we created a bucket solely for the data and uploaded all 3600 CSV files with a desktop client.

Next, we created the scripts for the workers and the job initiator. We would potentially need to run the process multiple times, so we chose Aman Gupta’s EventMachine-based AMQP client.

Here’s the worker script, which was set up as a daemon using runit:

#!/usr/bin/env ruby

$: << File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
require 'rubygems'
require 'eventmachine'
require 'mq'
require 'custom_libraries'

Signal.trap('INT') { AMQP.stop{ EM.stop } }
Signal.trap('TERM'){ AMQP.stop{ EM.stop } }

AMQP.start(:host => ARGV.shift) do
 MQ.prefetch(1)
 MQ.queue('jobs').bind(MQ.direct('jobs')).subscribe do |header, body|
   GenerationJob.new(body).generate
 end
end

Basically, it connects to the RabbitMQ host specified on the command line, subscribes to the job queue, and starts processing messages.

The job initiation script is almost as simple:

#!/usr/bin/env ruby

$: << File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
require 'rubygems'
require 'eventmachine'
require 'mq'

AWSID = (ENV['AMAZON_ACCESS_KEY_ID'] || 'XXXXXXXXXXXXXXXXXXXX')
AWSKEY = (ENV['AMAZON_SECRET_ACCESS_KEY'] || 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX')

Signal.trap('INT') { AMQP.stop{ EM.stop } }
Signal.trap('TERM'){ AMQP.stop{ EM.stop } }

host = ARGV.shift
input_bucket = "custom-data"
output_bucket = "custom-output"
output_prefix = Time.now.strftime("/%Y%m%d%H%M%S")
count = 0

AMQP.start(:host => host) do
 exchange = MQ.direct('jobs')

 STDIN.each_line do |file|
   count += 1
   $stdout.print "."; $stdout.flush
   payload = {
     :input => [input_bucket, file.strip],
     :output => [output_bucket, output_prefix],
     :s3id => AWSID,
     :s3key => AWSKEY
   }
   exchange.publish(Marshal.dump(payload))
 end
 AMQP.stop { EM.stop }
end
puts "#{count} data enqueued for generation."

It reads from STDIN the names of files to add to the queue, which are stored in the S3 bucket. Before running the job, we created a text file that listed each of the 3600 files, one per line, which could then be piped to this script on the command line. Then it passes along all the information each worker needs to find the data, and where to put it when completed. We scoped the output by the time the job was enqueued, making it easier to discern older runs from newer ones.

Configuring the cloud

Now that the meat of the job was ready, we dived into configuring the servers with Chef. We created a Chef repository, added the Opscode cookbooks as a submodule, and uploaded these default cookbooks to the server:

  • apt
  • build-essential
  • erlang
  • imagemagick
  • runit
  • ruby

We created some additional cookbooks to fill out the generic setup:

  • rabbitmq - Installs and configures RabbitMQ
  • gemcutter - Upgrades Rubygems, installs Gemcutter and makes gemcutter.org the default gem source

Lastly we created our custom cookbook, which sets up all the libraries we need, downloads the code, and sets up the worker process as a runit service. Let’s walk through the default recipe in that cookbook:


%w{haml gruff fastercsv activesupport prawn prawn-core prawn-format prawn-layout eventmachine amqp aws-s3}.each do |g|
 gem_package g
end

This simply installs all of gems that we need to run the job.


# Find the node that has the job queue
q = search(:node, "run_list:role*job_queue*")[0].first

Here we use Chef’s search feature to find the node that has RabbitMQ installed and running so we can pass it to the worker script.


# Create directory to put the code in
directory "/srv"

# Unzip the code if necessary
execute "Unpack code" do
 command "tar xzf generationjobs.tar.gz"
 cwd "/srv"
 action :nothing
end

# Download the code
remote_file "/srv/generationjobs.tar.gz" do
 source "generationjobs.tar.gz"
 notifies :run, resources(:execute => "Unpack code"), :immediate
end

# Create the directory where output goes
directory "/srv/generationjobs/tmp" do
 recursive true
end

In these four resources, we set up the working directory for the worker process, download the project code (stored on the Chef server as a tarball), and unpack it. The interesting thing about this sequence is that we don’t automatically unpack the tarball. Since the Chef client runs periodically in the background, we don’t want to be unpacking the code every time, but only when it has changed. We use an immediate notification from the remotefile resource to tell the unpacking to run when the tarball is a new version; remotefile won’t download the tarball unless the file checksum has changed.


# Create runit service for worker
runit_service "generationworker" do
 options({:worker_bin => "/srv/generationjobs/bin/worker", :queue_host => q})
 only_if { q }
end

The last step is a pseudo-resource defined in the “runit” cookbook that creates all the pieces of a runit daemon for you; we only had to create the configuration templates for the daemon and put them in our cookbook. The additional options passed to the runitservice tell the templates the location of the worker code and the RabbitMQ host. We also take advantage of the “onlyif” option so the service won’t be created if there’s no host with RabbitMQ on it yet.

The last step in the Chef configuration was to create two roles, one for the queue and one for the worker. Naturally, the node that has the queue can also act as a worker. Here’s what the role JSON documents look like:


// The queue role
{
 "name": "job_queue",
 "chef_type": "role",
 "json_class": "Chef::Role",
 "default_attributes": {

 },
 "description": "Provides a message queue for sending jobs out to the workers.",
 "recipes": [
   "erlang",
   "rabbitmq"
 ],
 "override_attributes": {

 }
}

// The worker role
{
 "name": "job_worker",
 "chef_type": "role",
 "json_class": "Chef::Role",
 "default_attributes": {

 },
 "description": "Processes the data from a queue into the PDF, PNG and HTML output.",
 "recipes": [
   "apt",
   "build-essential",
   "ruby",
   "gemcutter",
   "imagemagick::rmagick",
   "runit",
   "custom"
 ],
 "override_attributes": {

 }
}

Running the jobs on EC2

Now comes the fun (and easy) part! Armed with an AWS account, an EC2 certificate, and knife, we began firing up nodes to run the job. With Opscode’s preconfigured Chef AMI, you can pass a JSON node configuration in the EC2 initial data. First we generated the configuration for the job queue node:

$ knife instance_data --run-list="role[job_queue] role[job_worker]" | pbcopy

With the JSON configuration in the clipboard, we could paste it into ElasticFox (or the AWS Management console) and fire up the first EC2 node. Several minutes later, the node was ready to go. Now, we created a similar configuration, but with only the worker role:

$ knife instance_data --run-list="role[job_worker]" | pbcopy

Then we fired up nine of the nodes with that configuration and proceeded to initiate the job:

$ ssh -i ~/ec2-keys/my-ec2-cert.pem root@ec2-public-hostname
[root@ec2-public-hostname]$ cd /srv/generationworker
[root@ec2-public-hostname]$ bin/startjobs localhost > manifest.txt

After all the preparation, that’s all there was to it! A little over an hour later, we had generated PNG graphs, PDF, and HTML from all 3600 datasets.

Conclusion

It’s no mystery why “cloud computing” is so popular. The ability to quickly and cheaply access computational power, utilize it, and then dispose of it is really appealing, and tools like Chef and EC2 make it really easy to accomplish. What can you cook up?

Rails Dog is Officially Launched

October 5 2009 by railsdog

We’re proud to announce the launch of our new company, Rails Dog LLC. RailsDog has been operating for a while now as a programming blog and as the sponsor of the open source e-commerce project known as Spree. Demand for our professional e-commerce consulting services has now grown to the point where we are able to organize as a company and lease physical office space here in Washington, DC.

Several experts from the Spree community have now joined our company full time and we have many more available to us in a subcontractor capacity. We have also updated our website which we’ll be adding to in the coming weeks. During this time period we will also be introducing you to some of our new team members.

Rails Dog will continue to sponsor the Spree e-commerce project as well as offer paid consulting services related to web applications development. The Spree project will remain freely available under the terms of the BSD License. October is going to be a busy month for us. Our list of client projects is going to enable us to spend even more time focused on Spree and we hope to add several exciting new features this month.

Resource Controller for “Skinny” Rails Controllers

July 24 2009 by railsdog

These days it seems to be conventional wisdom to build “skinny” controllers in your Rails app. Skinny controllers help you to consolidate your business logic into one place (the model) which allows for easier maintenance and testing. If you’re still not convinced, perhaps you should watch this informative public service announcement.

I’ve been using the excellent resource_controller plugin by James Golick for well over a year now. Its an excellent tool for handling the standard RESTful controller actions that one normally codes by hand (or generator.) We currently use it in the Spree project and yesterday I was curious to compare the size of these controllers to those of a project that does not use resource_controller. First lets look at the results of running rake stats on the Spree project.

spree-stats

As you can see, there are 44 controllers in this project. Yeah, that’s a lot of controllers but Spree is a pretty huge project (its a full featured e-commerce platform.) Now notice that out of 44 classes we only have 111 methods. That’s an average (rounding down) of only 2 methods per class! The methods themselves are also pretty skinny with an average of 8 lines of code per method.

Now lets take a look at Fat Free CRM. I chose this project because it is similar to Spree in that its a full featured platform (not just a little plugin.) DISCLAIMER: I’ve never used this application but I’ve heard good things about it. I’m in no way trying to disparage another person’s work here - I just need a point of comparison.

fat-free-stats

You can see that this project’s controllers are pretty fat. There are only 12 of them but they have more lines of code then in the 44 controllers of Spree. There are 11 methods per controller (compared to 2 in Spree), with the typical method size being the same as Spree. Don’t let the equivalent method size fool you. For instance, take a look at this typical 4 line method in a Spree controller.


create do
  flash nil 
  wants.html {redirect_to edit_order_url(@order)}
end

This is an example of how we override the default resource_controller behavior. In this specific case we did not want to include anything in the flash, nor redirect to show after object creation. If you’re happy with the resource_controller defaults, this method is not even needed.

If you’re interested in a thorough overview of resource_controller, Fabio Akita has produced a very detailed screencast on how it works. Its one of the longest screencasts I’ve ever seen (about 1 hr) but its well worth the time if you’re struggling to wrap your head around how REST works in Rails (both with and without resource_controller.)

Using Rack to Combine Sinatra and Word Press

June 2 2009 by railsdog

There’s been a lot of buzz recently around the lightweight Ruby framework known as Sinatra. It seems ideally suited for simple web applications that require a little sprinkle of Ruby and using the entire Ruby on Rails stack might be overkill. You’ve probably also heard of Rack (unless you’ve been without Internet access for the past six months.) If you’re new to Rack and would like to learn more, you can check out this excellent presentation from RailsConf. I recently had the need to build a website that featured a blog along with some minimal dynamic content so I thought I would try hooking up Sinatra together with the Word Press blog engine.

WordPress(WP) is an excellent blogging platform. Its the consensus tool of choice for professional bloggers (those who blog for a living as opposed to people like me who occassionally blog about technology.) Since I was really happy with WP and I believe in using the best technology for the job, I wanted to see if I could get it working alongside Sinatra which I planned to use for the rest of the site. Why not just do the whole thing in PHP? Well the answer is that I hate programming in PHP. I didn’t mind using an excellent tool like WP but I didn’t want to get carried away with the PHP when it came to my own coding.

First, you need to get WP up and running with Apache HTTP Server. You can use any server really but I would recommend Apache since its widely used and well documented when it comes to PHP applications. There is no shortage of WP setup documentation so I’m not going to cover it here. Just be sure to setup WP in its own directory (/blog) instead of the top level root directory. Once you can reach your WP blog via http://example.com/blog then you’re ready to proceed.

You’re also going to need to install Phusion Passenger. I’m not going to cover installation of Passenger either since most Rails developers are already familiar with it. If you’re not, I suggest you read some of the excellent documentation on the subject. What you may not know, however, is that Passenger also supports arbitrary Ruby web appications that follow the Rack interface (like say … Sinatra.) So lets get started with a simple Rack app and then move onto Sinatra.

Install the Rack Gem

Lets make sure the rack gem is installed on our server.


gem install rack

Create the Directories Required by Rack

Suppose we are creating an application called example which is located in the /webapps directory


mkdir /webapps/example/public
mkdir /webapps/example/tmp

Create a Basic Rackup File

We also need to create a /webapps/example/config.ru for starting the Rack application


app = proc do |env|
    return [200, { "Content-Type" => "text/html" }, "hello world"]
end
run app

Change the Document Root

In the Apache config you will want to configure the DocumentRoot to point to the public directory in your Rack app (/webapps/example/public). Leave your WP blog directory where it is for now. Restart Apache.

If you navigate to your application in a web browser you should see the familiar “hello world” greeting. This means Phusion and Rack are now working together without a problem. Lets add Sinatra to the mix.

Setup Sinatra

Install the Sinatra gem if you haven’t done so already.


gem install sinatra

Replace your config.ru with one that will setup Sinatra


require 'rubygems'
require 'sinatra'

Sinatra::Application.default_options.merge!(
  :run => false,
  :env => :production,
  :views => '/webapps/example'
)

require 'app.rb'
run Sinatra.application

Now create an app.rb which will be the basis for your Sinatra app


get '/' do
  'Hello Sinatra!'
end

Restart Apache and navigate to your local application using your web browser you should now see the Sinatra greeting that we just added.

Add the WP Blog

Move your entire blog dir into your Sinatra public directory. So you will now have /webapps/example/public/blog along with the original .htacess file used by WP. Your blog won’t work yet, there’s an important tweak to be made to your httpd.conf


<Location /blog>
  PassengerEnabled off
</Location>

This tells Passenger not to bother with files in the blog directory. This is key to getting the PHP-based WP blog to run inside of a directory where everything else is Sinatra. If you test your application inside of a web browser the blog should be working!

Build Out Your Sinatra Application

Go ahead and build out the rest of your Sinatra application now. Most likely you’re going to want to share a common layout, header and footer between your Sinatra application and your WP blog. You can add “partial” support in Sinatra using a helper (Sinatra does not explicitly support partials.)

Modify your app.rb as follows:


get '/' do
  @section = 'overview'
  erb :overview, :layout => :default
end

helpers do
  # Usage: partial :foo
  def partial(page, options={})
    erb page, options.merge!(:layout => false)
  end
end

Then you can create a default.erb to serve as the default layout.


<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
<head profile="http://gmpg.org/xfn/11">
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link rel="Shortcut Icon" type="image/ico" href="/blog/wp-content/themes/spree/favicon.ico" />
  <title>Spree</title>
  <link rel="stylesheet" href="/blog/wp-content/themes/spree/style.css" type="text/css" media="screen" />
</head>
<body>
  <div id="page">
    <!-- BEGIN: Header -->
    <%= partial :header %>
    <!-- END: Header -->
    <div id="main">
      <div class="content">  
        <p>
          <%= yield %>
        </p>
      </div>
    </div>
    <!-- BEGIN: Footer -->
    <%= partial :footer %>
    <!-- END: Footer -->
  </div>                 
</body>
</html>

Notice how I am using the favicon and stylesheets right from the WP blog theme. From this point on we’re just writing a nice Sinatra application and sharing styles and images with our WP blog.

Final Thoughts

This is an excellent way to build a simple Ruby based website that requires a blog. I started off by having my artist just work up a design for the WP blog since she could do that on her own without any Ruby. I then copied the header and footer into my Sinatra app and created some stub ERB files for her to edit. Sinatra worked out well for my very simple needs. I basically needed a simple way to share layouts and to highlight the current navigation tab in my header.

My artist was able to edit these ERB files in place because they were basically straight HTML. All of the dynamic stuff was contained in the layout itself. The end result is a seamless integration of Ruby + PHP where each technology is being used for what it does best.

A Public “Thank You”

May 6 2009 by railsdog

@GreggPollack put together an excellent RubyHeroes presentation before the keynote.  In addition to recognizing some of the great contributions these individuals made, Gregg suggested that we all take a moment to thank at least three people whose work has made our lives that much easier.  So instead of getting caught up in minor complaints about the accomodations or speeches, I decided to throw out some more positive energy.

  1. GitHub - I’m not going to waste a lot of space talking about how much of an impact GitHub has made on the Rails community.   Its not like these guys are toiling away in anonymity but their contribution has been so important that I’m just going to go on record as saying “Thanks.”

  2. Phusion - Again, everyone knows what these guys have done for simplifying Rails deployment.  Just because they get plenty of “Thank Yous” doesn’t mean I can’t throw my own two cents in here.  I also had the opportunity to thank one of the Phusion guys in person at dinner this evening.

  3. ResourceController - James Golick’s plugin finally convinced me to embrace REST.  If you’re not familiar with it, its a great way to simplify the implementation of your standard RESTful controller.  My controllers became way more DRY as a result.  Again, saw James at dinner and shook his hand in person but it doesn’t hurt to say it here.

Honorary Mentions:

  • DHH and RailsCore for Rails (DUH)
  • Chad Fowler and company for organizing RailsConf
  • Gregg Pollack for the Ruby Heroes presentation, conference videos and the entertaining RailsEnvy podcasts (along with Jason Seifer.)

RailsConf Kicks Off

May 6 2009 by railsdog

Well today was the first day of talks here at RailsConf.  DHH kicked things off with an opening speech.  He spent some time talking about Rails 3.0 features but unfortunately it was hard to follow the slides in the back.  Somebody on #railsconf thought it would be cool if you could broadcast slides to everyoene’s laptop during the live talk.  That would have come in handy for this.

The talk was good despite the sound and slide difficulties.  I always enjoy hearing how other programmers approach the challenges of their job.  DHH was talking about how “renogiation of requirements” can ultimately lead to a huge productivity gain.  He described a time at 37 signals where he spent two weeks trying to solve an impossible task.  He ultimately came up with a much simpler solution and proposed it to the client.  Client was indifferent (”Sure, whatever”) and he was done in 48 hours.

The talks this morning have been pretty good so far.  Mercifully there is no smoking in the conference center so that is a welcome change of pace from the rest of the hotel.  My main complaint so far would be that the chairs have metal backs with no cushion so they’re extremely uncomfortable.

One last thing I would like to mention is the state of the Rails community.  There are lots of first time RailsConf attendees here this year which is a great sign.  People also seem to be mixing a lot better instead of hanging out exclusively with the people that they came here with.  The Rails community really seems to be moving in a more positive direction, despite minor flare ups and distractions along the way.

I really think the whole Rails - Merb merger sets a good example for how much everyone stands to gain when setting aside ego and just work together to write the best possible software.  Constructive criticism is fine but collaboration is even more productive.

Vegas: First Impressions

May 5 2009 by railsdog

I’ve been in dozens of airports around the world but stepping off the plane at LAS conveys the immediate impression that you have arrived somewhere different.  The first thing you see as you come out of the jetway are tons of slot machines.  I have nothing nice to say about the airport ground transportation options.  I used the 30 minute ride to my hotel (which was like 5 miles from the airport) to come up with about half a dozen ways to design a better system.  You can start by knocking down the worthless fucking monorail that they have and replace it with a proper light rail system that would actually connect people to the one place everyone wants to go - the airport!

Went out last night with @hackerchic, @tunagami and Sonny (twitter account not shown.)  Took the afore mentioned monorail to the MGM where we had a good time at the Centrifuge bar.  Every 20 minutes or so some dance song would come blasting on and the bar tenders would all jump up on the bar and start dancing.  So that seemed to be a pretty authentic Vegas experience.

Cigarette smoking is completely out of hand here.  I’m really not used to people smoking indoors and it seems to be everywhere.  My eyes and throat are really irritated from all of the second hand smoke.  On the plus side, you can drink beer anywhere you want (including in the middle of the street.)  Its kind of strange to be drinking a beer while in line to check in to your hotel (next to the guy smoking a cigarette.)  Even stranger is the notion of just walking outside with a giant 32 oz. beer in your hand.

Five Reasons To Go to RailsConf

April 15 2009 by railsdog

RailsConf is less than three weeks away now but there is still time to sign up if you’re interested in coming. With the recent reduction in the hotel price ($99) it might be worth considering. Here are a few more reasons to go this year in no particular order:

  1. Excellent Speakers — Last year was my first RailsConf and I was blown away by the quality of the speakers. Pretty much every single session had both an interesting topic and an engaging speaker. Some sessions are better than others, but overall I think the speaker quality was quite high. The slides are always available online and usually very high-quality.
  2. Convenient Location — This year the conference will be held in Las Vegas, Nevada for the first time. Las Vegas is easy to get to from almost anywhere in the United States. There really isn’t a better location in terms of the sheer number of direct flights other than perhaps Orlando Florida. The conference will also be held in the actual hotel where attendees will be staying. With an action-packed schedule this really adds to the convenience since you won’t be traveling back and forth from the hotel to the conference center like last year. It’s all right there under one roof.
  3. Hiring Opportunities — At last year’s RailsConf there was a shocking number of companies who were interested in hiring qualified Rails programmers. Every time I sat down there was some brochure or business card laying there mentioning that such and such a company was looking to hire. So if you’re looking to switch jobs or to find out more about the kinds of jobs that are out there this is a great opportunity.
  4. Creativity Boost — RailsConf is also a great opportunity to come up with new ideas and inspiration for your next 12 months of programming. With all the great talks and ideas floating around your head will soon be buzzing with exciting new ideas to take back to your existing job. It’s a great opportunity to take a break from the daily coding march and to gain some new perspective.
  5. A Chance to Network — The conference is also a chance to meet all those people you have been chatting with online over these past several months. All of your favorite bloggers will be there plus those people whom you’ve been working with on your favorite open source projects. It’s fun to put a name with a face and have more in-depth discussions that are not really possible online.

So if you haven’t already signed up for RailsConf go and do it now. It’s definitely worth the time and money.  If you decide to go, make sure to say “hi” if you see me there.

Custom Bash Prompt for Git Branches

March 7 2009 by railsdog

example of git bash prompt

git bash prompt

If you are an active git user then you may find yourself constantly switching between different branches in the same project as you are working. If you’re not doing this, you’re probably not experiencing the full power of git. I recently added some functionality to my bash prompt that shows the current git branch in green and a yellow asterisk if there are uncommitted changes in the project.

Here’s how to do it. First you will need a copy of .gitcompletion.sh in your home directory. This script has some functions that we will use to customize the bash prompt. It also has some nice side effects like tab completion for branch names, etc. Now simply add the following to your .bashprofile


source ~/.git_completion.sh

function parse_git_dirty {
  [[ $(git status 2> /dev/null | tail -n1) != "nothing to commit (working directory clean)" ]] && echo "*"
}

function parse_git_branch {
  git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/[\1$(parse_git_dirty)]/"
}

export PS1='\h:\W$(__git_ps1 "[\[\e[0;32m\]%s\[\e[0m\]\[\e[0;33m\]$(parse_git_dirty)\[\e[0m\]]")$ '

GitHub Search Tip

February 2 2009 by railsdog

GitHub recently made significant changes to their search functionality. I often use their search to look for related projects, such as new Spree extensions. Prior to these changes I would simply search on spree. Now when you do this the default results are IMO less then desirable.

So if you’re like me and you just want to find projects containing your search term then I have a tip for you. Just add fork:false to your search and it will exclude all of the forks from your results. This makes it much eeasier for projects like Spree where there are 60+ forks that you would have to sift through in order to find what you’re really looking for.