getSubversionRevision UDF

First, a confession.   I cheat hard-core on the deployment for most of my personal apps.  In almost all cases I check out a working directory in the production webroot and just use `svn update` to "deploy".  I've a couple apps that I use an Ant/Rsync-based mechanism to deploy where I actually have stuff to build, but in general I skip it.  It also allows easy tweaking in production which I don't generally recommend, but for apps that I'm the only user it's quite handy.

Anyway, I needed to check the Subversion revision of files and directories within my working copy so that I can append a query string to my static assets and then set their Expires header to "forever".  That way browsers will cache them forever, but when the app gets updated the query string will change so browsers will download a new copy.  Here's the function:

<cffunction name="getSubversionRevision" output="false" returntype="numeric">
  <cfargument name="path" type="string" required="true" />
  <cfset var output = "" />
  <cfif NOT directoryExists(path) AND NOT fileExists(path)>
    <cfthrow type="IllegalArgumentException"
      message="You can't get the revision of a non-existent path" />
  <cfset path = REReplace(path, "[\\/]", createObject("java", "").separator, "all") />
  <cfexecute name="svn"
    arguments="info #path#"
    variable="output" />
  <cfif NOT output CONTAINS "Last Changed Rev">
    <cfthrow type="SubversionException"
      message="Unknown output returned from 'svn info #path#'"
      detail="#output#" />
  <cfreturn REReplaceNoCase(output, "^.*Last Changed Rev:\s*([0-9]+).*$", "\1") />

To use it, pass it the absolute path of a directory or file that is SVN managed, and you'll get back the revision number for that path.  Obviously it requires the `svn` command line binary to exist on the system path for the user your CFML engine is running as.  The output parsing also assumes a Subversion 1.5 client, though it may work with older releases.  I've also only tested on Linux, but it shouldn't matter.

On application startup I run the UDF to set an application variable:

<cfset application.subversionRevision = getSubversionRevision(
) />

And then in my JS/CSS URLs, I append that revision:

<script src="static/util.js?m=#application.subversionRevision#"></script>

The second step is clearly divorced from the subversion stuff, you can use any indicator thing you want, but revision is about the right level of stable.  With a "real" build process you would just write the tagged revision to the CFSET as a static string, but in the absence of that, this seems to be a nice approach.

NB: I've simplified the examples – they use the revision of Application.cfc (or wherever the first line lives) for the caching of util.js.  In real life, you'll probably want to be a little more specific in your revisions, or just use a real deployment mechanism.  ;)

5 responses to “getSubversionRevision UDF”

  1. Brian

    Barney – doesn't this approach though reset all of the caching every time you deploy a single file? The revision of your application will always be the most recent commit version so even if you haven't changed jquery.js in 2 years, if you change test.cfm you're going to have a new m=XXX value, no?

  2. Brian

    Side note, I address this via an Ant script which gets the current revision number and puts it into a Coldspring-managed SimpleConfiguration file which I can use anywhere in my site (the config bean is put in the model-glue event bucket onRequestStart, so it's available to every view).

  3. Brian

    I was taking this was running from Application.cfc – I see now that it would only take the value of Application.cfc versus the HEAD. That still seems to have the same problems though – if you're using this to set revision numbers for caching on JS and CSS, you're setting it based on an unrelated file? I guess the drawbacks are offset by the use case – personal sites where you don't sweat the deployment details so much.

    I run all of my wordpress sites via SVN similar to what you described above… svn switch to the next tag.