I've created this blog to catalog software ideas, code examples and everything in between. I've been in the industry for over 3 years and have worked on a variety of projects from Identity and Access Management, to Web Application Security, to Java/Ruby application development.

Read More

Using Basecamp to keep track of SVN commits

The Problem

How can I ensure that I have merged all my changes into my branches?

I’ve been working on a project recently that includes a few developers, a lot of code, and multiple different code branches.  The code has quick release cycles, and the problem I run into more often than not is pushing releases out the door that are missing bug fixes that have been committed to trunk, but have inadvertently not made it into the release branch.  For me, this is made more difficult because of the speed of development, multiple developers and multiple different branches to merge the fixes into.  This is most likely a common problem, with a myriad of different process based solutions, but for me, it’s got to be quick, and it has to not impede my frantic work flow.

In general, solutions that work to help me have to be largely transparent and heavily automated.  That is why I came up with an idea to solve this problem of forgetting to merge in critical pieces of code that have been committed to trunk.  Here is an example layout of a source tree:

trunk/
branches/product-version-1.0
branches/product-version-2.0
branches/product-version-3.0
branches/product-version-4.0
branches/product-version-5.0
branches/FeatureDevelopment

In this example situation we have a stable trunk, and a branch dedicated to each major version of the product as well as one branch used to perform feature development. The feature development branch,  in some cases, is not stable. When either bug fixes, or features are ready to be committed they are done so in logical units that are well tested and documented with commit messages. Then comes the task of merging all of those commits to all of the other branches that need the updated code.  How does one keep track of all of those commits by multiple developers working on the project, and ensure all of them make it in?

The Solution

In my case I use Basecamp heavily to manage Project To-Do’s, Documents, etc.  Because of this my solution makes use of Basecamp to create a To-Do each time code is commited as a mechanism to remind me before I do a release to ensure all of the code has been merged.  Because I have limited time and energy to manually create To-Do’s each time I commit something, or tell all of my developers to do the same, this has to be automated.  Luckily Basecamp comes with a handy Basecamp API.  Since I don’t feel like using curl to make all of my requests and handle responses myself I’ll make use of their Ruby wrapper to do the XML preparation and HTTP response handling for me.

With this wrapper script I will be able to create my own script that will take the necessary information from the SVN commit message and create a To-Do.  The next question is how to make sure this script is called automatically when code is committed to SVN.  Enter the concept of the SVN post commit hook.  If you’ve ever created an SVN repository or inherited one you’ve probably seen the hooks directory.  To learn more about it take a look at the Hook Scripts section here.  Basically every time a commit is made to the SVN repository, it will invoke a script.  In that script we will invoke our ruby script that will create the To-Do in Basecamp.

So, now we have our Basecamp API wrapper script, we know we’re going to add to SVN’s post commit hook script to call it, now we need to know how we’re going to get the information out of SVN as it relates to the commit message, and possibly who the committer was.  That is where we’ll use the svnlook utility.  Svnlook will give us a lot of information relating to a specific revision.  Here is an example of some of the information that can be gleaned from svnlook: author, changed, date, diff, dirs-changed, info, log.  That is just a subset of the options available to use with svnlook, but are the most pertinent in this case.

Now comes the actual script itself that will use svnlook and the Basecamp API to create the To-Do.  In my case, I want to only create a To-Do if I’m not merging code, and my continuous integration engine Continuum is not building a release for me.  The code is pasted below.

The Code

#!env ruby
#
#  add_commit_todo.rb
#  AddCommitTodo
#
#  Copyright 2010 Paul Codding
#  All rights reserved.
#
#  Released under the BSD license.

require File.dirname(__FILE__) + '/basecamp'
require 'time'

class AddCommitTodo
  PROJECT_NAME = "Your Project"
  TODO_LIST_NAME = "Your To-Do List that could be named 'Commits To Be Merged'"
  BASECAMP_URL = "yourcompany.basecamphq.com"
  BASECAMP_USER = "yourusername"
  BASECAMP_PASSWORD = "yourpassword"
  BASECAMP_USE_SSL = true
  SVNLOOK_PATH = "/usr/bin/svnlook"

  def initialize(repo_path, revision)
    if (repo_path && revision)
      @repo_path = repo_path
      @revision = revision
      @session = Basecamp.establish_connection!(BASECAMP_URL, BASECAMP_USER, BASECAMP_PASSWORD, BASECAMP_USE_SSL)
    else
      usage()
      exit(1)
    end
  end

  def usage
    warn "Please specify the repo path and the revision... e.g. 'ruby add_commit_todo.rb /home/paul/svn/project 100'"
  end

  def add_todo
    # Determine the To-Do content using svnlook
    svn_log = `#{SVNLOOK_PATH} log #{@repo_path} --revision #{@revision}`
    svn_log.strip!
    puts "[D] SVN Log output for revision: #{@revision} - '#{svn_log}'"
    if svn_log.include?("erge") || svn_log.include?("maven")
      puts "[!] Not adding To-Do as this is a Merge operation"
    else
      # Find the correct project
      Basecamp::Project.find(:all).each { |project| project.name.eql?(PROJECT_NAME) ? @project = project : nil }
      Basecamp::TodoList.all(@project.id, false).each { |todo_list| todo_list.name.eql?(TODO_LIST_NAME) ? @todo_list = todo_list : nil }
      puts "[*] Adding merge To-Do to list: '#{@todo_list.name} (#{@todo_list.id})'"
      @todo_item = Basecamp::TodoItem.new(:todo_list_id => @todo_list.id)
      @todo_item.content = "[ r#{@revision} ] - #{svn_log}"
      @todo_item.responsible_party = "c#{@project.company.id}"
      @todo_item.notify = true
      puts "[*] Saving To-Do Item with content: '#{svn_log}'"
      @todo_item.save
    end
  end
end
act = AddCommitTodo.new(ARGV[0], ARGV[1])
act.add_todo

The script can be downloaded here.

Ways to Improve

There are several things that could be added into this code, like the ability to automatically close out To-Do’s as the code is merged, or also create a Message in Basecamp to notify the team when bug fixes have been merged, etc.  There are also a number of other examples of Basecamp related post commit hooks here and here.

Tags: , ,

Filed under:Software

Leave a Reply