CaptainHook
This is my first post to the msmvps.com web site. I decided to move my blog from http://community.strongcoders.com/blogs/vcsjones.
So, I thought I would start off by talking about something a little obscure that a co-worker turned me onto.
OK, different captain hook. We're not talking about Dustin Hoffman, either. No, I am talking about the .NET Subversion hook framework from Phil Haack. Phil and his company released a nice .NET Framework Library on Source Forge.
OK, a bit of information, first.
I'm a big fan of Subversion. It's what we use at our company for all of our source control, and we use it for a lot of things besides maintaining our code. Documents, Code, etc. It's by far my favorite Source Control solution, and the details of that are another blog post.
Aside from Subversion, we also use an Agile planning and estimation program called TargetProcess. It's very useful for Agile shops. But for the purpose of this post, think of it as any other tracking system, like Bugzilla or FogBugz (we use FB too for a client).
One of my favorite features of TargetProcess is that it integrates with Subversion cleanly. When I commit with Subversion, all I have to do is put a pound (#) and then the ID of the "story" (think of that like a bug) and then I can track it from TagetProcess, view diffs, and read commit messages to that given story. For example:
KJ - #1234 - Fixed minor typo in Administration screen.
Now when I look in TargetProcess at #1234, I can see the commit message, and the files that changed.
So, the problem I am trying to solve on the team is forgetting to put a number in the commit message. Fortunately, Subversion allows us to hook pretty easily. Specifically in this case, we want to use a pre-commit hook.
Despite Subversion's ease of hooking, it can be a little tricky in .NET. And unlike the movies, CaptainHook is the protagonist for this. I'm not going to go into details of how captain hook actually works, but I will go into detail of what's great about it.
Subversion allows hooking at the repository level. So, for a given repository, there are several types of hooks. The one I am keen on is the pre-commit. If you look in a repository, there is a directory called "hooks". When a commit is made, it will start a file named "pre-commit" It can be a BAT file, executable, perl script, what have you. If the exit code does not equal zero, the commit will not go through.
CaptainHook, in a nutshell, is an executable that probes a directory for assemblies with "hooks" in them. It executes the hooks, and if any of the hooks return false, CaptainHook exits with a non-zero code, probably 1.
So, what we want to do is put a regular expression on the commit message... something like "#\d+". What does one of these hooks look like?
public class RequireTargetProcessNumber : PreCommitHook
{
protected override bool HandleHookEvent(ITransactionInfo commit)
{
string commitMessage = commit.LogMessage;
if (!HasTargetProcessNumber(commitMessage))
{
this.Context.Output.WriteError("TargetProcess Number was not specified in commit message.");
return false;
}
else
{
return true;
}
}
public bool HasTargetProcessNumber(string commitMessage)
{
return Regex.IsMatch(commitMessage, @"#\d+");
}
}
So there are a few things going on in here... we are overriding the HandleHookEvent from our base class, PreCommitHook. This gives us an ITransactionInfo, which provides us with information about the current transaction. We test the commit message for our regular expression, and if it doesn't match, we fail. I extracted that regular expression to unit test it. If HandleHookEvent was public I could probably test it that way with a mock test as well.
Anyway, that's all there is to it. I can create as many assemblies of these as I want to modularize them, and drop them in a configurable path that CaptainHook looks for.
I highly recommend this for anyone that uses Subversion. It has a lot of other practical uses, such as emailing when a commit is done, or writing to an RSS feed on a successful commit.