[This was not submitted as diffs, and I haven't bothered to make a diff myself, so I'm not sure in any detail what was changed. The change to use CGI.pm I am unconvinced about. What does it buy us? I tend to find extra layers make it hard to see what is going on. Separation of config items from the main file does sound like a good idea. The 'meta information' feature (being able to show the results of "file" on the file) I really don't know about. I sort of can see the point but it seems too distant from CVS to me. Maybe the config item should specify "file" (and any other such program(s)). The change for "?rev=HEAD" seems like a good idea; I've been wanting that for a while. 'use strict' I have no opinion about. -kingdon] From: Martin.Cleaver@BCS.org.uk To: "Martin Cleaver" , Cc: "Info-Cvs" Subject: cvsweb - strict, use CGI with enhancements Date: Wed, 28 Jan 1998 01:08:18 -0000 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_001C_01BD2B89.385EE920" This is a multi-part message in MIME format. ------=_NextPart_000_001C_01BD2B89.385EE920 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit Hi Bill, I have been playing with your cvsweb.pl and find it an incredibly useful tool - well done! (For those on info CVS, cvsweb allows read-only viewing of a CVS repository from a web brower. Very useful!) I took the liberty of making a few modifications and I have several more in mind. The modifications I have made include: use of CGI.pm use of 'use strict' addition of 'meta data' option addition of 'last checkout' option separation of config items from the main file. I was wondering whether you had made enhancements and whether you would be interested in merging the results of our efforts. I have attached my version of cvsweb.pl... Major future enhancements I would like to include: ability to write to the repository from CVS web. If you have done this already that would be a major bonus... Thanks for a fab tool, Martin. -- Martin.Cleaver@BCS.org.uk -- Martin.Cleaver@BCS.org.uk ------=_NextPart_000_001C_01BD2B89.385EE920 Content-Type: application/octet-stream; name="Cvsweb.pl" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="Cvsweb.pl" #!/usr/local/bin/perl -s # # cvsweb - a CGI interface to the CVS tree. # # Note, this is under RCS control in /home/fenner: # $Header: /usr/cvs_base/cvs-web/cvsweb.pl,v 1.2 1998/01/26 19:26:07 = mrjc Exp $ # # Written by Bill Fenner . # # Copyright (C) 1996 # William C. Fenner. 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. # 3. Neither the name of the author nor the names of any co-contributors # may be used to endorse or promote products derived from this = software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY William C. Fenner 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 William C. Fenner, HIS BROTHER = B1FF, 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. # # # To do: # - Parse the ,v yourself instead of using rlog output, since people # sometimes paste rlog output into commit entries, making it impossible # to parse. # - Figure out how to do directory-wide diff's (get named tags = cheaply?) # # package main; use FileHandle; my $cgi; { # disable warnings local ($^W) =3D 0; use CGI; $cgi =3D new CGI; } sub output; init_output(); use strict; use vars qw($title $h1 $intro $shortinstr $ignore $v); use vars qw($cvsroot $rcsbinaries $diricon $texticon $backicon = $tailhtml); { # disable warnings local ($^W) =3D 0; require 'cvswebconfig.pl'; } ##### End configuration section require 'timelocal.pl'; require 'ctime.pl'; my $cvswebversion =3D '$Revision: 1.1.1.1 $ MC'; if (defined($rcsbinaries)) { $ENV{'PATH'} =3D $rcsbinaries . ":" . $ENV{'PATH'}; } my $WHAT =3D '/usr/ccs/bin/what'; my $IDENT =3D '/usr/local/bin/ident'; my $SORT =3D 'sort -u'; my $LDD =3D 'ldd'; my $updir; # Last directory we were in. my $verbose =3D $v; (my $where =3D $cgi->path_info()) =3D~ s|^/||; $where =3D~ s|/$||; my $fullname =3D $cvsroot . '/' . $where; (my $scriptname =3D $cgi->script_name()) =3D~ s|^/?|/|; $scriptname =3D~ s|/$||; my $scriptwhere =3D $scriptname . '/' . $where; $scriptwhere =3D~ s|/$||; my $querystring =3D $ENV{'QUERY_STRING'}; if (!-d $cvsroot) { fatal("500 Internal Error",'cvsroot \''.$cvsroot.'\' not found!'); } if (-d $fullname) { display_directory($fullname); exit 0; } elsif (! -f $fullname . ',v') { fatal("404 Not Found","$where: no such file or directory"); } # Okay, the file exists... if ($cgi->param()) { if ($cgi->param('meta')) { display_meta($fullname, $cgi->param('rev')); exit 0; } =09 if ($cgi->param('rev')) { display_single_revision($fullname); exit 0; } =09 if ($cgi->param('r1')) { display_diff_between_revisions($fullname); exit 0; } } else { display_log($fullname); display_footer(); } exit 0; sub display_header { output "Content-type: text/html\n\n"; output "${title}: /$where\n"; output "\n"; output "\n"; output "

