#!/usr/bin/perl -w # # Perl script to create a chronological HTML file from 'cvs log' output # # cvs2chrono Copyright (C) Dorando 2003 # # 0.5.0 / 06 Aug 2003 / Complete rewrite, new options, now uses strict # 0.3.6 / 24 Apr 2003 / Handles branches correctly, for deleted files # 0.3.5 / 18 Apr 2003 / New file list format # 0.3.1 / 16 Apr 2003 / Bugzilla links, diff for webcvs, extended time format # 0.3.0 / 15 Apr 2003 / New parser, generates smaller and better output # 0.2.2 / 14 Apr 2003 / Can now use a namefile, direct link to revisions # 0.2.1 / 14 Apr 2003 / Removes Attic/ in displayed dir, smaller output # 0.2.0 / 14 Apr 2003 / Now XHTML1.1 conform, and takes much less memory # 0.1.0 / 13 Apr 2003 / Initial release # # Permission is granted to redistribute this script under the terms of the # GNU General Public License http://www.gnu.org/copyleft/gpl.html use strict; my $version='0.5.0'; # loading required modules use Getopt::Long qw(:config gnu_getopt); use Pod::Usage; use Date::Parse; use Date::Format; # defining global variables use vars qw( $opt_bugzilla $opt_config $opt_cvs $opt_daterange %opt_dirs $opt_dummy $opt_extendtime $opt_help $opt_header $opt_input %opt_names $opt_output $opt_reverse $opt_simple $opt_small $opt_title $opt_verbose $opt_webcvs ); # defining local variables my (@lines, @log); # default values for options $opt_output='chrono.html'; $opt_title='Chronological CVS Log'; $opt_webcvs=''; # load an ArgvFile if this module is available, otherwise try a perl script if(eval 'require Getopt::ArgvFile qw(argvFile);'){argvFile(home=>1);} else{eval "require '$ENV{'HOME'}/.cvs2chrono'";} # parse command line options GetOptions('bugzilla|b=s' => \$opt_bugzilla, 'config|c=s' => \$opt_config, 'cvs|options=s' => \$opt_cvs, 'daterange|d=s' => \$opt_daterange, 'dir|directory=s' => \%opt_dirs, 'extendtime|e=s' => \$opt_extendtime, 'help|h|?' => \$opt_help, 'input|i=s' => \$opt_input, 'module|m=s' => \$opt_dummy, 'name=s' => \%opt_names, 'output|o=s' => \$opt_output, 'prefix|p=s' => \$opt_dummy, 'reverse|r' => \$opt_reverse, 'simple' => \$opt_simple, 'small|s' => \$opt_small, 'title|t=s' => \$opt_title, 'verbose|V' => \$opt_verbose, 'webcvs|w=s' => \$opt_webcvs ) || pod2usage(1); # print usage if($opt_help){pod2usage(1)}; # load additional config file if($opt_config){eval "require '$opt_config'"}; # get CVS log data if($opt_input) # from an input file { open(LOG,$opt_input) or die "Error: Could not open $opt_input"; @lines=; close(LOG); } else # from cvs log { $opt_cvs||=""; if($opt_daterange){$opt_cvs.='-d"'.$opt_daterange.'"';} print $opt_cvs; @lines = `cvs log $opt_cvs `; } # parse through the log file beginning on second line for(my $length=@lines, my $i=1; $i<$length; $i++) { if($lines[$i] =~ 'RCS') { # defining local variables my ($rev, $time, $author, $logtext, $state); # get current file my ($file) = ($lines[$i+1] =~ 'Working file: (.*)'); #TODO document # output it if desired if(defined($opt_verbose)){print "Processing $file\n"}; # skip the header until($lines[$i] =~ "^(=|-)+\n"){$i++;} # read current block until($lines[$i] =~ "^=+\n") { # current line is either a revision if($lines[$i] =~ "^revision (.+)"){$rev=$1;} # or the date/author/state elsif($lines[$i] =~ "^date: (.+?); author: (.+?); state: (.+?);") {$time=$1;$author=$2;$state=$3;} # or a comment line elsif(!($lines[$i] =~ "^-+\n")) { $logtext.=$lines[$i]; # we don't want newlines at start so remove them if($logtext eq "\n"){$logtext="";} } # next line is garbage, skip it $i++; # commit is completed if($lines[$i] =~ "^(=|-)+\n") { # set logtext if it hasn't been set yet $logtext||=""; # last newline interferes with next step, remove it chomp($logtext); # no gain except for looking nicer $_=$logtext; # first make the logtext html conform s{&}{&}g; s{<}{<}g; s{>}{>}g; # break line, break s{\n}{
\n}g; # create for picture links s{(http://.+?\.(jpg|jpeg|png|mng|gif))}{}g; # create for bugzilla references if($opt_bugzilla){s{#([0-9]+)}{#$1}ig} # leaving nice mode :-) $logtext=$_; # now add it to @log for parsing later push(@log,"$time\n$author\n$file#$rev;$state\t$logtext"); # delete logtext to save RAM undef $logtext; } } } } # delete @lines to save RAM undef @lines; print "Generating $opt_output\n"; # Sorting the log if($opt_reverse){@log = sort(@log);}else{@log = reverse(sort(@log));} # Adding one entry for better handling push(@log,""); # Open our output file open(CHRONO,">$opt_output") or die "Error: Could not create $opt_output\n"; # Print predefined header if $opt_header isn't set print CHRONO $opt_header || <<"HTMLHEADER"; $opt_title
HTMLHEADER # variable for progression notify my $oldprogression=""; my $progression=""; # now parse our log for(my $length=@log, my $i=0, my @files; $i<$length-1; $i++) { # retrieving our data my ($time,$author,$file,$logtext) = ($log[$i] =~ /(.+?)\n(.+?)\n(.+?)\t(.*)/s); # split $file into dir/file for sorting # not as elegant as Basename but should be more effecient my (undef, $path, $filename) = ($file=~ "((.*)/|)(.*)"); # add it to filelist push(@files,"$path\t$filename"); # and begin parsing next commit if it has the same comment $log[$i+1]=~/.+?\t(.*)/s; if($logtext eq $1){next} # now that one commit is completed last formating begins # if full author name is available, use it if($opt_names{$author}){$author = $opt_names{$author} } # apply better Date/Time format if requested if($opt_extendtime){$time = time2str($opt_extendtime, str2time($time))} # temporary variables my $files="";my $currentdir=(($opt_simple) ? "" : "." );my $class=""; # go through the filelist now foreach (sort (@files)) { # splitting filestring my ($dir,$file,$branch,$rev,$state) = ($_=~/(.*)\t(.*)#(.*)\.(.*);(.*)/); # directory has changed if ($currentdir ne $dir) { # if there already were files add line break if($files && $opt_simple){$files.= ($opt_small ? "\n" : "
\n")} # or close and open table row elsif($files){$files.="\n"} # save our new dir $currentdir = $dir; # print current dir $files.=($opt_simple ? $dir.": " : '['. ($opt_dirs{$dir} ? $opt_dirs{$dir} : $dir).']'); # negate styleclass so we have changing colors $class = ! $class; } # if there are more than one file add a space between them else {$files.=" "} # FIXME reformat, document [ if($opt_webcvs && $state ne "dead") { $_=($dir eq "" ? "" : $dir."/"); if($4==1){$files.="$2*";} else{$files.="$2";} } elsif($5 eq "dead"){$files.="$2"} else{$files.=$file;}; # ] } # now print the formatted commit block print CHRONO "\n". "\n". "
$time by $author
$logtext
\n". "\n". "
". "$files". "

