#!/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 = '
' . $logmsg . &listFiles('Added','A',$entry) . &listFiles('Modified','M',$entry) . &listFiles('Replaced','R',$entry) . &listFiles('Deleted','D',$entry) . '
'; ## Output this item... print '' , "\n" , 'Revision ' , $revision , ': ' , &svn_XML_Escape($firstLine) , '' , '' , &dateFormat($date) , '' , '' , $author , '' , "\n" , '' , &svn_XML_Escape($link) , '' , "\n" , '' , &svn_XML_Escape($link) , '' , "\n" , '' , &svn_XML_Escape($logmsg) , '' , "\n" , '' , "\n"; } print '
' , "
\n"; } else { print "Status: 404 Log Not Available\n"; &svn_HEADER('SVN RSS - Insurrection Server'); print '

Failed to access the log

' , '

Log command:

' , '
' , $cmd , '
'; &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) . ''; } return $result; }