$h1

\n
\n"; if ($where eq '') { output $intro; } else { output $shortinstr; } output "

Current directory: /$where\n"; output "


\n"; } sub display_footer { local ($^W) =3D 0; # disable warnings my $sel =3D ""; my @revorder =3D (); output "\n"; output "This form allows you to request diffs between any two\n"; output "revisions of a file. You may select a symbolic revision\n"; output "name using the selection box or you may type in a numeric\n"; output "revision using the type-in text box.\n"; output "

\n"; output "

\n"; output "Diffs between \n"; output "\n"; output "\n"; output " and \n"; output "\n"; output "\n"; output "
Unidiff
\n"; output "Context diff
\n"; output "\n"; output "
\n"; output "
Created by cvsweb $cvswebversion

\n"; output $tailhtml,"\n"; output "\n"; } sub htmlify { my ($string) =3D @_; $string =3D~ s/&/&/g; $string =3D~ s//>/g; return $string; } sub htlink { my ($name, $where) =3D @_; return "$name\n"; } sub revcmp { my ($rev1, $rev2) =3D @_; my (@r1) =3D split(/\./, $rev1); my (@r2) =3D split(/\./, $rev2); local ($a,$b); while (($a =3D pop(@r1)) && ($b =3D pop(@r2))) { if ($a !=3D $b) { return $a <=3D> $b; } } if (@r1) { return 1; } if (@r2) { return -1; } return 0; } sub fatal { my ($errcode, $errmsg) =3D @_; output "Status: $errcode\n"; output "Content-type: text/html\n"; output "\n"; output "Error\n"; output "Error: $errmsg\n"; exit(1); } sub display_directory { # Something that would be nice to support, although I have no real # good idea of how, would be to get full directory diff's, using # symbolic names (revision numbers would be meaningless). # The problem is finding a list of symbolic names that is common # to all the files in the directory. # display_header(); my @dir; opendir(DIR, $fullname) || fatal("404 Not Found","$where: $!"); @dir =3D readdir(DIR); closedir(DIR); # Using in this manner violates the HTML2.0 spec but # provides the results that I want in most browsers. Another # case of layout desires spooging up HTML. output "\n"; foreach (sort @dir) { if ($_ eq '.') { next; } if ($_ eq '..') { next if ($where eq ''); ($updir =3D $scriptwhere) =3D~ s|[^/]+$||; if (defined($backicon)) { output "3D\"[UP"; } else { output "[UP ]"; } output " ". htlink("Previous Directory", $updir). "
"; } elsif (-d $fullname . "/" . $_) { if (defined($diricon)) { output "3D\"[DIR]\""; } else { output "[DIR]"; } output " ". htlink($_ . "/", $scriptwhere . '/' . $_ . '/'). "
"; } elsif (s/,v$//) { if (defined($texticon)) { output "3D\"[TXT]\""; } else { output "[TXT]"; } output " ". htlink($_, $scriptwhere . '/' . $_).=20 " ". htlink("(last rev)", = $scriptwhere.'/'.$_.'?rev=3DHEAD&content-type=3Dtext/plain'). '
'; } } output "
\n"; output "
Created by cvsweb $cvswebversion

