To generate the RSS feed, you need a script, similar to the following one, that takes the repository and extracts the information about the last repository commit. The script itself is fairly long, but I’ll go over what’s happening shortly.
#!/bin/bash
# Main configuration variables ReposName=$1
RepositoriesDir=/svnrepos/repositories MaxItems=100
# Setup misc. variables
Repos=$RepositoriesDir/$ReposName RssFile=/var/www/html/rss/$ReposName.xml RssFileTmp=/var/www/html/rss/$ReposName.xml.tmp RssHeader=$Repos/rss/header RssFooter=$Repos/rss/footer RssItemsDir=$Repos/rss/items
1. The scripts and techniques in this section were graciously provided by Stuart Robinson and his employer, Absolute Systems (www.absolutesys.com).
11.2 Making the Most of Hook Scripts 173
#=================================================
# Attempts to create a lock file so that this script isn't
# affected by other instances that might be started at the same time. # If a lock-file already exists, this script is exited immediately. AcquireLockFile ()
{
LockFile=/svnrepos/locks/$ReposName.rssGen.lock if [ -a $LockFile ]; then
# Another process is currently updating the RSS feed, so exit. echo "Lock-file $LockFile exists. Exiting."
echo "" exit 1 fi
# Create the lock file touch $LockFile
echo "Lock-file $LockFile acquired." }
#================================================= ReleaseLockFile ()
{
rm -f $LockFile
echo "Lock-file $LockFile released." }
#================================================= ComputeFirstAndLastItemRevisions ()
{
echo "Computing first and last item revisions:" SvnHeadRevision=`svnlook youngest $Repos`
LastItemToInclude=$SvnHeadRevision
FirstItemToInclude=$((LastItemToInclude-MaxItems+1)) if [[ $FirstItemToInclude -lt 0 ]]; then
FirstItemToInclude=1 fi
echo " First revision to include: $FirstItemToInclude" echo " Last revision to include : $LastItemToInclude" echo " Max items : $MaxItems"
}
#================================================= DeleteOldItemFiles ()
“svnbook” 2005/4/14 14:55 page 174 #195
i i
174 Chapter 11 The Joy of Automation
{
echo "Deleting old item files"
FirstMissingItem=$((LastItemToInclude+1))
# Move all items we want to keep to tmp, and then delete all others. if [[ ! -a $RssItemsDir/tmp ]]; then
mkdir $RssItemsDir/tmp fi
for ((Rev=LastItemToInclude; Rev >= FirstItemToInclude; Rev--)) do
if [[ -a $RssItemsDir/Item.$Rev ]]; then
#echo " Moving $RssItemsDir/Item.$Rev to $RssItemsDir/tmp" mv $RssItemsDir/Item.$Rev $RssItemsDir/tmp
else
# We need items from FirstItemToInclude -> LastItemToInclude, but # the current item is missing, so record the revision number so # we can later access SVN to create the missing items.
FirstMissingItem=$Rev
#echo " DEBUG: FirstMissingItem=$FirstMissingItem" fi
done
#echo " Removing all other Item files in $RssItemsDir" rm -f $RssItemsDir/Item.*
# Now move the items we want to keep back to $RssItemsDir
#echo " Moving the files we're keeping from $RssItemsDir/tmp back to¬
$RssItemsDir"
mv -f $RssItemsDir/tmp/Item.* $RssItemsDir rmdir $RssItemsDir/tmp
}
#=================================================
# Creates new Items to represent each of the SVN revisions for
# which there are currently no item-files in $RssItemsDir (for revision # numbers >= FirstItemToInclude, and <= LastItemToInclude).
CreateNewItemFilesFromSVN () {
# Now access SVN to create RSS items for all revisions from $MaxRev¬
+1 -> $LatestRevision
echo "Accessing SVN to create missing items" echo " First missing item: Rev $FirstMissingItem" echo " Last missing item: Rev $LastItemToInclude"
for ((Rev=FirstMissingItem; Rev <= LastItemToInclude; Rev++)) do
11.2 Making the Most of Hook Scripts 175
echo " Creating <Item> for SVN revision $Rev" RssItemFile=$RssItemsDir/Item.$Rev
echo " ItemFile=$RssItemFile" echo " Computing vars..."
AuthorId=`svnlook -r $Rev author $Repos 2>&1`
Author=`getent passwd | grep $AuthorId | cut -d: -f 5`
CommitMsg=`svnlook log -r $Rev $Repos 2>&1`
CommitDate=`svnlook -r $Rev date $Repos 2>&1`
CommitDateRss=`echo $CommitDate | sed -e "s/\([^ ]*\) \([^ ]*\)¬
\([^ ]*\).*/\\1T\\2+02:00/"`
# Sample valid date=<dc:date>2004-06-07T17:03:30+02:00</dc:date> URL="http://svnserver/viewcvs?rev=$Rev&root=$ReposName&view=rev" FirstModifiedPath=`svnlook -r $Rev changed $Repos | cut -b5¬
-1000 | sed -e "s{\([^/]*/[^/]*\).*{\1{" | uniq`
Category=`echo $FirstModifiedPath | sed -e "s&\(.*\)/\(.*\)¬
&\\2 (\\1)&"`
echo " Done computing vars" echo " <item>" > $RssItemFile
echo " <title><![CDATA[$CommitMsg]]></title>" >> $RssItemFile echo " <link><![CDATA[$URL]]></link>" >> $RssItemFile
echo " <description><![CDATA[$CommitMsg]]></description>" >> ¬
$RssItemFile
echo " <category>$Category</category>" >> $RssItemFile echo " <dc:creator>$Author</dc:creator>" >> $RssItemFile echo " <dc:date>$CommitDateRss</dc:date>" >> $RssItemFile echo " <pubDate>$CommitDateRss</pubDate>" >> $RssItemFile echo " </item>" >> $RssItemFile
echo "CommitDateRss=$CommitDateRss" done
}
#=================================================
# Echos the contents of RssHeader, followed by each of the Rss Item ¬
files
# in revision-number-order, followed by RssFooter to the Rss file being¬
generated.
AssembleThePieces () {
echo "Assembling the pieces" cat $RssHeader > $RssFileTmp
“svnbook” 2005/4/14 14:55 page 176 #197
i i
176 Chapter 11 The Joy of Automation
echo " <dc:date>$PubDate</dc:date>" >> $RssFileTmp echo " <pubDate>$PubDate</pubDate>" >> $RssFileTmp
echo " <lastBuildDate>$PubDate</lastBuildDate>" >> $RssFileTmp # Add all RSS items to the RSS file
for ((Rev=LastItemToInclude; Rev >= FirstItemToInclude; Rev--)) do
cat $RssItemsDir/Item.$Rev >> $RssFileTmp done
# Add the RSS footer
cat $RssFooter >> $RssFileTmp mv -f $RssFileTmp $RssFile }
#================================================= # Generates a new RSS file for the repository. GenerateRssFile () { AcquireLockFile ComputeFirstAndLastItemRevisions DeleteOldItemFiles CreateNewItemFilesFromSVN AssembleThePieces ReleaseLockFile echo "Done." } GenerateRssFile
Setting Up Variables
Let’s take a look at this script, section by section. The first section sets up a number of useful variables that will be used throughout the rest of the script.
# Main configuration variables ReposName=$1
RepositoriesDir=/svnrepos/repositories MaxItems=100
# Setup misc. variables
Repos=$RepositoriesDir/$ReposName
RssFile=/var/www/html/rss/$ReposName.xml
11.2 Making the Most of Hook Scripts 177
RssHeader=$Repos/rss/header RssFooter=$Repos/rss/footer RssItemsDir=$Repos/rss/items
The first three variables are the important configuration variables that need to be cus- tomized for the specific location of the script. TheReposNamevariable indicates the name of the repository associated with the feed. In this case, that variable is taken from the first argument sent to the script, which is supplied by thepost-commithook script that calls thegenRSSscript. TheRepositoriesDirvariable points to the directory where all of the Subversion repositories are stored. So, if you have two repositories, /var/svnrepos1
and /var/svnrepos2, you would set RepositoriesDir equal to /var. Finally, the
MaxItemsvariable stores the maximum number of items that is included in the RSS feed.
Locking the Script
Because commits can occur very close together, it would be possible for this script to end up running concurrently with another instance of itself. To avoid that, and serialize the running of the script, we need to create a lock that will be acquired when the script is run, and released when it is finished. If another script attempts to acquire the lock at the same time, the script will exit.
#=================================================
# Attempts to create a lock file so that this script isn't
# affected by other instances that might be started at the same time. # If a lock-file already exists, this script is exited immediately. AcquireLockFile ()
{
LockFile=/svnrepos/locks/$ReposName.rssGen.lock if [ -a $LockFile ]; then
# Another process is currently updating the RSS feed, so exit. echo "Lock-file $LockFile exists. Exiting."
echo "" exit 1 fi
# Create the lock file touch $LockFile
echo "Lock-file $LockFile acquired." }
#================================================= ReleaseLockFile ()
{
rm -f $LockFile
echo "Lock-file $LockFile released." }
“svnbook” 2005/4/14 14:55 page 178 #199
i i
178 Chapter 11 The Joy of Automation
This section of the script consists of two fairly simple functions. The first function,
AcquireLockFile()simply checks to see if the lock file exists. If it does, the script exits. If there is no lock file, the script creates one by runningtouch. When the script exits, the
ReleaseLockFile()function is called. This function simply removes the lock file that
AcquireLockFile()created, thus freeing up the next instance of the script to run.
Computing Revision Range
Next, we need to compute the range of revisions that will be included in the RSS feed, which will be made up of a number of revisions equal to the value ofMaxItems(as set at the beginning of the script), from the HEAD revision back. So, ifMaxItemsis equal to 100, and the repository is currently at revision 1400, the range is from revision 1301 through revision 1400.
#================================================= ComputeFirstAndLastItemRevisions ()
{
echo "Computing first and last item revisions:" SvnHeadRevision=`svnlook youngest $Repos`
LastItemToInclude=$SvnHeadRevision
FirstItemToInclude=$((LastItemToInclude-MaxItems+1)) if [[ $FirstItemToInclude -lt 0 ]]; then
FirstItemToInclude=1 fi
echo " First revision to include: $FirstItemToInclude" echo " Last revision to include : $LastItemToInclude" echo " Max items : $MaxItems"
}
The HEAD revision of the repository is found by runningsvnlook youngest, which returns the revision number of the youngest revision in the repository. Then, the beginning of the range is calculated by subtracting theMaxItemsvalue from the HEAD revision. If the first revision happens to fall below zero (i.e., there aren’tMaxItemsrevisions in the repository), the start of the range is set to the beginning of the repository.