#!/usr/bin/perl # # Perl script to create a chronological HTML file from 'cvs log' output # # cvs2chrono Copyright (C) Dorando 2003,2004 # XULChris 2003 # # 0.6.0 / 2004-08-20 / Better parser # 0.5.0 / 2003-08-06 / Complete rewrite, new options, now uses strict # 0.3.6 / 2003-04-24 / Handles branches correctly, for deleted files # 0.3.5 / 2003-04-18 / New file list format # 0.3.1 / 2003-04-16 / Bugzilla links, diff for webcvs, extended time format # 0.3.0 / 2003-04-15 / New parser, generates smaller and better output # 0.2.2 / 2003-04-14 / Can now use a namefile, direct link to revisions # 0.2.1 / 2003-04-14 / Removes Attic/ in displayed dir, smaller output # 0.2.0 / 2003-04-14 / Now XHTML1.1 conform, and takes much less memory # 0.1.0 / 2003-04-13 / 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.6.0'; # loading required modules use Getopt::Long qw(:config gnu_getopt no_ignore_case); 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_extendtime $opt_footer $opt_header $opt_input %opt_names $opt_output $opt_reverse $opt_simple $opt_small $opt_title $opt_verbose $opt_webcvs ); # defining local variables our (@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|C=s' => \$opt_cvs, 'daterange|d=s' => \$opt_daterange, 'dir|directory|D=s' => \%opt_dirs, 'extendtime|e=s' => \$opt_extendtime, 'footer|F=s' => \$opt_footer, 'header|H=s' => \$opt_header, 'help|h|?' => sub {pod2usage(1)}, 'input|i=s' => \$opt_input, 'man|M' => sub {pod2usage(-verbose => 2)}, 'name|N=s' => \%opt_names, 'output|o=s' => \$opt_output, 'reverse|r' => \$opt_reverse, 'simple|S' => \$opt_simple, 'small|s' => \$opt_small, 'title|t=s' => \$opt_title, 'verbose|V' => \$opt_verbose, 'version|v' => sub {print "cvs2chrono $version"; exit}, 'webcvs|w=s' => \$opt_webcvs ) || 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 "can't open $filename: $!"; } else # from cvs log { $opt_cvs||=""; if($opt_daterange){$opt_cvs.='-d"'.$opt_daterange.'"';} open(LOG, "cvs -Q log $opt_cvs |") or die "cannot fork: $!"; } { local $/ = "=============================================================================\n"; while() { # defining local variables my ($rev, $time, $author, $logtext, $state); chomp; local $/ = "\n"; my ($header, @revisions) = split /^----------------------------$/m, $_; # get current file my ($file) = ($header =~ ".*\n.*\nWorking file: (.*)"); # output it if desired if(defined($opt_verbose)){print "Processing $file\n"}; foreach (@revisions) { ($rev, $time, $author, $state, $logtext) = ($_ =~ /.*?\nrevision (.*?)\ndate: (.+?); author: (.+?); state: (.+?);.*?\n(.*)/s); # skip initial imports FIXME make optional? if($rev eq "1.1.1.1"){next;} # remove branch info, not used curently $logtext =~ s/^branches: (.+);//; # removes leading newlines. TODO is there a better way? $logtext =~ s/^\n*//m; # 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"); } } } close(LOG); my $length=@log; if($length == 0){ print "cvs log failed\n"; exit; } 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 if(defined($opt_header)) {print CHRONO $opt_header;} else {print CHRONO <<"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 efficient my (undef, $path, $filename) = ($file=~ "((.*)/|)(.*)"); # if(not $path){$path="."}; # 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} } else{print "Unknown Author: $author\n";$opt_names{$author} = $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; } } } if(defined($opt_footer)) {print CHRONO $opt_footer;} else {print CHRONO "Generated with cvs2chrono $version\n". "
"; } 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 -C --cvs OPTIONS OPTIONS to pass to cvs -d, --daterange DATE_SPEC CVS Date range, see the -D option of CVS. -D --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 -F --footer TEXT Replaces the standard footer -H --header TEXT Replaces the standard header -h, --help This Help screen. -i, --input FILE Input file to use, renders -d useless. If FILE is "-", STDIN is used. -N --name ID=NAME Translate Author ID to NAME. --options see --cvs -o, --output FILE Specify an output filename. -r, --reverse Reverses output, normal output is from newest to oldest. -S --simple Create simplier (pre 0.5.0) file list output -s, --small Generates a smaller file list output. Only works with --simple. -t --title TEXT TEXT to use as html title -v, --version Print version -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