\n"; output $tailhtml."\n"; output "\n"; } sub display_single_revision ($) { my ($fullname) =3D @_; my $rev =3D $cgi->param('rev'); # output ">$rev\n"; my $fh =3D get_rev($fullname, $rev); $| =3D 1; if ($cgi->param('content-type')) { output "Content-type: ".$cgi->param('content-type')."\n"; }=20 if ($cgi->param('content-encoding')) { if ($cgi->param('content-encoding') eq "x-gzip" ) { output "Content-encoding: x-gzip\n\n"; open(GZIP, "|gzip -1 -c"); # need lightweight compression print GZIP <$fh>; close(GZIP); } } else { output "\n"; output <$fh>; } close($fh); } sub get_rev { my ($fullname, $rev) =3D @_; # /home/ncvs/src/sys/netinet/igmp.c,v --> standard output # revision 1.1.1.2 # /* if ($rev eq "HEAD") { $rev =3D ""; } my $fh =3D new FileHandle("co -p$rev '$fullname' 2>&1 |") || fail("500 Internal Error", "Couldn't co: $!"); # /home/ncvs/src/sys/netinet/igmp.c,v --> standard output # revision 1.1.1.2 # /* $_ =3D <$fh>; if (/^$fullname,v\s+-->\s+standard output\s*$/o) { # As expected } else { fatal("500 Internal Error", "Unexpected output from co: $_ ". "(Filename requested wasn't in output)"); } $_ =3D <$fh>; =20 if ($_ =3D~ /^revision\s+$rev\s.*$/) { # As expected } else { unless ($rev eq "") { fatal("500 Internal Error", "Unexpected output from co: $_ ". "(Revision number requested wasn't in output)"); } } return $fh } sub display_meta { my ($fullname, $rev) =3D @_; =20 $| =3D 1; output "Content-type: text/html\n"; output "\n"; =20 output "Meta information for $fullname = rev=3D$rev"; output "

Meta information for $fullname rev=3D$rev

\n"; output "
\n"; # darn, file on solaris can't work on a file on standard input. $ignore =3D <<'EOM';=20 my $fh =3D get_rev($fullname, $rev); output "\nFile information:\n"; output "
";
    open (FILECMD, "|file");
    print FILECMD <$fh>;
    close FILECMD;
    output "
"; EOM my $fh =3D get_rev($fullname, $rev); output "\nCVS/RCS information:\n"; output "
";
    open (IDENT, "|$IDENT");
    print IDENT <$fh>;
    close IDENT;
    output "
"; =20 $fh =3D get_rev($fullname, $rev); output "SCCS information:\n"; output "
";
    open (WHAT, "|$WHAT | $SORT");
    print WHAT <$fh>;
    close WHAT;
    output "
"; $fh =3D get_rev($fullname, $rev); output "LDD information:\n"; output "
";
    open (LDD, "|$LDD");
    print LDD <$fh>;
    close LDD;
    output "
"; output "
\n"; close $fh; } =20 sub display_diff_between_revisions { my ($qs) =3D @_; my $r1 =3D $cgi->param('r1'); my $r2 =3D $cgi->param('r2'); my ($rev1, $rev2); my ($sym1, $sym2); my ($tmp1, $tmp2); my ($diffname, $difftype); my ($f1, $f2); if ($r1 =3D~ (/([^&:]+)(:([^&]+))?/)) { $rev1 =3D $1; $sym1 =3D $3; } if ($rev1 eq 'text') { if (/tr1=3D([^&]+)/) { $rev1 =3D $1; } } if ($r2 =3D~ (/([^&:]+)(:([^&]+))?/)) { $rev2 =3D $1; $sym2 =3D $3; } if ($rev2 eq 'text') { if (/tr2=3D([^&]+)/) { $rev2 =3D $1; } } output "REV1=3D$rev1, REV2=3D$rev2\n"; # if (!($rev1 =3D~ /^[\d\.]+$/) || !($rev2 =3D~ /^[\d\.]+$/)) { # fatal("404 Not Found", # "Malformed query \"$ENV{'QUERY_STRING'}\""); # } # # rev1 and rev2 are now both numeric revisions. # Thus we do a DWIM here and swap them if rev1 is after rev2. # XXX should we warn about the fact that we do this? if (revcmp($rev1,$rev2) > 0) { ($tmp1, $tmp2) =3D ($rev1, $sym1); ($rev1, $sym1) =3D ($rev2, $sym2); ($rev2, $sym2) =3D ($tmp1, $tmp2); } # $difftype =3D "-c"; $diffname =3D "Context diff"; if ($cgi->param('f')) { if ($cgi->param('f') eq 'u') { $difftype =3D '-u'; $diffname =3D "Unidiff"; } } # XXX should this just be text/plain # or should it have an HTML header and then a
	    output "Content-type: text/plain\n\n";
	    open(RCSDIFF, "rcsdiff $difftype -r$rev1 -r$rev2 '$fullname' 2>&1 =
|") ||
		fail("500 Internal Error", "Couldn't rcsdiff: $!");
#
#=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
#RCS file: /home/ncvs/src/sys/netinet/tcp_output.c,v
#retrieving revision 1.16
#retrieving revision 1.17
#diff -c -r1.16 -r1.17
#*** /home/ncvs/src/sys/netinet/tcp_output.c     1995/11/03 22:08:08     =
1.16
#--- /home/ncvs/src/sys/netinet/tcp_output.c     1995/12/05 17:46:35     =
1.17
#
# Ideas:
# - nuke the stderr output if it's what we expect it to be
# - Add "no differences found" if the diff command supplied no output.
#
#*** src/sys/netinet/tcp_output.c     1995/11/03 22:08:08     1.16
#--- src/sys/netinet/tcp_output.c     1995/12/05 17:46:35     1.17 =
RELENG_2_1_0
# (bogus example, but...)
#
	    if ($difftype eq '-u') {
		$f1 =3D '---';
		$f2 =3D '\+\+\+';
	    } else {
		$f1 =3D '\*\*\*';
		$f2 =3D '---';
	    }
	    while () {
		if (m|^$f1 $cvsroot|o) {
		    s|$cvsroot/||o;
		    if ($sym1) {
			chop;
			$_ .=3D " " . $sym1 . "\n";
		    }
		} elsif (m|^$f2 $cvsroot|o) {
		    s|$cvsroot/||o;
		    if ($sym2) {
			chop;
			$_ .=3D " " . $sym2 . "\n";
		    }
		}
		output $_;
	    }
	    close(RCSDIFF);

}


