=================================================================== RCS file: /cvs/cvsweb/cvsweb.cgi,v retrieving revision 1.7 retrieving revision 1.38 diff -u -p -r1.7 -r1.38 --- cvsweb/cvsweb.cgi 1997/04/30 18:25:05 1.7 +++ cvsweb/cvsweb.cgi 2000/02/01 14:52:38 1.38 @@ -3,10 +3,37 @@ # cvsweb - a CGI interface to the CVS tree. # # Written by Bill Fenner on his own time. -# Insert BSD copyright here. # +# Copyright (c) 1996-1998 Bill 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. +# +# 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. +# +# $FreeBSD: www/en/cgi/cvsweb.cgi,v 1.37 2000/01/05 15:47:43 phantom Exp $ +# + + #HTTP_USER_AGENT: Mozilla/1.1N (X11; I; SunOS 4.1.3_U1 sun4m) via proxy gateway CERN-HTTPD/3.0 libwww/2.17 -#SERVER_NAME: www.freebsd.org +#SERVER_NAME: www.FreeBSD.org #QUERY_STRING: baz #SCRIPT_FILENAME: /usr/local/www/cgi-bin/env.pl #SERVER_PORT: 80 @@ -23,15 +50,37 @@ #SCRIPT_NAME: /cgi-bin/env.pl #SERVER_SOFTWARE: Apache/1.0.0 #REMOTE_HOST: beta.xerox.com -#SERVER_ADMIN: webmaster@freebsd.org +#SERVER_ADMIN: webmaster@FreeBSD.org # require 'timelocal.pl'; require 'ctime.pl'; $hsty_base = ""; require 'cgi-style.pl'; +#&get_the_source; -$cvsroot = '/home/ncvs'; +%CVSROOT = ( + 'freebsd', '/home/ncvs', + 'learn', '/c/learncvs', + ); + +%CVSROOTdescr = ( + 'freebsd', 'FreeBSD', + 'learn', 'Learn', + ); + +%mirrors = ( + 'Germany', 'http://www.de.FreeBSD.org/cgi/cvsweb.cgi', + 'Spain', 'http://www.es.FreeBSD.org/cgi/cvsweb.cgi', + 'USA, California', 'http://www.FreeBSD.org/cgi/cvsweb.cgi', + 'Japan', 'http://www.jp.FreeBSD.org/cgi/cvsweb.cgi', + ); + +$cvstreedefault = 'freebsd'; +$cvstree = $cvstreedefault; +$cvsroot = $CVSROOT{"$cvstree"} || "/home/ncvs"; + + $intro = " This is a WWW interface to the FreeBSD CVS tree. You can browse the file hierarchy by picking directories @@ -45,11 +94,12 @@ a form at the bottom of the page that allows you to display diffs between arbitrary revisions.

If you would like to use this CGI script on your own web server and -CVS tree, see -the CVSWeb distribution site. +CVS tree, see +the CVSWeb distribution site or the current FreeBSD version.