\n\n"; # reset variables to save RAM (needed?) undef $files;undef @files; if(defined($opt_verbose)) { $progression=int($i / ($length-2) * 100)."%\n"; if($oldprogression ne $progression) { $oldprogression=$progression; print $progression; } } } print CHRONO "Generated with cvs2chrono $version\n"; print CHRONO "
"; close(CHRONO); __END__ =head1 NAME cvs2chrono - Create a chronological HTML file from 'cvs log' output =head1 SYNOPSIS cvs2chrono [options] =head1 OPTIONS -b, --bugzilla URL Replace #Number with URL+Number -c, --config FILE Load a perl based config FILE --cvs OPTIONS OPTIONS to pass to cvs -d, --daterange DATE_SPEC CVS Date range, see the -D option of CVS. --dir DIR=NAME --directory DIR=NAME Translate DIR to NAME. Doesn't work with --simple -e, --extendtime TEMPLATE Use TEMPLATE to format time. See Date::Format -h, --help This Help screen. -i, --input FILE Input file to use, renders -d useless. If FILE is "-", STDIN is used. -m, --module Dummy (used in Activitymail). --name ID=NAME Translate Author ID to NAME. --options see --cvs -o, --output FILE Specify an output filename. -p, --prefix Dummy (used in Activitymail). -r, --reverse Reverses output, normal output is from newest to oldest. --simple Create simplier (pre 0.5.0) file list output -s, --small Generates a smaller file list output. Only works with --simple. --title TEXT TEXT to use as html title -V, --verbose Print current parsed file and percentage of html creation. -w, --webcvs URL Specify a URL to use for webcvs links. -? see --help =head1 DESCRIPTION B will parse the output of a CVS log and format it into a chronological sorted html document. =head1 EXAMPLE cvs2chrono -d ">=2002/08/01 UTC" -o "messlog" -w "http://cvs.mess.org:6502/cgi-bin/viewcvs.cgi/" NOTE: CVS outputs only logs for directories that are also in the working directory, so you may want to run 'cvs update' without -P to get logs for deleted directories. =head1 BUGS Logtexts that look like cvs log formating can cause the script to behave badly =cut