sub display_log {
        my ($fullname) =3D @_;

	local($^W) =3D 0;
	my %revsym;
	my %log;
	my $symnames;
	my %date;
	my @revorder;
	my %symrev;
	my %author;
	my $sel;
=09
	open(RCS, "rlog '$fullname'|") || fatal("500 Internal Error",
						"Failed to spawn rlog");
	while () {
	    output if ($verbose);
	    if ($symnames) {
		if (/^\s+([^:]+):\s+([\d\.]+)/) {
		    $symrev{$1} =3D $2;
		    if ($revsym{$2}) {
			$revsym{$2} .=3D ", ";
		    }
		    $revsym{$2} .=3D $1;
		} else {
		    $symnames =3D 0;
		}
	    } elsif (/^symbolic names/) {
		$symnames =3D 1;
	    } elsif (/^-----/) {
		last;
	    }
	}
# each log entry is of the form:
# ----------------------------
# revision 3.7.1.1
# date: 1995/11/29 22:15:52;  author: fenner;  state: Exp;  lines: +5 -3
# log info
# ----------------------------
	my $yr;
	my $rev;
	logentry:
	while (!/^=3D=3D=3D=3D=3D=3D=3D=3D=3D/) {
	    $_ =3D ;
	    output "R:", $_ if ($verbose);
	    if (/^revision ([\d\.]+)/) {
		$rev =3D $1;
	    } elsif (/^=3D=3D=3D=3D=3D=3D=3D=3D/ || =
/^----------------------------$/) {
		next logentry;
	    } else {
		fatal("500 Internal Error","Error parsing RCS output: $_");
	    }
	    $_ =3D ;
	    output "D:", $_ if ($verbose);
	    if =
(m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+author:\s+(\S+);|) =
{
		$yr =3D $1;
		# damn 2-digit year routines
		if ($yr > 100) {
		    $yr -=3D 1900;
		}
		$date{$rev} =3D timelocal($6,$5,$4,$3,$2 - 1,$yr);
		$author{$rev} =3D $7;
	    } else {
		fatal("500 Internal Error", "Error parsing RCS output: $_");
	    }
	    line:
	    while () {
		output "L:", $_ if ($verbose);
		next line if (/^branches:\s/);
		last line if (/^----------------------------$/ || =
/^=3D=3D=3D=3D=3D=3D=3D=3D=3D/);
		$log{$rev} .=3D $_;
	    }
	    output "E:", $_ if ($verbose);
	}
	close(RCS);
	output "Done reading RCS file\n" if ($verbose);
#
# Sort the revisions into commit-date order.
	@revorder =3D sort {$date{$b} <=3D> $date{$a}} keys %date;
	output "Done sorting revisions\n" if ($verbose);
#
# HEAD is an artificial tag which is simply the highest tag number on =
the main
# branch.  Find it by looking through @revorder; it should at least
# be near the beginning (In fact, it *should* be the first commit listed =
on
# the main branch.)
	my $i;
	revision:
	for ($i =3D 0; $i <=3D $#revorder; $i++) {
	    if ($revorder[$i] =3D~ /^\d+\.\d+$/) {
		if ($revsym{$revorder[$i]}) {
		    $revsym{$revorder[$i]} .=3D ", ";
		}
		$revsym{$revorder[$i]} .=3D "HEAD";
		$symrev{"HEAD"} =3D $revorder[$i];
		last revision;
	    }
	}
	output "Done finding HEAD\n" if ($verbose);
#
# Now that we know all of the revision numbers, we can associate
# absolute revision numbers with all of the symbolic names, and
# pass them to the form so that the same association doesn't have
# to be built then.
#
# should make this a case-insensitive sort
	my ($head, $branch, $regex);
	foreach (sort keys %symrev) {
	    $rev =3D $symrev{$_};
	    if ($rev =3D~ /^(\d+(\.\d+)+)\.0\.(\d+)$/) {
		#
		# A revision number of A.B.0.D really translates into
		# "the highest current revision on branch A.B.D".
		#
		# If there is no branch A.B.D, then it translates into
		# the head A.B .
		#
		# This is pure speculation.
		#
		$head =3D $1;
		$branch =3D $3;
		$regex =3D $head . "." . $branch;
		$regex =3D~ s/\./\./g;
		#             <
		#           \____/
		$rev =3D $head;
	=09
		revision:
		my $r;
		my $rev;
		foreach $r (@revorder) {
		    if ($r =3D~ /^${regex}/) {
			$rev =3D $head . "." . $branch;
			last revision;
		    }
		}
		$revsym{$rev} .=3D ", " if ($revsym{$rev});
		$revsym{$rev} .=3D $_;
	    }
	    $sel .=3D "