January 03
Subversion Hooks Best Practices - Part 2
In this second part of the Subversion Hooks Best Practices series, we'll go into the post-commit hook, or the hook that is executed after a commit is successfully made to the repository. It is rare to find a software project (open source or not) that does not have some type of bug database. However, having to write what you are doing in the bug database for a bug and manage the checkins of your code can be time consuming and wasteful. Thus, this hook will add your commit message and changed files to a comment on a specific ticket in your bug database. This tutorial will use the Mantis Bug Tracker, however, it can easily be updated to work on other bug trackers as well.
post-commit
The post-commit hook is the hook that is executed after the files are committed to the repository. This hook must return true to allow the commit.
Purpose
The post-commit hook in this article will add a comment entry to a bug in a Mantis Bug Tracking system if told to do so.
Layout
The layout of the hook is simple. The hook script will call a larger Perl script that will analyze the commit message. If the message contains a pound sign followed by a number (#45) for instance, it will attempt to locate that bug in the online bug database and append the checkin comments and changed files.
You will have to create two files before you begin, they are /var/subversion/my_repo/hooks/post-commit and /usr/share/subversion/hook-scripts/post-message.pl.
On your Subversion server, change to the /var/subversion/my_repo/hooks/ directory. Doing a listing here will show the template files for the hooks. You will need to rename the post-commit.tmpl file to post-commit and give it execute permissions.
sudo cp post-commit.tmpl post-commit && sudo chmod +x post-commit
Open the file for editing in your editor (I will use vim).
sudo vim post-commit
You will see the shebang line at the top, indicating this is a shell script, a long comment describing the file, and some shell code at the bottom. Most of this can be ignored, and I suggest deleting the large comment so it does not get in the way.
The two lines to pay attention to are
REPOS="$1" REV="$2"
These are the arguments passed into the hook script. The first argument is the directory that the repository is located in. This is not the directory the repository is checked out to! It is the /var/subversion/my_repo/ directory. The second parameter is RVN, or the Revision Number of this commit. After the transaction is committed to the repository, it receives a Revision Number. This information will be passed to the Perl script. You can safely remove all of the code between the code above and the end
#!/bin/sh REPOS="$1" RVN="$2"
As stated earlier, the values of $REPOS and $RVN will be passed to the post-message.pl file for further analysis. This is accomplished by adding this line between the variables and the exit so your script looks like
#!/bin/sh REPOS="$1" REV="$2" /usr/share/subversion/hook-scripts/post-message.pl "$REPOS" "$REV"
Because the commit has already been made, if this hook fails, all it can do is report back to you the failure data, it can't prevent anything from happening. Thus, the exit code is not analyzed to determine what to do.
Create the post-message.pl file in the directory /usr/share/subversion/hook-scripts/
cd /usr/share/subversion/hook-scripts/ sudo touch post-message.pl sudo vim post-message.pl
Because of the length of the script, I will present it entirely here and then it will be deconstructed.
#!/usr/bin/perl -w
use strict;
use DBI;
my $dbh = DBI->connect('DBI:mysql:database_name:server_name', 'database_user_name', 'database_password') or die;
# Similar to the hook script itself, this script receives the repository path
# and the revision number and uses those to get appropriate data.
my $repos = shift @ARGV;
my $rev = shift @ARGV;
# Ensure a valid revision is given.
die if $rev < 1;
my $commit_msg = `svnlook log -r$rev "$repos"`;
my $changed_files = `svnlook changed -r$rev "$repos"`;
my $svn_user = 4; # A user with this ID has been created in the bug tracker
if ( $commit_msg =~ m/\#(\d*)/i ) {
my $bug_id = $1;
# First, ensure the bug actually exists
my $stm = $dbh->prepare('SELECT * FROM `mantis_bug_table` WHERE id = ?');
$stm->execute($bug_id);
$commit_msg = $commit_msg . "\n";
$commit_msg .= "=== Changed Files ===\n";
$commit_msg .= '<pre>' . $changed_files . '</pre>';
if ( $stm->rows == 1 ) {
$stm = $dbh->prepare("INSERT INTO `mantis_bugnote_table` (
bug_id, reporter_id, bugnote_text_id,
date_submitted, last_modified
) VALUES (
?, ?, ?,
NOW(), NOW()
)"
);
$stm->execute($bug_id, $svn_user, 1);
my $insert_id = $dbh->last_insert_id(undef, undef, qw(mantis_bugnote_table id));
$stm = $dbh->prepare("INSERT INTO `mantis_bugnote_text_table` (id, note) VALUES (?, ?)");
$stm->execute($insert_id, $commit_msg);
$stm = $dbh->prepare("UPDATE `mantis_bugnote_table` SET bugnote_text_id = ? WHERE id = ?");
$stm->execute($insert_id, $insert_id);
}
undef $stm;
}
$dbh->disconnect();
Configuration
The emphasized text above is for you to populate with your specific values. In the case of my server, my Subversion server and Bug Tracking server are not the same. Thus, I had to create a user for MySQL (the database that Mantis uses) that had cross network connectivity. You will have to do the same if they are different servers. Additionally, if you are using Mantis, create a user named subversion that only has reporter privileges. Then lock the user so no one can log in with it. Update the $svn_user variable with it's User ID.
The meat of the script starts at the lines:
my $commit_msg = `svnlook log -r$rev "$repos"`; my $changed_files = `svnlook changed -r$rev "$repos"`;
and continues to the end.
svnlook
Bundled with Subversion is another command line program called svnlook. This program can easily analyze a repository by passing the action, and repository path, and the Transaction ID or Revision Number. The complete svnlook reference is online and I suggest you read through it.
As discussed previously, the svnlook command will analyze the new checkin. The log and changed get the checkin log and the changed files, respectively. The log message is passed to the variable $commit_msg and checked with a regular expression. The regular expression is simple: if, at any where in the string is a pound (#) followed by any number of digits, it is considered a reference to a Bug ID. Thus,
I fixed the rounding error for #45is a valid match.
This C# code is really difficult to debug!is not.
Next, the bug is checked against the database to ensure it exists. If it does not, the script just continues and exits. Otherwise, the commit message and changed files are formatted. The PRE tag is added around the changed files so they are easily read in the comment. In Mantis, the PRE tags are not stripped out. Thus, the message would look like:
I fixed the rounding error for 0000045
U trunk/Artisan/Db/Result/Mysqli.php U trunk/Artisan/Db/Result.php U trunk/Artisan/Db/Sql/Insert.php U trunk/Artisan/Db/Sql.php
Another nice feature of Mantis is that it recognizes the #number format as well, and if that bug exists, the #45 is converted to a link to the bug 45.
Continuing on, Mantis has a slightly different way of storing its comments. First, the initial comment is created in the bugnote table. This stores everything except the text of the comment to make searching and look up faster. Then the $insert_id is found. This is the ID of the newly inserted comment record, *not* the ID of the bug, we already know that. Next, the actual text of the comment is added to the bugnote_text table. Finally, and this is what I find odd, is that the original bugnote table has to be updated with another ID of itself. I'm not clear why, but would be interested in asking the Mantis developers. At any rate, its a simple UPDATE.
Finally the connection to the database is closed and the script is finished.
Another facet that I have seen added is to use the svnlook command to get a diff of everything checked in and add this to the bug comments as well. I played around with this but never found something I really liked, and thus, took it out. Additionally, if you do a large checkin of many changed files, it can make your bug comments long and difficult to read.
