#!/usr/bin/perl
#
# $Id$
# Copyright 2004-2006 - Michael Sinz
#
# This script handles the return of rss data.
#
require 'admin.pl';
## First, lets see if we in good standing...
&checkAuthMode();
## The maximum number of entries to be returned in
## the RSS Feed. This is just in case there was
## a very busy period in the repository.
my $MAX_ENTRIES = 20;
## For the RSS data we will show up to n days worth
## of activity as long as it is less than the above
## number of entries.
my $RSS_DAYS_RANGE = 7;
## Rough guess as to the number of days in a month...
## ('?','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
my @monthDays = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
## Yes, yes, I know that Feb has 29 days every so often. But
## we don't really care if we got a little bit of extra data
## every once in a while... So we just ignore that fact...
## Get the local document URL
my $rssURL = &svn_URL();
## Split the repository from the path within the repository
my $rpath = &svn_REPO();
my $opath = &svn_RPATH();
## What we are going to do here is just get the log entry for the
## head revision and use it to find out the date of the last
## entry. Then, we use the date formula to figure out how
## far back to ask for. This is all done in the hopes of not
## needing to get the whole revision history and then trimming
## it. We really just want the last n days (but no more than
## MAX_ENTRIES) from the last entry date.
##
## This gives us useful RSS feeds even for repositories that
## have been quiet.
## We first look for the HEAD revision to find out the time
## of the newest revision...
my $rev = '-r HEAD ';
## Arg - svn log -r HEAD does not really work if there are no
## current changes in this part of the tree. So, we need to
## find the most current revision first, using svn ls -v
## and looking for the largest revision almost works except
## that the directory may be newer than any of its contents
## so we then need to get all revisions from the HEAD to
## that highest revision (without copies). Usually this
## will result in 1 or 2 entries at most, but it requires
## that "svn ls -v" run first, which is extra overhead.
## Since this is not needed at the root of the repository
## we don't do it unless we are not at the root.
## (The root always has every revision since it is all paths)
if ($opath ne '/')
{
my $cmd = $SVN_CMD . ' ls -v ' . $rssURL;
my $lastR = 0;
foreach my $rline (split("\n",`$cmd`))
{
if ($rline =~ m/^\s*(\d+)\s+/o)
{
my $r = 0 + $1;
$lastR = $r if ($r > $lastR);
}
}
$rev = "-r HEAD:$lastR ";
}
my $cmd = $SVN_CMD . ' log --non-interactive --no-auth-cache --xml --stop-on-copy ' . $rev . $rssURL;
my $hdata = `$cmd`;
if ($hdata =~ m:\s*(.*?)\s*:so)
{
## Date format: 2005-05-07T02:41:02.820786Z
my $firstDate = $1;
## Check if this is the same as before...
if ("\"$firstDate\"" eq $ENV{'HTTP_IF_NONE_MATCH'})
{
## The user tells me he already has this one...
print $cgi->header('-Status' => '304 Not Modified');
exit 0;
}
if ($firstDate =~ m/^(\d+)-(\d+)-(\d+)(T.*)$/o)
{
my $year = $1;
my $month = $2;
my $day = $3;
my $rest = $4;
$day = $day - $RSS_DAYS_RANGE;
while ($day < 1)
{
$month = $month - 1;
if ($month < 1)
{
$year = $year - 1;
$month = 12;
}
$day = $day + $monthDays[$month]
}
$rev = sprintf("-r 'HEAD:{%04d-%02d-%02d%s}' ",$year,$month,$day,$rest);
}
}
## Now, lets build the correct command to run...
$cmd = $SVN_CMD . ' log --non-interactive --no-auth-cache --xml --stop-on-copy --verbose ' . $rev . $rssURL;
## Default our encoding to UTF-8, just in case we are not
## given one by the server.
my $encoding = 'utf-8';
my $log;
my $top;
my $topDate;
my @entries;
if ((defined $rpath) && (defined $opath))
{
$log = `$cmd`;
## Parse the log into entries array...
@entries = ($log =~ m|()|sgo);
## Drop all of the entries that are beyond our limit...
while (@entries > $MAX_ENTRIES)
{
pop @entries;
}
## Get our XML intro so we can match the encodings.
## (We are not changing any bytes of content, so it will
## be whatever was given to us.
if ($log =~ m:(<\?xml.*?\?>):so)
{
$top = $1;
if ($top =~ m/encoding="(.*?)"/so)
{
$encoding = $1;
}
}
## Get the date of the first entry
if ($entries[0] =~ m:\s*(.*?)\s*:so)
{
$topDate = $1;
}
## And the date of the last entry
if ($entries[@entries - 1] =~ m:\s*(.*?)\s*:so)
{
$endDate = $1;
}
}
if ((defined $top) && (defined $topDate))
{
## Check if we have loaded the admin stuff yet...
&loadAccessFile() if (!defined %groupComments);
## Note that RSS feeds expire after 120 minutes...
## Also, we use ETag and Last-Modified such that we can
## return conditional get results. Note that we only really
## look at the ETag so we don't worry about making a valid
## Last-Modified header.
print $cgi->header('-expires' => '+120m' ,
'-Last-Modified' => &dateFormat($topDate) ,
'-ETag' => "\"$topDate\"" ,
'-type' => 'text/xml; charset=' . $encoding);
my $rLink = &svn_HTTP() . &svn_URL_Escape($SVN_REPOSITORIES_URL . $rpath . $opath) . '?Insurrection=log';
print $top , "\n"
, '' , "\n"
, "\n"
, "\n"
, "\n"
, ''
, '' , "\n"
, 'Repository: ' , &svn_XML_Escape($rpath . ': ' . $opath) , '' , "\n"
, 'RSS Feed of the activity in "' , &svn_XML_Escape($opath)
, '" of the "' , &svn_XML_Escape($rpath)
, '" repository from ' , &dateFormat($topDate)
, ' to ' , &dateFormat($endDate) , '. <hr/>'
, &svn_XML_Escape($groupComments{$rpath . ':/'})
, '' , "\n"
, '' , &svn_XML_Escape($rLink) , '' , "\n"
, 'Insurrection RSS Feeder - '
, &svn_XML_Escape('$Id$')
, '' , "\n"
, '' , &dateFormat($topDate) , ''
, '' , &dateFormat($topDate) , ''
, '120' , "\n";
foreach my $entry (@entries)
{
my ($revision) = ($entry =~ m:revision="(.+?)">:so);
my ($author) = ($entry =~ m:\s*(.*?)\s*:so);
my ($logmsg) = ($entry =~ m:\s*(.*?)\s*:so);
my ($date) = ($entry =~ m:\s*(.*?)\s*:so);
## Get the first line...
my ($firstLine) = ($logmsg =~ m:^(.*?)(\n|$):so);
## Convert line enders into
$logmsg =~ s:\n: :sgo;
## If the author does not have a domain, add the default one
$author = &emailAddress($author);
## Make the link to this individual log message.
my $link = $rLink . '&r=' . $revision;
## Now finish building the log message...
## (It get escaped below)
$logmsg = '
';
&svn_TRAILER('$Id$');
}
## Build the list of files modified/updated/etc by the revision...
sub listFiles($msg,$tag,$entry)
{
my $msg = shift;
my $tag = shift;
my $entry = shift;
my @files = ($entry =~ m:]*action="$tag"[^>]*>(.*?):sg);
my $result = '';
if (scalar(@files) > 0)
{
$result .= '' . $msg . ': ' . scalar(@files) . '