Please send any suggestions, comments, etc. to -Bill Fenner <fenner@freebsd.org> +Bill Fenner <fenner\@FreeBSD.org> "; $shortinstr = " Click on a directory to enter that directory. Click on a file to display @@ -60,18 +110,13 @@ chance to display diffs between revisions. $verbose = $v; ($where = $ENV{'PATH_INFO'}) =~ s|^/||; $where =~ s|/$||; -$fullname = $cvsroot . '/' . $where; ($scriptname = $ENV{'SCRIPT_NAME'}) =~ s|^/?|/|; $scriptname =~ s|/$||; $scriptwhere = $scriptname . '/' . $where; $scriptwhere =~ s|/$||; -if (!-d $cvsroot) { - &fatal("500 Internal Error",'$CVSROOT not found!'); -} - -if ($q = $ENV{'QUERY_STRING'}) { - foreach (split(/&/, $q)) { +if ($query = $ENV{'QUERY_STRING'}) { + foreach (split(/&/, $query)) { s/%(..)/sprintf("%c", hex($1))/ge; # unquote %-quoted if (/(\S+)=(.*)/) { $input{$1} = $2; @@ -79,7 +124,62 @@ if ($q = $ENV{'QUERY_STRING'}) { $input{$_}++; } } + $query = "?" . $query; } + + +$config = '/usr/local/etc/cvsweb'; +do "$config" if -f $config; + +if ($input{'cvsroot'}) { + if ($CVSROOT{$input{'cvsroot'}}) { + $cvstree = $input{'cvsroot'}; + $cvsroot = $CVSROOT{"$cvstree"}; + } +} +do "$config-$cvstree" if -f "$config-$cvstree"; + +$fullname = $cvsroot . '/' . $where; + +if (!-d $cvsroot) { + &fatal("500 Internal Error",'$CVSROOT not found!

The server on which the CVS tree lives is probably down. Please try again in a few minutes.'); +} + + +{ + local(@foo, $i); + local($scriptname) = $ENV{'SCRIPT_NAME'}; + foreach (sort keys %CVSROOT) { + if (-d $CVSROOT{$_}) { + push(@foo, $_); + } + } + if ($#foo >= 1) { + $intro .= "

\nThis script supports the following CVS trees:\n"; + for($i = 0; $i <= $#foo; $i++) { + $intro .= qq{} . + ($CVSROOTdescr{$foo[$i]} ? + $CVSROOTdescr{$foo[$i]} : $foo[$i]) . qq{} . + ($i == $#foo ? ".\n" : ",\n"); + } + } +} + + +{ + local(@mirrors) = sort keys %mirrors;; + if ($#mirrors >= 0) { + $intro .= "

\nThis script is mirrored in:\n"; + local($m); + for($m = 0; $m <= $#mirrors; $m++) { + $intro .= qq($mirrors[$m]); + $intro .= ',' if $m != $#mirrors; + $intro .= "\n"; + } + } +} + + if (-d $fullname) { opendir(DIR, $fullname) || &fatal("404 Not Found","$where: $!"); @dir = readdir(DIR); @@ -91,33 +191,85 @@ if (-d $fullname) { print &html_header("/$where"); print $shortinstr; } - print "

Current directory: /$where\n"; + print "

"; + print "Current CVS tree: ", + ($CVSROOTdescr{"$cvstree"} ? $CVSROOTdescr{"$cvstree"} : + $cvstree), "
\n"; + print "Current directory: /$where\n"; print "


\n"; # Using in this manner violates the HTML2.0 spec but # provides the results that I want in most browsers. Another # case of layout spooging up HTML. print "\n"; - foreach (sort @dir) { + lookingforattic: + for ($i = 0; $i <= $#dir; $i++) { + if ($dir[$i] eq "Attic") { + last lookingforattic; + } + } + $haveattic = 1 if ($i <= $#dir); + if (!$input{"showattic"} && ($i <= $#dir) && + opendir(DIR, $fullname . "/Attic")) { + splice(@dir, $i, 1, + grep((s|^|Attic/|,!m|/\.|), readdir(DIR))); + closedir(DIR); + } + # Sort without the Attic/ pathname. + foreach (sort {($c=$a)=~s|.*/||;($d=$b)=~s|.*/||;($c cmp $d)} @dir) { if ($_ eq '.') { next; } + # ignore CVS lock and stale NFS files + next if /^#cvs\.|^,|^\.nfs/; + + if (s|^Attic/||) { + $attic = " (in the Attic)"; + } else { + $attic = ""; + } if ($_ eq '..') { next if ($where eq ''); ($updir = $scriptwhere) =~ s|[^/]+$||; print " ", - &link("Previous Directory",$updir), "
"; + &link("Previous Directory",$updir . $query), "
"; # print " ", # &link("Directory-wide diffs", $scriptwhere . '/*'), "
"; } elsif (-d $fullname . "/" . $_) { print " ", - &link($_ . "/", $scriptwhere . '/' . $_ . '/'), "
"; + &link($_ . "/", $scriptwhere . '/' . $_ . '/' . $query), + $attic, "
"; } elsif (s/,v$//) { # TODO: add date/time? How about sorting? print " ", - &link($_, $scriptwhere . '/' . $_), "
"; + &link($_, $scriptwhere . '/' . + ($attic ? "Attic/" : "") . $_ . $query), + $attic, "
"; } } print "
\n"; + if ($input{"only_on_branch"}) { + print "
\n"; + print "Currently showing only branch $input{'only_on_branch'}.\n"; + $input{"only_on_branch"}=""; + foreach $k (keys %input) { + print "\n" if $input{$k}; + } + print "\n"; + print "
\n"; + } + $formwhere = $scriptwhere; + $formwhere =~ s|Attic/?$|| if ($input{"showattic"}); + if ($haveattic) { + print "
\n"; + $input{"showattic"}=!$input{"showattic"}; + foreach $k (keys %input) { + print "\n" if $input{$k}; + } + print "\n"; + print "
\n"; + } print &html_footer; print "\n"; } elsif (-f $fullname . ',v') { @@ -130,10 +282,269 @@ if (-d $fullname) { $input{'r2'}, $input{'tr2'}, $input{'f'}); exit; } +print("going to dolog($fullname)\n") if ($verbose); + &dolog($fullname); +} elsif ($fullname =~ s/\.diff$// && -f $fullname . ",v" && + $input{'r1'} && $input{'r2'}) { + # Allow diffs using the ".diff" extension + # so that browsers that default to the URL + # for a save filename don't save diff's as + # e.g. foo.c + &dodiff($fullname, $input{'r1'}, $input{'tr1'}, + $input{'r2'}, $input{'tr2'}, $input{'f'}); + exit; +} elsif (($newname = $fullname) =~ s|/([^/]+)$|/Attic/$1| && + -f $newname . ",v") { + # The file has been removed and is in the Attic. + # Send a redirect pointing to the file in the Attic. + ($newplace = $scriptwhere) =~ s|/([^/]+)$|/Attic/$1|; + &redirect($newplace); + exit; +} elsif (0 && (@files = &safeglob($fullname . ",v"))) { + print "Content-type: text/plain\n\n"; + print "You matched the following files:\n"; + print join("\n", @files); + # Find the tags from each file + # Display a form offering diffs between said tags +} else { + # Assume it's a module name with a potential path following it. + $xtra = $& if (($module = $where) =~ s|/.*||); + # Is there an indexed version of modules? + if (open(MODULES, "$cvsroot/CVSROOT/modules")) { + while () { + if (/^(\S+)\s+(\S+)/o && $module eq $1 + && -d "${cvsroot}/$2" && $module ne $2) { + &redirect($scriptname . '/' . $2 . $xtra); + } + } + } + &fatal("404 Not Found","$where: no such file or directory"); +} + +sub htmlify { + local($string, $pr) = @_; + + $string =~ s/&/&/g; + $string =~ s//>/g; + + if ($pr) { + $string =~ s!\b((pr[:#]?\s*#?)|((bin|conf|docs|gnu|i386|kern|misc|ports)\/))(\d+)\b!$&!ig; + } + + $string; +} + +sub link { + local($name, $where) = @_; + + "$name\n"; +} + +sub revcmp { + local($rev1, $rev2) = @_; + local(@r1) = split(/\./, $rev1); + local(@r2) = split(/\./, $rev2); + local($a,$b); + + while (($a = shift(@r1)) && ($b = shift(@r2))) { + if ($a != $b) { + return $a <=> $b; + } + } + if (@r1) { return 1; } + if (@r2) { return -1; } + return 0; +} + +sub fatal { + local($errcode, $errmsg) = @_; + print "Status: $errcode\n"; + print &html_header("Error"); +# print "Content-type: text/html\n"; +# print "\n"; +# print "Error\n"; +# print "Error: $errmsg\n"; + print "Error: $errmsg\n"; + print &html_footer; + exit(1); +} + +sub redirect { + local($url) = @_; + print "Status: 301 Moved\n"; + print "Location: $url\n"; + print &html_header("Moved"); +# print "Content-type: text/html\n"; +# print "\n"; +# print "Moved\n"; +# print "This document is located here.\n"; + print "This document is located here.\n"; + print &html_footer; + exit(1); +} + +sub safeglob { + local($filename) = @_; + local($dirname); + local(@results); + + ($dirname = $filename) =~ s|/[^/]+$||; + $filename =~ s|.*/||; + + if (opendir(DIR, $dirname)) { + $glob = $filename; + # transform filename from glob to regex. Deal with: + # [, {, ?, * as glob chars + # make sure to escape all other regex chars + $glob =~ s/([\.\(\)\|\+])/\\$1/g; + $glob =~ s/\*/.*/g; + $glob =~ s/\?/./g; + $glob =~ s/{([^}]+)}/($t = $1) =~ s-,-|-g; "($t)"/eg; + foreach (readdir(DIR)) { + if (/^${glob}$/) { + push(@results, $dirname . "/" .$_); + } + } + } + + @results; +} + +sub checkout { + local($fullname, $rev) = @_; + + open(RCS, "co -p$rev '$fullname' 2>&1 |") || + &fail("500 Internal Error", "Couldn't co: $!"); +# /home/ncvs/src/sys/netinet/igmp.c,v --> standard output +# or +# /home/ncvs/src/sys/netinet/igmp.c,v --> stdout +# revision 1.1.1.2 +# /* + $_ = ; + if (/^(\S+),v\s+-->\s+st(andar)?d ?out(put)?\s*$/o && $1 eq $fullname) { + # As expected + } else { + &fatal("500 Internal Error", + "Unexpected output from co: $_"); + } + $_ = ; + if ($rev eq ".") { + # latest rev requested, don't check + } elsif (/^revision\s+$rev\s*$/) { + # As expected + } else { + &fatal("500 Internal Error", + "Unexpected output from co: $_"); + } + $| = 1; + print "Content-type: text/plain\n\n"; + print ; + close(RCS); +} + +sub dodiff { + local($fullname, $r1, $tr1, $r2, $tr2, $f) = @_; + + if ($r1 =~ /([^:]+)(:(.+))?/) { + $rev1 = $1; + $sym1 = $3; + } + if ($rev1 eq 'text') { + $rev1 = $tr1; + } + if ($r2 =~ /([^:]+)(:(.+))?/) { + $rev2 = $1; + $sym2 = $3; + } + if ($rev2 eq 'text') { + $rev2 = $tr2; + } + if (!($rev1 =~ /^[\d\.]+$/) || !($rev2 =~ /^[\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) = ($rev1, $sym1); + ($rev1, $sym1) = ($rev2, $sym2); + ($rev2, $sym2) = ($tmp1, $tmp2); + } +# +# XXX Putting '-p' here is a personal preference + if ($f eq 'c') { + $difftype = '-p -c'; + $diffname = "Context diff"; + } elsif ($f eq 's') { + $difftype = '--side-by-side --width=164'; + $diffname = "Side by Side"; + } else { + $difftype = '-p -u'; + $diffname = "Unidiff"; + } +# XXX should this just be text/plain +# or should it have an HTML header and then a
+	print "Content-type: text/plain\n\n";
+	open(RCSDIFF, "rcsdiff $difftype -r$rev1 -r$rev2 '$fullname' 2>&1 |") ||
+	    &fail("500 Internal Error", "Couldn't rcsdiff: $!");
+#
+#===================================================================
+#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 = '---';
+	    $f2 = '\+\+\+';
+	} else {
+	    $f1 = '\*\*\*';
+	    $f2 = '---';
+	}
+	while () {
+	    if (m|^$f1 $cvsroot|o) {
+		s|$cvsroot/||o;
+		if ($sym1) {
+		    chop;
+		    $_ .= " " . $sym1 . "\n";
+		}
+	    } elsif (m|^$f2 $cvsroot|o) {
+		s|$cvsroot/||o;
+		if ($sym2) {
+		    chop;
+		    $_ .= " " . $sym2 . "\n";
+		}
+	    }
+	    print $_;
+	}
+	close(RCSDIFF);
+}
+
+sub dolog {
+	local($fullname) = @_;
+	local($curbranch,$symnames);	#...
+
+	print("Going to rlog '$fullname'\n") if ($verbose);
 	open(RCS, "rlog '$fullname'|") || &fatal("500 Internal Error",
 						"Failed to spawn rlog");
 	while () {
 	    print if ($verbose);
+	    if (/^branch:\s+([\d\.]+)/) {
+		$curbranch = $1;
+	    }
 	    if ($symnames) {
 		if (/^\s+([^:]+):\s+([\d\.]+)/) {
 		    $symrev{$1} = $2;
@@ -165,17 +576,26 @@ if (-d $fullname) {
 	logentry:
 	while (!/^=========/) {
 	    $_ = ;
+	    last logentry if (!defined($_));	# EOF
 	    print "R:", $_ if ($verbose);
 	    if (/^revision ([\d\.]+)/) {
 		$rev = $1;
 	    } elsif (/^========/ || /^----------------------------$/) {
 		next logentry;
 	    } else {
-		&fatal("500 Internal Error","Error parsing RCS output: $_");
+		# The rlog output is syntactically ambiguous.  We must
+		# have guessed wrong about where the end of the last log
+		# message was.
+		# Since this is likely to happen when people put rlog output
+		# in their commit messages, don't even bother keeping
+		# these lines since we don't know what revision they go with
+		# any more.
+		next logentry;
+#		&fatal("500 Internal Error","Error parsing RCS output: $_");
 	    }
 	    $_ = ;
 	    print "D:", $_ if ($verbose);
-	    if (m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+author:\s+(\S+);|) {
+	    if (m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+author:\s+(\S+);\s+state:\s+(\S+);|) {
 		$yr = $1;
 		# damn 2-digit year routines
 		if ($yr > 100) {
@@ -183,6 +603,7 @@ if (-d $fullname) {
 		}
 		$date{$rev} = &timelocal($6,$5,$4,$3,$2 - 1,$yr);
 		$author{$rev} = $7;
+		$state{$rev} = $8;
 	    } else {
 		&fatal("500 Internal Error", "Error parsing RCS output: $_");
 	    }
@@ -203,12 +624,13 @@ if (-d $fullname) {
 	print "Done sorting revisions\n" if ($verbose);
 #
 # HEAD is an artificial tag which is simply the highest tag number on the main
-# branch (I think!).  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.)
+# branch, unless there is a branch tag in the RCS file in which case it's the
+# highest revision on that branch.  Find it by looking through @revorder; it
+# is the first commit listed on the appropriate branch.
+	$headrev = $curbranch || "1";
 	revision:
 	for ($i = 0; $i <= $#revorder; $i++) {
-	    if ($revorder[$i] =~ /^\d+\.\d+$/) {
+	    if ($revorder[$i] =~ /^(\S*)\.\d+$/ && $headrev eq $1) {
 		if ($revsym{$revorder[$i]}) {
 		    $revsym{$revorder[$i]} .= ", ";
 		}
@@ -236,8 +658,6 @@ if (-d $fullname) {
 		# If there is no branch A.B.D, then it translates into
 		# the head A.B .
 		#
-		# This is pure speculation.
-		#
 		$head = $1;
 		$branch = $3;
 		$regex = $head . "." . $branch;
@@ -255,16 +675,27 @@ if (-d $fullname) {
 		}
 		$revsym{$rev} .= ", " if ($revsym{$rev});
 		$revsym{$rev} .= $_;
+		if ($rev ne $head) {
+		    $branchpoint{$head} .= ", " if ($branchpoint{$head});
+		    $branchpoint{$head} .= $_;
+		}
 	    }
 	    $sel .= "