#! @PERL@ -w # @configure_input@ # Copyright (c) 2001-2006 by Martin Kammerhofer # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # Convert cvsup(1) logfile to HTML. # @(#)$Id: cvsuplog2cvsweb.pl.in,v 1.27 2006/06/22 10:44:15 martin Exp $ require 5.003 ; use strict ; use constant CONFENV => "CVSWEB_CONVERTERS_CONF" ; # ENV variable use constant CONFNAME => "cvsweb-converters.conf" ; # file basename use constant COMMON => "common" ; # default section in ini-file use Config ; use Config::IniFiles ; use Getopt::Long ; { local $^W = 0 ; eval "use URI::Escape" ; if ($@) { # provide a dummy function if package URI::Escape is not available. sub uri_escape { return $_[0] ; } warn "$0: package URI::Escape is not available\n" ; } } # URL-encode space and all control and hi-bit characters sub url_escape ($) { return uri_escape( shift, "\x00-\x20\x7f-\xff" ) ; } sub lookup_configfile() ; sub read_configfile ($@) ; my $origPATH = $ENV{PATH} || "/bin:/usr/bin:@PREFIX@/bin" ; delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)} ; my ( $cvsweb, $urlsuffix, $branch, $cvsrootdir, $outfile, $perldoc ) = # DO NOT EDIT THE DEFAULTS HERE - use the configfile instead! # (Otherwise you have to start all over when you upgrade this script.) ( "http://cvsweb.FreeBSD.org/", # URL of cvsweb.cgi "", # e.g. "cvsroot=myproject&content-type=text/x-cvsweb-markup" "", # e.g. "MAIN" "", # e.g. "/home/ncvs" "-", "perldoc", ) ; my %optctl = ( "branchtag" => \$branch, "cvsrootdir" => \$cvsrootdir, "cvsweb" => \$cvsweb, "output-file" => \$outfile, "urlsuffix" => \$urlsuffix, ) ; my $prog = $0 ; $prog =~ s+^.*/++ ; # basename $prog =~ s/\.p(er)?l$// ; # truncate '.pl' suffix my $version = '$Id: cvsuplog2cvsweb.pl.in,v 1.27 2006/06/22 10:44:15 martin Exp $' ; # '; $version =~ s/^\s*\$Id: // ; $version =~ s/ \$\s*$// ; sub usage () { print STDERR "usage: $prog [--cvsweb=URL] [--urlsuffix=STR] [--branch=TAG]\n", " [--cvsrootdir=DIR] [--output-file=FILE] cvsup.log\n", " or: $prog --help\n", " or: $prog --version\n" ; exit 64 ; } sub help () { $ENV{PATH} = $origPATH ; { exec $perldoc, $0 } ; # try 'perldoc' in the same directory as perl itself $ENV{PATH} .= ":" . dirname( $Config{perlpath} ) ; { exec 'perldoc', $0 } ; print STDERR "$prog: cannot exec 'perldoc'\n" ; goto &usage ; } sub html_escape ($) { local $_ = shift or die ; s/\&/&/g ; s/\"/"/g ; s/>/>/g ; s/ 0 # more than one filearg ) { usage() ; } help() if $optctl{help} ; if ( $optctl{version} ) { print "$version\n" ; exit 0 ; } usage() if !@ARGV && -t ; # only tty input $cvsrootdir =~ s!/*$!! ; if ( $outfile ne "-" ) { close(STDOUT) or die ; $outfile = $1 if $outfile =~ /^(.*)$/ ; # untaint open( STDOUT, "> $outfile" ) or die "$prog: redirect output to '$outfile': $!.\nStopped" ; } my ( @f, $fname, $htm, $uri ) ; $cvsweb =~ s!/+$!! ; # remove trailing slashes my $cvsweb_suffix = "" ; $cvsweb_suffix = "?only_with_tag=" . $branch if $branch ; $cvsweb_suffix .= ( $cvsweb_suffix ? "&" : "?" ) . $urlsuffix if $urlsuffix ; my $title = "$prog" ; $title .= " $ARGV[0]" if $#ARGV == 0 ; $title = html_escape($title) ; # paranoia print < $title
EndOfHeader

