Managing Plugins with Piston

(Mon Jan 22, 2007) [/Rails#

For all the same reasons I'm not keen on using Subversion externals to manage versions of Rails, I'd rather not rely on a remote Subversion repository to manage Rails plugins my applications use. Actually, it's even more important with plugins. There's nothing worse than not being able to deploy because Fred's plugin server took a long vacation. Again, it pays to be selfish when it comes to your application's dependencies.

Enter Piston. It lets you manage your external dependencies in a similar manner to a Subversion external link—it pulls changes from a third-party Subversion repository—but it actually checks the changes into your local repository. And when you're ready to step up to a new revision of Fred's plugin, it makes it drop-dead easy to update your local repository with the remote repository. Indeed, it's all the goodness of svn:externals without all the downsides of remote repository access.

And judging by the number of passionate emails from happy Piston users, I figured it deserved a write-up...

Fits Like a Piston

Say you want to use the resource_feeder plugin to serve up RSS and Atom feeds from your Rails application. How do you manage the plugin with Piston?

1. Install Piston
$ gem install piston
2. Convert or Import

You have two choices for checking the plugin in to your Subversion repository. If you already have an external link to a remote repository, you can convert it to a Piston-managed folder of your repository. Alternatively, you can import the plugin into your repository straight from the remote repository.

Convert an Existing External

If you're currently using an svn:externals link, you can inspect it by typing

$ cd my_rails_app
$ svn proplist --verbose vendor/plugins
Properties on 'vendor/plugins':
  svn:externals : 
  resource_feeder http://dev.rubyonrails.com/svn/rails/plugins/resource_feeder

To convert it to a Piston-managed folder of your Subversion repository, type

$ piston convert
Importing 'http://dev.rubyonrails.com/svn/rails/plugins/resource_feeder' to vendor/plugins/resource_feeder (-r 5719)
Exported r5719 from 'http://dev.rubyonrails.com/svn/rails/plugins/resource_feeder' to 'vendor/plugins/resource_feeder'

Done converting existing svn:externals to Piston

Then check the changes into your Subversion repository:

$ svn commit vendor/plugins
Import a Non-Existing Plugin

To import the plugin straight into your Subversion repository, type

$ piston import http://dev.rubyonrails.com/svn/rails/plugins/resource_feeder \
                vendor/plugins/resource_feeder

Then check the changes into your Subversion repository:

$ svn commit vendor/plugins
3. Check the Status

To check the status of each Piston-managed directory, type

$ piston status
  vendor/plugins/resource_feeder (http://dev.rubyonrails.com/svn/rails/plugins/resource_feeder)
4. Update the Plugin(s)

When you're ready to snag the latest changes from the remote repository, type

$ piston update vendor/plugins/resource_feeder/
Processing 'vendor/plugins/resource_feeder'...
  Fetching remote repository's latest revision and UUID
Updated to r5719 (2 changes)

Updating is where Piston really shines. If you had simply copied a revision of the plugin into your repository, it would be inconvenient to sync up with future revisions of the plugin. But piston update updates the plugin in your repository to the latest revision in the remote repository, merging in any local changes you've made.

And how do you know when a plugin in a remote repository has been updated? Easy. Use Subtlety to get an RSS feed of changes.

To update all Piston-managed directories, simply type

$ piston update
5. Lock a Revision

If you want to lock the plugin to the current revision to avoid unexpected (unwanted) updates, type

$ piston lock vendor/plugins/resource_feeder/
'vendor/plugins/resource_feeder/' locked at revision 5719
6. Unlock and Update

Then when you're ready to live on the bleeding edge again, unlock the plugin by typing

$ piston unlock vendor/plugins/resource_feeder/
Unlocked 'vendor/plugins/resource_feeder/'

Then run piston update to update again.

Advantages

  • Checkouts and updates are faster than hitting a remote repository each time you run svn update or svn checkout. And plugins generally don't change often enough to warrant frequent remote queries.
  • You get control over when you update to new revisions, and can prevent inadvertent updates by locking to a specific revision.
  • You can deploy without the nail-biting fear of a remote plugin repository being offline.
  • You can modify plugins locally and your changes will be preserved when you update to a more recent revision.
  • Not everyone on your team needs to have Piston installed. Once you've imported or converted a plugin to a Piston-managed folder, folks without Piston installed can happily check out and update those plugins.

Disadvantages

  • Your repository grows in size more rapidly.
  • Merging can be slow, as described in the caveats.
  • Piston doesn't preserve change history from the remote repository. Rather, Piston will grab the current revision, or differences between what you currently have and the latest revision, and merge those changes into your checked out copy. However, you can examine the changes before committing them to your local repository.

Managing Rails Versions

Clearly you could also use Piston to manage the contents of your vendor/rails directory. It's simply a matter of typing

$ piston import http://dev.rubyonrails.org/svn/rails/trunk vendor/rails

or

$ piston convert vendor/rails

Frankly, I'm currently on the fence about doing this. Piston seems ideal for merging in relatively small chunks of code at a time, such as Rails plugins. I'm not yet comfortable enough with Piston to put all the Rails bits under its control in every Rails application. I have no reason not to trust it, I'm just preferring this approach for now. Your mileage may vary, of course.

Thanks to Graeme Mathieson and everyone else who wrote in with suggestions!