How do I post commits to my project timeline?

Lighthouse allows for basic integration with your source control tool. Although a more specialized SCM(Source Control Management) tool may be desirable, it can be useful to send changeset notices to your Lighthouse dashboard. Lighthouse's source control support is general enough that other tools should work, but this example will focus on subversion.

Authentication

This example beacon will use a token for authentication. Go to the My Profile link in the top right of the page. There you can create a new API token for your account. You can specify the project if you want, but make sure full access is selected.

token01

token02

token03

Keywords in your changeset revision logs.

Once your subversion beacon is setup, you can now add update keywords to your log messages that will update tickets. To simply refer to a ticket:

Prototype new feature [#15]

The #15 will add this log message as a comment to ticket #15. You can also change various properties:

New feature is implemented and tested [#15 tagged:committed responsible:rick milestone:"Launch" state:resolved]

Setting up the script

As far as beacons go, this 73-line script for subversion integration is about as simple as they get. This script should work as a post-commit hook. It assumes your svn server has ruby with the standard library installed, and access to the svnlook and curl commands.

#!/usr/bin/ruby
require 'yaml'
require 'cgi'
require 'net/http'
require 'uri'

# configure multiple project settings below
SVNLOOK    = `which svnlook`.strip
LOG_FILE   = '/tmp/svn-hooks.log'

OPTIONS = {
  # default token
  :token   => 'CHANGEME',
  # tokens for other members of the team
  # name is matched against svn user name
  :users   => { 'bob' => "bob's token", 'fred' => "fred's token" },
  # full url, please
  :account => 'http://activereload.lighthouseapp.com',
  :project => 2, # REPLACE
  :prefix  => /^trunk/ # OPTIONAL
}

def gather_and_post(repo_path, revision, options)
  if options[:prefix]
    commit_dirs_changed = `#{SVNLOOK} dirs-changed #{repo_path} -r #{revision}`
    return unless commit_dirs_changed.split(/\n/)[0] =~ options[:prefix]
  end

  commit_author  = `#{SVNLOOK} author #{repo_path} -r #{revision}`.chop
  commit_log     = `#{SVNLOOK} log #{repo_path} -r #{revision}`
  commit_date    = `#{SVNLOOK} date #{repo_path} -r #{revision}`
  commit_changed = `#{SVNLOOK} changed #{repo_path} -r #{revision}`

  commit_changes = commit_changed.split("\n").inject([]) do |memo, line| 
    if line.strip =~ /(\w)\s+(.*)/
      memo << [$1, $2]
    end
  end.to_yaml

  changeset_xml = <<-END_XML
  <changeset>
    <title>#{CGI.escapeHTML("%s committed changeset [%d]" % [commit_author, revision])}</title>
    <body>#{CGI.escapeHTML(commit_log)}</body>
    <changes>#{CGI.escapeHTML(commit_changes)}</changes>
    <revision>#{CGI.escapeHTML(revision.to_s)}</revision>
    <changed-at type="datetime">#{CGI.escapeHTML(commit_date.split('(').first.strip)}</changed-at>
  </changeset>
END_XML

  token = options[:users][commit_author] || options[:token]
  url = URI.parse('%s/projects/%d/changesets.xml' % [options[:account], options[:project]])

  req = Net::HTTP::Post.new(url.path) 
  req.basic_auth token, 'x' # to ensure authentication
  req.body = changeset_xml.strip
  req.set_content_type('application/xml')

  res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
  case res
    when Net::HTTPSuccess, Net::HTTPRedirection
      ## all good, we submitted...
    else
      res.error!
  end
end

begin
  # feel free to add multiple calls below
  gather_and_post ARGV[0], ARGV[1], OPTIONS
rescue
  %x{echo "repo:#{ARGV[0]} rev: #{ARGV[1]}" > #{LOG_FILE}}
  %x{echo "Error: #{$!.to_s.gsub('`',"'")} trace:#{caller}" >> #{LOG_FILE}}
end

First, make sure the basic paths at the top to your commands are correct. Then, make sure the #gather_and_post call has the correct arguments. The :token, :account, and :project options are self-explanatory. The :prefix option should be a regex that is matched against the paths in the commit. If it's provided and matches the commit, the script proceeds with the ping.

You can also run this script manually to test. Assuming this is in /svn/my-repo/hooks/post-commit, you'd run it with: ruby /svn/my-repo/hooks/post-commit /svn/my-repo 1000 (fill in your own repository path and revision number).

The important SVN bits of this approach were yanked from: webtypes > Subversion post commit hook using basecamp API":http://www.webtypes.com/2006/03/31/subversion-post-commit-hook-using-basecamp-api. Also, thanks goes to "James Cox for taking the initiative in writing a better version with the Net/HTTP library.