while (<>) {
    @f = split ;
    if (   $f[0] eq 'Edit'
	|| $f[0] eq 'Checkout'
	|| $f[0] eq 'Create'
	|| $f[0] eq 'Delete' )
    {

	# hyperlink the file name
	$fname = $f[1] ;
	$fname =~ s/,v$// ;
	$htm = html_escape($fname) ;                          # be paranoid
	$uri = url_escape("$cvsweb/$fname$cvsweb_suffix") ;
	s!\Q$f[1]\E!$htm! ;
    } elsif ( $f[0] eq 'Add' && $#f >= 1 && $f[1] eq 'delta' ) {

	# make the UTC date more readable
	s{\b(\d{4})\.(\d{2})\.(\d{2})\.(\d{2})\.(\d{2})\.(\d{2})\b}
	{$1-$2-$3 $4:$5:$6} ;

	# hyperlink the revision number to the delta (diff)
	my $committer = html_escape( $f[4] ) ;
	s/\Q$f[4]\E/$committer/ ;                             # be paranoid
	my ( $delta, $prev ) = ( $f[2], $f[2] ) ;
	if ( $delta =~ /^\d+(?:\.\d+)*\.(\d+)$/ ) {
	    my $lsn = $1 ;    # least significant number
	    $lsn -= 1 ;
	    if ($lsn) {       # delta x.y => x.(y+1)
		$prev =~ s/$1$/$lsn/ ;
	    } else {          # delta x.y => x.y.z.1
		$prev =~ s/\.\d+\.\d+$// ;
	    }

	    # link the delta revision
	    $uri =
		url_escape("$cvsweb/$fname") . "#"
	      . url_escape("rev$delta$cvsweb_suffix") ;
	    s!\b\Q$delta\E\b!$delta! ;

	    # link the word "delta" to the patch
	    my $suffix = ( $cvsweb_suffix ? "$cvsweb_suffix&" : "?" ) ;
	    $uri =
	      url_escape("$cvsweb/$fname.diff${suffix}r1=$prev&r2=$delta") ;
	    s!\bdelta\b!delta! ;
	}
    } elsif ( $f[0] eq 'Rsync'
	|| ( $f[0] eq 'Append' && $#f >= 1 && $f[1] eq 'to' ) )
    {
	$htm = html_escape( $f[-1] ) ;
	$uri = url_escape("file:$cvsrootdir/$f[-1]") ;
	s!\Q$f[-1]\E!$htm! ;
    }
} continue {
    print ;
}

print <<'EndOfFooter';
EndOfFooter if ( $outfile ne "-" ) { close(STDOUT) or die ; } exit( $. ? 0 : 1 ) ; # it's an error if no lines have been read # ---------------------------------------------------------------------- # Look for a config file # # try 1. $ENV{CONFENV} (constants CONF* are defined above) # 2. "~/.CONFNAME" (in home directory prefixed with a dot) # and 3. "@PREFIX@/etc/CONFNAME" sub lookup_configfile () { if ( exists $ENV{ +CONFENV } ) { return $ENV{ +CONFENV } ; } elsif ( exists $ENV{HOME} && -e ( "$ENV{HOME}/." . CONFNAME ) ) { return "$ENV{HOME}/." . CONFNAME ; } elsif ( -e "@PREFIX@/etc/" . CONFNAME ) { return "@PREFIX@/etc/" . CONFNAME ; } return undef ; } # read defaults for commandline options from the config file # 1st argument is filename, other arguments are names of config variables sub read_configfile ($@) { my ( $configfile, @param_names ) = @_ ; map { $_ = $1 if /^(.*)/ } (@param_names) ; # untaint my %config ; if ( !tie %config, 'Config::IniFiles', ( -file => $configfile, -default => COMMON ) ) { $" = "\n" ; die "$0: cannot tie to config file '$configfile'\n", "@Config::IniFiles::errors\n" ; } # get my (sub)section(s) my ( @sections, %param_hash ) ; push @sections, $config{ +COMMON } if exists $config{ +COMMON } ; while ( my ( $section, $hashref ) = each %config ) { push @sections, $hashref if $section =~ m!\Q$prog\E!io ; } # read and assign my parameters my $parameter_count = 0 ; foreach my $name (@param_names) { $param_hash{$name} = undef ; # remember parameter names foreach my $section (@sections) { if ( exists $section->{$name} ) { eval qq! \$$name = \$section->{'$name'} ! ; die "$prog: cannot assign to \$$name: $@\n" if $@ ; $parameter_count++ ; } } } print STDERR "$prog: WARNING: no parameters read from '$configfile'\n" unless $parameter_count ; # warn about unrecognized parameters my @unrecognized ; foreach my $section (@sections) { foreach my $parameter ( keys %{$section} ) { push @unrecognized, $parameter unless exists $param_hash{$parameter} ; } } if (@unrecognized) { @unrecognized = sort @unrecognized ; print STDERR "$prog: WARNING: ", "the following parameters are not recognized:\n", "@unrecognized\n" ; } # untie untie %config or die "untie failed" ; } # end of sub "read_configfile" __END__; =head1 NAME cvsuplog2cvsweb - convert a C log file to HTML =head1 SYNOPSIS =over 4 =item . cvsuplog2cvsweb [--cvsweb=I] [--urlsuffix=I] [--branch=I] [--cvsrootdir=I] [--output-file=I] cvsup.log =item . cvsuplog2cvsweb --version =back =head1 DESCRIPTION The cvsuplog2cvsweb program takes a logfile from C and converts it into HTML. Names of changed (added, updated or deleted) files are replaced with hyperlinks to a C CGI script. This means you can click on any of the updated files and see the CVS log (change history) and have access to all the revisions and deltas. (C is written by John Polstra . It is a network distribution package for CVS repositories. The cgi-script C was originally written by Bill Fenner for the FreeBSD project. It allows browsing of CVS-repositories with a HTML-browser. CVS is a popular version control system.) Options may be abbreviated to a unique prefix. The options are as follows: =over 4 =item --cvsweb=I Specify URL of cvsweb.cgi script. =item --urlsuffix=I Specify some extra information for appending to generated URLs. (You should not type a leading C or C<&> character because it will be added automatically.) =item --branch=I Tell C that you are only interested in file revisions on the specified branch. =item --output-file=I Specify the output file. If no output file is specified then standard output is used. =item --cvsrootdir=I Add HTML C links to non-versioned (rsynced or appended) files. The directory I is prepended to the generated C URLs. =item --version Print version information and exit. =back =head1 FILES C looks for a configuration file in three places. =over 4 =item * If the variable C is set in the environment its content is interpreted as the name of the configuration file, otherwise =item * the file F<~/.cvsweb-converters.conf> is examined, and finally =item * F<@PREFIX@/etc/cvsweb-converters.conf> is tried. =back Only the first found file is read. =head1 EXAMPLE Suppose you are running the FreeBSD operating system and want to upgrade your sources from the RELENG_6 branch. You already have a working cvsup config file in F. Since your nearest cvsup mirror is in Germany you use Ccvsup.log> Now you want to know what all this source updates are about and invoke Ccvsuplog.html> Finally open F with your favourite browser! =head1 AUTHOR Martin Kammerhofer =cut # Local Variables: # mode: perl # End: #EOF#