3 # cvsweb - a CGI interface to CVS trees.
5 # Written in their spare time by
6 # Bill Fenner <fenner@FreeBSD.org> (original work)
7 # extended by Henner Zeller <zeller@think.de>,
8 # Henrik Nordström <hno@hem.passagen.se>
9 # Ken Coar <coar@Apache.Org>
10 # Dick Balaska <dick@buckosoft.com>
11 # Jens-Uwe Mager <jum@helios.de>
14 # * Bill Fenners cvsweb.cgi revision 1.28 available from:
15 # http://www.FreeBSD.org/cgi/cvsweb.cgi/www/en/cgi/cvsweb.cgi
17 # Copyright (c) 1996-1998 Bill Fenner
18 # (c) 1998-1999 Henner Zeller
19 # (c) 1999 Henrik Nordström
20 # All rights reserved.
22 # Redistribution and use in source and binary forms, with or without
23 # modification, are permitted provided that the following conditions
25 # 1. Redistributions of source code must retain the above copyright
26 # notice, this list of conditions and the following disclaimer.
27 # 2. Redistributions in binary form must reproduce the above copyright
28 # notice, this list of conditions and the following disclaimer in the
29 # documentation and/or other materials provided with the distribution.
31 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
32 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
33 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
35 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
36 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
37 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
38 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
39 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
40 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 # $Id: cvsweb.cgi,v 1.112 2001/07/24 13:03:16 hzeller Exp $
46 require './pserver-lib.pl';
47 $access{'cvsweb'} || &error($text{'cvsweb_ecannot'});
50 $config $allow_version_select $verbose
51 %CVSROOT %CVSROOTdescr %MIRRORS %DEFAULTVALUE %ICONS %MTYPES
52 %alltags @tabcolors %fileinfo %tags @branchnames %nameprinted
53 %symrev %revsym @allrevisions %date %author @revdisplayorder
54 @revisions %state %difflines %log %branchpoint @revorder
55 $checkoutMagic $doCheckout $scriptname $scriptwhere
56 $where $Browser $nofilelinks $maycompress @stickyvars
57 %input $query $barequery $sortby $bydate $byrev $byauthor
58 $bylog $byfile $hr_default $logsort $cvstree $cvsroot
59 $mimetype $defaultTextPlain $defaultViewable $allow_compress
60 $GZIPBIN $backicon $diricon $fileicon $fullname $newname
61 $cvstreedefault $body_tag $logo $defaulttitle $address
62 $backcolor $long_intro $short_instruction $shortLogLen
63 $show_author $dirtable $tablepadding $columnHeaderColorDefault
64 $columnHeaderColorSorted $hr_breakable $hr_funout $hr_ignwhite
65 $hr_ignkeysubst $diffcolorHeading $diffcolorEmpty $diffcolorRemove
66 $diffcolorChange $diffcolorAdd $diffcolorDarkChange $difffontface
67 $difffontsize $inputTextSize $mime_types $allow_annotate
68 $allow_markup $use_java_script $open_extern_window
69 $extern_window_width $extern_window_height $edit_option_form
70 $checkout_magic $show_subdir_lastmod $show_log_in_markup $v
71 $navigationHeaderColor $tableBorderColor $markupLogColor
72 $tabstop $state $annTable $sel $curbranch @HideModules @DissallowRead
73 $module $use_descriptions %descriptions @mytz $dwhere $moddate
74 $use_moddate $has_zlib $gzip_open
77 ##### prototype declarations ########
78 sub printDiffSelect($);
79 sub findLastModifiedSubdirs(@);
81 sub spacedHtmlText($);
87 sub getMimeTypeFromSuffix($);
90 sub cvswebMarkup($$$);
97 sub flush_diff_rows ($$$$);
98 sub human_readable_diff($);
99 sub navigateHeader ($$$$$);
100 sub plural_write ($$);
101 sub readableTime ($$);
102 sub clickablePath($$);
106 sub download_url($$$);
107 sub download_link($$$$);
114 sub forbidden_module($);
115 sub forbidden_file($);
116 sub checkForbidden($@);
122 ##### Start of Configuration Area ########
124 # User configuration is stored in
125 $config = "./cvsweb.conf";
127 # == Configuration defaults ==
128 # Defaults for configuration variables that shouldn't need
130 $allow_version_select = 1;
132 ##### End of Configuration Area ########
134 ######## Configuration variables #########
135 # These are defined to allow checking with perl -cw
136 %CVSROOT = %MIRRORS = %DEFAULTVALUE = %ICONS = %MTYPES =
137 %tags = %alltags = @tabcolors = %fileinfo = ();
138 $cvstreedefault = $body_tag = $logo = $defaulttitle = $address =
139 $backcolor = $long_intro = $short_instruction = $shortLogLen =
140 $show_author = $dirtable = $tablepadding = $columnHeaderColorDefault =
141 $columnHeaderColorSorted = $hr_breakable = $hr_funout = $hr_ignwhite =
142 $hr_ignkeysubst = $diffcolorHeading = $diffcolorEmpty = $diffcolorRemove =
143 $diffcolorChange = $diffcolorAdd = $diffcolorDarkChange = $difffontface =
144 $difffontsize = $inputTextSize = $mime_types = $allow_annotate =
145 $allow_markup = $use_java_script = $open_extern_window =
146 $extern_window_width = $extern_window_height = $edit_option_form =
147 $checkout_magic = $show_subdir_lastmod = $show_log_in_markup = $v =
148 $navigationHeaderColor = $tableBorderColor = $markupLogColor =
149 $tabstop = $use_moddate = $moddate = $gzip_open = undef;
151 ##### End of configuration variables #####
156 # Check if the zlib C library interface is installed, and if yes
157 # we can avoid using the extra gzip process.
159 require Compress::Zlib;
164 $checkoutMagic = "~checkout~";
165 $where = defined($ENV{'PATH_INFO'}) ? $ENV{'PATH_INFO'} : "";
166 $doCheckout = ($where =~ /^\/$checkoutMagic/);
167 $where =~ s|^/($checkoutMagic)?||;
170 ($scriptname = $ENV{'SCRIPT_NAME'}) =~ s|^/?|/|;
171 $scriptname =~ s|/+$||;
173 $scriptwhere = $scriptname . '/' . urlencode($where);
176 $scriptwhere = $scriptname;
178 $scriptwhere =~ s|/+$||;
180 # in lynx, it it very annoying to have two links
181 # per file, so disable the link at the icon
183 $Browser = $ENV{'HTTP_USER_AGENT'};
184 $nofilelinks = ($Browser =~ m'^Lynx/');
186 # newer browsers accept gzip content encoding
187 # and state this in a header
188 # (netscape did always but didn't state it)
189 # It has been reported that these
190 # braindamaged MS-Internet Exploders claim that they
191 # accept gzip .. but don't in fact and
192 # display garbage then :-/
193 # Turn off gzip if running under mod_perl and no zlib is available,
194 # piping does not work as expected inside the server.
195 $maycompress = (((defined($ENV{'HTTP_ACCEPT_ENCODING'})
196 && $ENV{'HTTP_ACCEPT_ENCODING'} =~ m|gzip|)
197 || $Browser =~ m%^Mozilla/3%)
198 && ($Browser !~ m/MSIE/)
199 && !(defined($ENV{'MOD_PERL'}) && !$has_zlib));
202 # put here the variables we need in order
203 # to hold our state - they will be added (with
204 # their current value) to any link/query string
206 @stickyvars = qw(cvsroot hideattic sortby logsort f only_with_tag);
212 &fatal("500 Internal Error",
213 'Configuration not found. Set the variable <code>$config</code> '
214 . 'in cvsweb.cgi, or the environment variable '
215 . '<code>CVSWEB_CONFIG</code>, to your <b>cvsweb.conf</b> '
216 . 'configuration file first.');
220 if ($query = $ENV{'QUERY_STRING'}) {
221 foreach (split(/&/, $query)) {
223 s/%(..)/sprintf("%c", hex($1))/ge; # unquote %-quoted
225 $input{$1} = $2 if ($2 ne "");
233 # For backwards compability, set only_with_tag to only_on_branch if set.
234 $input{only_with_tag} = $input{only_on_branch}
235 if (defined($input{only_on_branch}));
237 foreach (keys %DEFAULTVALUE)
239 # replace not given parameters with the default parameters
240 if (!defined($input{$_}) || $input{$_} eq "") {
241 # Empty Checkboxes in forms return -- nothing. So we define a helper
242 # variable in these forms (copt) which indicates that we just set
243 # parameters with a checkbox
244 if (!defined($input{"copt"})) {
245 # 'copt' isn't defined --> empty input is not the result
246 # of empty input checkbox --> set default
247 $input{$_} = $DEFAULTVALUE{$_} if (defined($DEFAULTVALUE{$_}));
250 # 'copt' is defined -> the result of empty input checkbox
251 # -> set to zero (disable) if default is a boolean (0|1).
253 if (defined($DEFAULTVALUE{$_})
254 && ($DEFAULTVALUE{$_} eq "0" || $DEFAULTVALUE{$_} eq "1"));
260 foreach (@stickyvars) {
261 # construct a query string with the sticky non default parameters set
262 if (defined($input{$_}) && ($input{$_} ne "") &&
263 (!defined($DEFAULTVALUE{$_}) || $input{$_} ne $DEFAULTVALUE{$_})) {
265 $barequery = $barequery . "&";
267 my $thisval = urlencode($_) . "=" . urlencode($input{$_});
268 $barequery .= $thisval;
271 # is there any query ?
273 $query = "?$barequery";
274 $barequery = "&" . $barequery;
280 # get actual parameters
281 $sortby = $input{"sortby"};
287 if ($sortby eq "date") {
290 elsif ($sortby eq "rev") {
293 elsif ($sortby eq "author") {
296 elsif ($sortby eq "log") {
303 $hr_default = $input{'f'} eq 'h';
305 $logsort = $input{"logsort"};
309 if (!defined($CVSROOT{$cvstreedefault})) {
310 &fatal("500 Internal Error",
311 "<code>\$cvstreedefault</code> points to a repository ($cvstreedefault)"
312 . "not defined in <code>%CVSROOT</code> "
313 . "(edit your configuration file $config)");
315 $cvstree = $cvstreedefault;
316 $cvsroot = $CVSROOT{"$cvstree"};
318 # alternate CVS-Tree, configured in cvsweb.conf
319 if ($input{'cvsroot'}) {
320 if ($CVSROOT{$input{'cvsroot'}}) {
321 $cvstree = $input{'cvsroot'};
322 $cvsroot = $CVSROOT{"$cvstree"};
326 # create icons out of description
327 foreach my $k (keys %ICONS) {
329 my ($itxt,$ipath,$iwidth,$iheight) = @{$ICONS{$k}};
331 $ {"${k}icon"} = "<IMG SRC=\"$ipath\" ALT=\"$itxt\" BORDER=\"0\" WIDTH=\"$iwidth\" HEIGHT=\"$iheight\">";
334 $ {"${k}icon"} = $itxt;
338 # Do some special configuration for cvstrees
339 do "$config-$cvstree" if (-f "$config-$cvstree");
341 $fullname = $cvsroot . '/' . $where;
342 $mimetype = &getMimeTypeFromSuffix ($fullname);
343 $defaultTextPlain = ($mimetype eq "text/plain");
344 $defaultViewable = $allow_markup && viewable($mimetype);
346 # search for GZIP if compression allowed
347 # We've to find out if the GZIP-binary exists .. otherwise
348 # ge get an Internal Server Error if we try to pipe the
349 # output through the nonexistent gzip ..
350 # any more elegant ways to prevent this are welcome!
351 if ($allow_compress && $maycompress && !$has_zlib) {
352 foreach (split(/:/, $ENV{PATH})) {
354 $GZIPBIN = "$_/gzip";
362 # ensure, that directories always end with (exactly) one '/'
363 # to allow relative URL's. If they're not, make a credirect.
365 my $pathinfo = defined($ENV{'PATH_INFO'}) ? $ENV{'PATH_INFO'} : "";
366 if (!($pathinfo =~ m|/$|) || ($pathinfo =~ m |/{2,}$|)) {
367 credirect ($scriptwhere . '/' . $query);
376 &fatal("500 Internal Error",'$CVSROOT not found!<P>The server on which the CVS tree lives is probably down. Please try again in a few minutes.');
380 # See if the module is in our forbidden list.
382 $where =~ m:([^/]*):;
384 if ($module && &forbidden_module($module)) {
385 &fatal("403 Forbidden", "Access to $where forbidden.");
387 ##############################
389 ###############################
390 elsif (-d $fullname) {
391 my $dh = do {local(*DH);};
392 opendir($dh, $fullname) || &fatal("404 Not Found","$where: $!");
393 my @dir = readdir($dh);
395 my @subLevelFiles = findLastModifiedSubdirs(@dir)
396 if ($show_subdir_lastmod);
397 getDirLogs($cvsroot,$where,@subLevelFiles);
400 html_header("$defaulttitle");
404 html_header("$where");
405 print $short_instruction;
409 if (($use_descriptions) && open (DESC, "<$cvsroot/CVSROOT/descriptions")) {
412 my ($dir,$description) = /(\S+)\s+(.*)/;
413 $descriptions{$dir} = $description;
417 print "<P><a name=\"dirlist\"></a>\n";
418 # give direct access to dirs
424 print "<p>Current directory: <b>", &clickablePath($where,0), "</b>\n";
426 print "<P>Current tag: <B>", $input{only_with_tag}, "</b>\n" if
427 $input{only_with_tag};
432 print "<HR NOSHADE>\n";
433 # Using <MENU> in this manner violates the HTML2.0 spec but
434 # provides the results that I want in most browsers. Another
435 # case of layout spooging up HTML.
439 if (defined($tableBorderColor)) {
440 # Can't this be done by defining the border for the inner table?
441 print "<table border=0 cellpadding=0 width=\"100%\"><tr><td bgcolor=\"$tableBorderColor\">";
443 print "<table width=\"100%\" border=0 cellspacing=1 cellpadding=$tablepadding>\n";
445 print "<tr><th align=left bgcolor=\"" . (($byfile) ?
446 $columnHeaderColorSorted :
447 $columnHeaderColorDefault) . "\">";
448 print "<a href=\"./" . &toggleQuery("sortby","file") .
449 "#dirlist\">" if (!$byfile);
451 print "</a>" if (!$byfile);
453 # do not display the other column-headers, if we do not have any files
454 # with revision information:
455 if (scalar(%fileinfo)) {
457 print "<th align=left bgcolor=\"" . (($byrev) ?
458 $columnHeaderColorSorted :
459 $columnHeaderColorDefault) . "\">";
460 print "<a href=\"./" . &toggleQuery ("sortby","rev") .
461 "#dirlist\">" if (!$byrev);
463 print "</a>" if (!$byrev);
466 print "<th align=left bgcolor=\"" . (($bydate) ?
467 $columnHeaderColorSorted :
468 $columnHeaderColorDefault) . "\">";
469 print "<a href=\"./" . &toggleQuery ("sortby","date") .
470 "#dirlist\">" if (!$bydate);
472 print "</a>" if (!$bydate);
476 print "<th align=left bgcolor=\"" . (($byauthor) ?
477 $columnHeaderColorSorted :
478 $columnHeaderColorDefault) . "\">";
479 print "<a href=\"./" . &toggleQuery ("sortby","author") .
480 "#dirlist\">" if (!$byauthor);
482 print "</a>" if (!$byauthor);
486 print "<th align=left bgcolor=\"" . (($bylog) ?
487 $columnHeaderColorSorted :
488 $columnHeaderColorDefault) . "\">";
489 print "<a href=\"./", toggleQuery("sortby","log"), "#dirlist\">" if (!$bylog);
490 print "Last log entry";
491 print "</a>" if (!$bylog);
494 elsif ($use_descriptions) {
495 print "<th align=left bgcolor=\"". $columnHeaderColorDefault . "\">";
508 for ($i = 0; $i <= $#dir; $i++) {
509 if ($dir[$i] eq "Attic") {
510 last lookingforattic;
513 if (!$input{'hideattic'} && ($i <= $#dir) &&
514 opendir($dh, $fullname . "/Attic")) {
516 grep((s|^|Attic/|,!m|/\.|), readdir($dh)));
520 my $hideAtticToggleLink = "<a href=\"./" .
521 &toggleQuery ("hideattic") .
522 "#dirlist\">[Hide]</a>" if (!$input{'hideattic'});
524 # Sort without the Attic/ pathname.
525 # place directories first
533 foreach (sort { &fileSortCmp } @dir) {
537 # ignore CVS lock and stale NFS files
538 next if (/^#cvs\.|^,|^\.nfs/);
540 # Check whether to show the CVSROOT path
541 next if ($input{'hidecvsroot'} && ($_ eq 'CVSROOT'));
543 # Check whether the module is in the restricted list
544 next if ($_ && &forbidden_module($_));
546 # Ignore non-readable files
547 next if ($input{'hidenonreadable'} && !(-r "$fullname/$_"));
550 $attic = " (in the Attic) " . $hideAtticToggleLink;
556 if ($_ eq '..' || -d "$fullname/$_") {
557 next if ($_ eq '..' && $where eq '/');
558 my ($rev,$date,$log,$author,$filename);
559 ($rev,$date,$log,$author,$filename) = @{$fileinfo{$_}}
560 if (defined($fileinfo{$_}));
561 print "<tr bgcolor=\"" . @tabcolors[$dirrow%2] . "\"><td>" if ($dirtable);
563 $url = "../" . $query;
568 print &link($backicon,$url);
570 print " ", &link("Previous Directory",$url);
573 $url = urlencode($_) . '/' . $query;
574 print "<A NAME=\"$_\"></A>";
579 print &link($diricon,$url);
581 print " ", &link($_ . "/", $url), $attic;
583 print " <a href=\"./" .
584 &toggleQuery ("hideattic") .
585 "#dirlist\">[Don't hide]</a>";
588 # Show last change in dir
590 print "</td><td> </td><td> " if ($dirtable);
592 print " <i>" . readableTime(time() - $date,0) . "</i>";
595 print "</td><td> " if ($dirtable);
598 print "</td><td> " if ($dirtable);
599 $filename =~ s%^[^/]+/%%;
600 print "$filename/$rev";
601 print "<BR>" if ($dirtable);
603 print " <font size=-1>"
604 . &htmlify(substr($log,0,$shortLogLen));
605 if (length $log > 80) {
612 my ($dwhere) = ($where ne "/" ? $where : "") . $_;
613 if ($use_descriptions && defined $descriptions{$dwhere}) {
614 print "<TD COLSPAN=" . ($infocols-1) . "> " if $dirtable;
615 print $descriptions{$dwhere};
616 } elsif ($dirtable && $infocols > 1) {
617 # close the row with the appropriate number of
618 # columns, so that the vertical seperators are visible
619 my($cols) = $infocols;
621 print "</td><td> ";
627 print "</td></tr>\n";
635 $fileurl = ($attic ? "Attic/" : "") . urlencode($_);
636 $url = $fileurl . $query;
642 next if (!defined($fileinfo{$_}));
643 ($rev,$date,$log,$author) = @{$fileinfo{$_}};
645 print "<tr bgcolor=\"" . @tabcolors[$dirrow%2] . "\"><td>" if ($dirtable);
646 print "<A NAME=\"$_\"></A>";
651 print &link($fileicon,$url);
653 print " ", &link($_, $url), $attic;
654 print "</td><td> " if ($dirtable);
655 download_link($fileurl,
657 $defaultViewable ? "text/x-cvsweb-markup" : undef);
658 print "</td><td> " if ($dirtable);
660 print " <i>" . readableTime(time() - $date,0) . "</i>";
663 print "</td><td> " if ($dirtable);
666 print "</td><td> " if ($dirtable);
668 print " <font size=-1>" . &htmlify(substr($log,0,$shortLogLen));
669 if (length $log > 80) {
674 print "</td>" if ($dirtable);
675 print (($dirtable) ? "</tr>" : "<br>");
680 if ($dirtable && defined($tableBorderColor)) {
681 print "</td></tr></table>";
683 print "". ($dirtable == 1) ? "</table>" : "</menu>" . "\n";
685 if ($filesexists && !$filesfound) {
686 print "<P><B>NOTE:</B> There are $filesexists files, but none matches the current tag ($input{only_with_tag})\n";
688 if ($input{only_with_tag} && (!%tags || !$tags{$input{only_with_tag}})) {
692 || $input{only_with_tag}
694 || defined($input{"options"})) {
695 print "<hr size=1 NOSHADE>";
698 if (scalar %tags || $input{only_with_tag}) {
699 print "<FORM METHOD=\"GET\" ACTION=\"./\">\n";
700 foreach my $var (@stickyvars) {
701 print "<INPUT TYPE=HIDDEN NAME=\"$var\" VALUE=\"$input{$var}\">\n"
702 if (defined($input{$var})
703 && (!defined($DEFAULTVALUE{$var})
704 || $input{$var} ne $DEFAULTVALUE{$var})
705 && $input{$var} ne ""
706 && $var ne "only_with_tag");
708 print "Show only files with tag:\n";
709 print "<SELECT NAME=only_with_tag";
710 print " onchange=\"submit()\"" if ($use_java_script);
712 print "<OPTION VALUE=\"\">All tags / default branch\n";
713 foreach my $tag (reverse sort { lc $a cmp lc $b } keys %tags) {
714 print "<OPTION",defined($input{only_with_tag}) &&
715 $input{only_with_tag} eq $tag ? " SELECTED":"",
719 print "<INPUT TYPE=SUBMIT VALUE=\"Go\">\n";
722 my $formwhere = $scriptwhere;
723 $formwhere =~ s|Attic/?$|| if ($input{'hideattic'});
725 if ($edit_option_form || defined($input{"options"})) {
726 print "<FORM METHOD=\"GET\" ACTION=\"${formwhere}\">\n";
727 print "<INPUT TYPE=HIDDEN NAME=\"copt\" VALUE=\"1\">\n";
728 if ($cvstree ne $cvstreedefault) {
729 print "<INPUT TYPE=HIDDEN NAME=\"cvsroot\" VALUE=\"$cvstree\">\n";
731 print "<center><table cellpadding=0 cellspacing=0>";
732 print "<tr bgcolor=\"$columnHeaderColorDefault\"><th colspan=2>Preferences</th></tr>";
733 print "<tr><td>Sort files by <SELECT name=\"sortby\">";
734 print "<OPTION VALUE=\"\">File";
735 print "<OPTION",$bydate ? " SELECTED" : ""," VALUE=date>Age";
736 print "<OPTION",$byauthor ? " SELECTED" : ""," VALUE=author>Author"
738 print "<OPTION",$byrev ? " SELECTED" : ""," VALUE=rev>Revision";
739 print "<OPTION",$bylog ? " SELECTED" : ""," VALUE=log>Log message";
740 print "</SELECT></td>";
741 print "<td>revisions by: \n";
742 print "<SELECT NAME=logsort>\n";
743 print "<OPTION VALUE=cvs",$logsort eq "cvs" ? " SELECTED" : "", ">Not sorted";
744 print "<OPTION VALUE=date",$logsort eq "date" ? " SELECTED" : "", ">Commit date";
745 print "<OPTION VALUE=rev",$logsort eq "rev" ? " SELECTED" : "", ">Revision";
746 print "</SELECT></td></tr>";
747 print "<tr><td>Diff format: ";
750 print "<td>Show Attic files: ";
751 print "<INPUT NAME=hideattic TYPE=CHECKBOX", $input{'hideattic'}?" CHECKED":"",
753 print "<tr><td align=center colspan=2><input type=submit value=\"Change Options\">";
754 print "</td></tr></table></center></FORM>\n";
757 print "</BODY></HTML>\n";
760 ###############################
762 ###############################
763 elsif (-f $fullname . ',v') {
764 if (defined($input{'rev'}) || $doCheckout) {
765 &doCheckout($fullname, $input{'rev'});
769 if (defined($input{'annotate'}) && $allow_annotate) {
770 &doAnnotate($input{'annotate'});
774 if (defined($input{'r1'}) && defined($input{'r2'})) {
775 &doDiff($fullname, $input{'r1'}, $input{'tr1'},
776 $input{'r2'}, $input{'tr2'}, $input{'f'});
780 print("going to dolog($fullname)\n") if ($verbose);
782 ##############################
784 ##############################
786 elsif ($fullname =~ s/\.diff$// && -f $fullname . ",v" &&
787 $input{'r1'} && $input{'r2'}) {
789 # $where-diff-removal if 'cvs rdiff' is used
790 # .. but 'cvs rdiff'doesn't support some options
791 # rcsdiff does (-w and -p), so it is disabled
792 # $where =~ s/\.diff$//;
794 # Allow diffs using the ".diff" extension
795 # so that browsers that default to the URL
796 # for a save filename don't save diff's as
798 &doDiff($fullname, $input{'r1'}, $input{'tr1'},
799 $input{'r2'}, $input{'tr2'}, $input{'f'});
803 elsif (($newname = $fullname) =~ s|/([^/]+)$|/Attic/$1| &&
804 -f $newname . ",v") {
805 # The file has been removed and is in the Attic.
806 # Send a credirect pointing to the file in the Attic.
807 (my $newplace = $scriptwhere) =~ s|/([^/]+)$|/Attic/$1|;
808 &credirect($newplace . "?" . $ENV{QUERY_STRING});
811 elsif (0 && (my @files = &safeglob($fullname . ",v"))) {
812 http_header("text/plain");
813 print "You matched the following files:\n";
814 print join("\n", @files);
815 # Find the tags from each file
816 # Display a form offering diffs between said tags
819 my $fh = do {local(*FH);};
821 # Assume it's a module name with a potential path following it.
822 $xtra = $& if (($module = $where) =~ s|/.*||);
823 # Is there an indexed version of modules?
824 if (open($fh, "$cvsroot/CVSROOT/modules")) {
826 if (/^(\S+)\s+(\S+)/o && $module eq $1
827 && -d "${cvsroot}/$2" && $module ne $2) {
828 &credirect($scriptname . '/' . $2 . $xtra);
832 &fatal("404 Not Found","$where: no such file or directory");
838 sub printDiffSelect($) {
839 my ($use_java_script) = @_;
840 $use_java_script = 0 if (!defined($use_java_script));
841 my ($f) = $input{'f'};
842 print "<SELECT NAME=\"f\"";
843 print " onchange=\"submit()\"" if ($use_java_script);
845 print "<OPTION VALUE=h",$f eq "h" ? " SELECTED" : "", ">Colored Diff";
846 print "<OPTION VALUE=H",$f eq "H" ? " SELECTED" : "", ">Long Colored Diff";
847 print "<OPTION VALUE=u",$f eq "u" ? " SELECTED" : "", ">Unidiff";
848 print "<OPTION VALUE=c",$f eq "c" ? " SELECTED" : "", ">Context Diff";
849 #print "<OPTION VALUE=s",$f eq "s" ? " SELECTED" : "", ">Side by Side";
853 sub findLastModifiedSubdirs(@) {
855 my ($dirname, @files);
857 foreach $dirname (@dirs) {
858 next if ($dirname eq ".");
859 next if ($dirname eq "..");
860 my ($dir) = "$fullname/$dirname";
863 my ($lastmod) = undef;
864 my ($lastmodtime) = undef;
865 my $dh = do {local(*DH);};
867 opendir($dh,$dir) || next;
868 my (@filenames) = readdir($dh);
871 foreach my $filename (@filenames) {
872 $filename = "$dirname/$filename";
873 my ($file) = "$fullname/$filename";
874 next if ($filename !~ /,v$/ || !-f $file);
875 $filename =~ s/,v$//;
876 my $modtime = -M $file;
877 if (!defined($lastmod) || $modtime < $lastmodtime) {
878 $lastmod = $filename;
879 $lastmodtime = $modtime;
882 push(@files, $lastmod) if (defined($lastmod));
890 # Special Characters; RFC 1866
891 $string =~ s/&/&/g;
892 $string =~ s/\"/"/g;
893 $string =~ s/</</g;
894 $string =~ s/>/>/g;
896 # get URL's as link ..
897 $string =~ s§(http|ftp|https)(://[-a-zA-Z0-9%.~:_/]+)([?&]([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*§<A HREF="$1$2$3">$1$2$3</A>§;
898 # get e-mails as link
899 $string =~ s§([-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})§<A HREF="mailto:$1">$1</A>§;
904 sub spacedHtmlText($) {
907 # Cut trailing spaces
911 $string =~ s/\t+/' ' x (length($&) * $tabstop - length($`) % $tabstop)/e
912 if (defined($tabstop));
914 # replace <tab> and <space> (§ is to protect us from htmlify)
915 # gzip can make excellent use of this repeating pattern :-)
916 $string =~ s/§/§%/g; #protect our & substitute
918 # make every other space 'breakable'
919 $string =~ s/ / §nbsp; §nbsp; §nbsp; §nbsp;/g; # <tab>
920 $string =~ s/ / §nbsp;/g; # 2 * <space>
921 # leave single space as it is
924 $string =~ s/ /§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;/g;
925 $string =~ s/ /§nbsp;/g;
928 $string = htmlify($string);
931 $string =~ s/§([^%])/&$1/g;
932 $string =~ s/§%/§/g;
938 my($name, $where) = @_;
940 return "<A HREF=\"$where\">$name</A>\n";
944 my($rev1, $rev2) = @_;
945 my(@r1) = split(/\./, $rev1);
946 my(@r2) = split(/\./, $rev2);
949 while (($a = shift(@r1)) && ($b = shift(@r2))) {
954 if (@r1) { return 1; }
955 if (@r2) { return -1; }
960 my($errcode, $errmsg) = @_;
961 if (defined($ENV{'MOD_PERL'})) {
962 Apache->request->status((split(/ /, $errcode))[0]);
965 print "Status: $errcode\n";
967 html_header("Error");
968 print "Error: $errmsg\n";
975 if (defined($ENV{'MOD_PERL'})) {
976 Apache->request->status(301);
977 Apache->request->header_out(Location => $url);
980 print "Status: 301 Moved\r\n";
981 print "Location: $url\r\n";
983 html_header("Moved");
984 print "This document is located <A HREF=$url>here</A>.\n";
993 my $dh = do {local(*DH);};
995 ($dirname = $filename) =~ s|/[^/]+$||;
996 $filename =~ s|.*/||;
998 if (opendir($dh, $dirname)) {
999 my $glob = $filename;
1001 # transform filename from glob to regex. Deal with:
1002 # [, {, ?, * as glob chars
1003 # make sure to escape all other regex chars
1004 $glob =~ s/([\.\(\)\|\+])/\\$1/g;
1007 $glob =~ s/{([^}]+)}/($t = $1) =~ s-,-|-g; "($t)"/eg;
1008 foreach (readdir($dh)) {
1010 push(@results, $dirname . "/" .$_);
1019 sub getMimeTypeFromSuffix($) {
1020 my ($fullname) = @_;
1021 my ($mimetype, $suffix);
1022 my $fh = do {local(*FH);};
1024 ($suffix = $fullname) =~ s/^.*\.([^.]*)$/$1/;
1025 $mimetype = $MTYPES{$suffix};
1026 $mimetype = $MTYPES{'*'} if (!$mimetype);
1028 if (!$mimetype && -f $mime_types) {
1029 # okey, this is something special - search the
1030 # mime.types database
1031 open ($fh, "<$mime_types");
1033 if ($_ =~ /^\s*(\S+\/\S+).*\b$suffix\b/) {
1041 # okey, didn't find anything useful ..
1042 if (!($mimetype =~ /\S\/\S/)) {
1043 $mimetype = "text/plain";
1048 ###############################
1050 ###############################
1051 sub doAnnotate ($$) {
1054 my ($pathname, $filename);
1055 my $reader = do {local(*FH);};
1056 my $writer = do {local(*FH);};
1058 # make sure the revisions are wellformed, for security
1060 if (!($rev =~ /^[\d\.]+$/)) {
1061 &fatal("404 Not Found",
1062 "Malformed query \"$ENV{'QUERY_STRING'}\"");
1065 if (&forbidden_file($fullname)) {
1066 &fatal("403 Forbidden", "Access forbidden. This file is mentioned in \@DissallowRead");
1070 ($pathname = $where) =~ s/(Attic\/)?[^\/]*$//;
1071 ($filename = $where) =~ s/^.*\///;
1075 navigateHeader ($scriptwhere,$pathname,$filename,$rev, "annotate");
1076 print "<h3 align=center>Annotation of $pathname$filename, Revision $rev</h3>\n";
1078 # this seems to be necessary
1079 $| = 1; $| = 0; # Flush
1081 # this annotate version is based on the
1082 # cvs annotate-demo Perl script by Cyclic Software
1083 # It was written by Cyclic Software, http://www.cyclic.com/, and is in
1084 # the public domain.
1085 # we could abandon the use of rlog, rcsdiff and co using
1086 # the cvsserver in a similiar way one day (..after rewrite)
1087 $pid = open2($reader, $writer, "cvs server") || fatal ("500 Internal Error",
1088 "Fatal Error - unable to open cvs for annotation");
1090 # OK, first send the request to the server. A simplified example is:
1091 # Root /home/kingdon/zwork/cvsroot
1094 # /home/kingdon/zwork/cvsroot/foo
1096 # /home/kingdon/zwork/cvsroot
1098 # although as you can see there are a few more details.
1100 print $writer "Root $cvsroot\n";
1101 print $writer "Valid-responses ok error Valid-requests Checked-in Updated Merged Removed M E\n";
1102 # Don't worry about sending valid-requests, the server just needs to
1103 # support "annotate" and if it doesn't, there isn't anything to be done.
1104 print $writer "UseUnchanged\n";
1105 print $writer "Argument -r\n";
1106 print $writer "Argument $rev\n";
1107 print $writer "Argument $where\n";
1109 # The protocol requires us to fully fake a working directory (at
1110 # least to the point of including the directories down to the one
1111 # containing the file in question).
1112 # So if $where is "dir/sdir/file", then @dirs will be ("dir","sdir","file")
1113 my @dirs = split('/', $where);
1117 # In our example, $_ is "dir".
1121 print $writer "Directory $path\n";
1122 print $writer "$cvsroot/$path\n";
1123 # In our example, $_ is "sdir" and $path becomes "dir/sdir"
1124 # And the next time, "file" and "dir/sdir/file" (which then gets
1125 # ignored, because we don't need to send Directory for the file).
1129 # And the last "Directory" before "annotate" is the top level.
1130 print $writer "Directory .\n";
1131 print $writer "$cvsroot\n";
1133 print $writer "annotate\n";
1134 # OK, we've sent our command to the server. Thing to do is to
1135 # close the writer side and get all the responses. If "cvs server"
1136 # were nicer about buffering, then we could just leave it open, I think.
1137 close ($writer) || die "cannot close: $!";
1139 # Ready to get the responses from the server.
1141 # E Annotations for foo/xx
1143 # M 1.3 (kingdon 06-Sep-97): hello
1146 my ($oldLrev, $oldLusr) = ("", "");
1147 my ($revprint, $usrprint);
1149 print "<table border=0 cellspacing=0 cellpadding=0>\n";
1156 # Adding one is for the (single) space which follows $words[0].
1157 my $rest = substr ($_, length ($words[0]) + 1);
1158 if ($words[0] eq "E") {
1161 elsif ($words[0] eq "M") {
1163 my $lrev = substr ($_, 2, 13);
1164 my $lusr = substr ($_, 16, 9);
1165 my $line = substr ($_, 36);
1166 # we should parse the date here ..
1167 if ($lrev eq $oldLrev) {
1171 $revprint = $lrev; $oldLusr = "";
1173 if ($lusr eq $oldLusr) {
1181 # is there a less timeconsuming way to strip spaces ?
1182 ($lrev = $lrev) =~ s/\s+//g;
1183 my $isCurrentRev = ("$rev" eq "$lrev");
1185 print "<b>" if ($isCurrentRev);
1186 printf ("%8s%s%8s %4d:", $revprint, ($isCurrentRev ? "|" : " "), $usrprint, $lineNr);
1187 print spacedHtmlText($line);
1188 print "</b>" if ($isCurrentRev);
1190 elsif ($words[0] eq "ok") {
1191 # We could complain about any text received after this, like the
1192 # CVS command line client. But for simplicity, we don't.
1194 elsif ($words[0] eq "error") {
1195 fatal ("500 Internal Error", "Error occured during annotate: <b>$_</b>");
1204 close ($reader) || warn "cannot close: $!";
1208 ###############################
1210 ###############################
1211 sub doCheckout($$) {
1212 my ($fullname, $rev) = @_;
1213 my ($mimetype,$revopt);
1214 my $fh = do {local(*FH);};
1216 if ($rev eq 'HEAD' || $rev eq '.') {
1220 # make sure the revisions a wellformed, for security
1222 if (defined($rev) && !($rev =~ /^[\d\.]+$/)) {
1223 &fatal("404 Not Found",
1224 "Malformed query \"$ENV{'QUERY_STRING'}\"");
1227 if (&forbidden_file($fullname)) {
1228 &fatal("403 Forbidden", "Access forbidden. This file is mentioned in \@DissallowRead");
1233 if (defined($input{"content-type"}) && ($input{"content-type"} =~ /\S\/\S/)) {
1234 $mimetype = $input{"content-type"}
1237 $mimetype = &getMimeTypeFromSuffix($fullname);
1240 if (defined($rev)) {
1243 readLog($fullname,$rev);
1244 $moddate=$date{$rev};
1251 $moddate=$date{$symrev{HEAD}};
1255 ### just for the record:
1256 ### 'cvs co' seems to have a bug regarding single checkout of
1257 ### directories/files having spaces in it;
1258 ### this is an issue that should be resolved on cvs's side
1260 # Safely for a child process to read from.
1261 if (! open($fh, "-|")) { # child
1262 open(STDERR, ">&STDOUT"); # Redirect stderr to stdout
1263 exec("cvs", "-d", "$cvsroot", "co", "-p", "$revopt", "$where");
1265 #===================================================================
1266 #Checking out squid/src/ftp.c
1267 #RCS: /usr/src/CVS/squid/src/ftp.c,v
1272 my ($revision, $filename, $cvsheader);
1275 last if (/^\*\*\*\*/);
1276 $revision = $1 if (/^VERS: (.*)$/);
1277 if (/^Checking out (.*)$/) {
1279 $filename =~ s/^\.\/*//;
1283 if ($filename ne $where) {
1284 &fatal("500 Internal Error",
1285 "Unexpected output from cvs co: $cvsheader"
1286 . "<p><b>Check whether the directory $cvsroot/CVSROOT exists "
1287 . "and the script has write-access to the CVSROOT/history "
1288 . "file if it exists."
1289 . "<br>The script needs to place lock files in the "
1290 . "directory the file is in as well.</b>");
1294 if ($mimetype eq "text/x-cvsweb-markup") {
1295 &cvswebMarkup($fh,$fullname,$revision);
1298 http_header($mimetype);
1304 sub cvswebMarkup($$$) {
1305 my ($filehandle,$fullname,$revision) = @_;
1306 my ($pathname, $filename);
1308 ($pathname = $where) =~ s/(Attic\/)?[^\/]*$//;
1309 ($filename = $where) =~ s/^.*\///;
1310 my ($fileurl) = urlencode($filename);
1314 navigateHeader ($scriptwhere, $pathname, $filename, $revision, "view");
1315 print "<HR noshade>";
1316 print "<table width=\"100%\"><tr><td bgcolor=\"$markupLogColor\">";
1317 print "File: ", &clickablePath($where, 1);
1319 &download_link(urlencode($fileurl), $revision, "(download)");
1320 if (!$defaultTextPlain) {
1322 &download_link(urlencode($fileurl), $revision, "(as text)",
1326 if ($show_log_in_markup) {
1327 readLog($fullname); #,$revision);
1328 printLog($revision,0);
1331 print "Version: <B>$revision</B><BR>\n";
1332 print "Tag: <B>", $input{only_with_tag}, "</b><br>\n" if
1333 $input{only_with_tag};
1335 print "</td></tr></table>";
1336 my @content = <$filehandle>;
1337 my $url = download_url($fileurl, $revision, $mimetype);
1338 print "<HR noshade>";
1339 if ($mimetype =~ /^image/) {
1340 print "<IMG SRC=\"$url$barequery\"><BR>";
1342 elsif ($mimetype =~ m%^application/pdf%) {
1343 print "<EMBED SRC=\"$url$barequery\" WIDTH=\"100%\"><BR>";
1347 foreach (@content) {
1348 print spacedHtmlText($_);
1355 my ($mimetype) = @_;
1357 $mimetype =~ m%^text/% ||
1358 $mimetype =~ m%^image/% ||
1359 $mimetype =~ m%^application/pdf% ||
1363 ###############################
1365 ###############################
1366 sub doDiff($$$$$$) {
1367 my($fullname, $r1, $tr1, $r2, $tr2, $f) = @_;
1368 my $fh = do {local(*FH);};
1369 my ($rev1, $rev2, $sym1, $sym2, @difftype, $diffname, $f1, $f2);
1371 if (&forbidden_file($fullname)) {
1372 &fatal("403 Forbidden", "Access forbidden. This file is mentioned in \@DissallowRead");
1376 if ($r1 =~ /([^:]+)(:(.+))?/) {
1380 if ($r1 eq 'text') {
1384 if ($r2 =~ /([^:]+)(:(.+))?/) {
1388 if ($r2 eq 'text') {
1392 # make sure the revisions a wellformed, for security
1394 if (!($rev1 =~ /^[\d\.]+$/) || !($rev2 =~ /^[\d\.]+$/)) {
1395 &fatal("404 Not Found",
1396 "Malformed query \"$ENV{'QUERY_STRING'}\"");
1399 # rev1 and rev2 are now both numeric revisions.
1400 # Thus we do a DWIM here and swap them if rev1 is after rev2.
1401 # XXX should we warn about the fact that we do this?
1402 if (&revcmp($rev1,$rev2) > 0) {
1403 my ($tmp1, $tmp2) = ($rev1, $sym1);
1404 ($rev1, $sym1) = ($rev2, $sym2);
1405 ($rev2, $sym2) = ($tmp1, $tmp2);
1407 my $human_readable = 0;
1410 $diffname = "Context diff";
1413 @difftype = qw{--side-by-side --width=164};
1414 $diffname = "Side by Side";
1417 $human_readable = 1;
1418 @difftype = qw{--unified=15};
1419 $diffname = "Long Human readable";
1423 $human_readable = 1;
1424 $diffname = "Human readable";
1428 $diffname = "Unidiff";
1431 fatal ("400 Bad arguments", "Diff format $f not understood");
1434 # apply special options
1435 if ($human_readable) {
1437 push @difftype, '-p';
1440 push @difftype, '-w';
1442 if ($hr_ignkeysubst) {
1443 push @difftype, '-kk';
1446 if (! open($fh, "-|")) { # child
1447 open(STDERR, ">&STDOUT"); # Redirect stderr to stdout
1448 exec("rcsdiff",@difftype,"-r$rev1","-r$rev2",$fullname);
1450 if ($human_readable) {
1452 &human_readable_diff($fh, $rev2);
1457 http_header("text/plain");
1460 #===================================================================
1461 #RCS file: /home/ncvs/src/sys/netinet/tcp_output.c,v
1462 #retrieving revision 1.16
1463 #retrieving revision 1.17
1464 #diff -c -r1.16 -r1.17
1465 #*** /home/ncvs/src/sys/netinet/tcp_output.c 1995/11/03 22:08:08 1.16
1466 #--- /home/ncvs/src/sys/netinet/tcp_output.c 1995/12/05 17:46:35 1.17
1469 # - nuke the stderr output if it's what we expect it to be
1470 # - Add "no differences found" if the diff command supplied no output.
1472 #*** src/sys/netinet/tcp_output.c 1995/11/03 22:08:08 1.16
1473 #--- src/sys/netinet/tcp_output.c 1995/12/05 17:46:35 1.17 RELENG_2_1_0
1474 # (bogus example, but...)
1476 if (grep { $_ eq '-u'} @difftype) {
1485 if (m|^$f1 $cvsroot|o) {
1492 elsif (m|^$f2 $cvsroot|o) {
1504 ###############################
1506 ###############################
1507 sub getDirLogs($$@) {
1508 my ($cvsroot,$dirname,@otherFiles) = @_;
1509 my ($state,$otherFiles,$tag, $file, $date, $branchpoint, $branch, $log);
1510 my ($rev, $revision, $revwanted, $filename, $head, $author);
1512 $tag = $input{only_with_tag};
1514 my ($DirName) = "$cvsroot/$where";
1515 my (@files, @filetags);
1516 my $fh = do {local(*FH);};
1518 push(@files, &safeglob("$DirName/*,v"));
1519 push(@files, &safeglob("$DirName/Attic/*,v")) if (!$input{'hideattic'});
1520 foreach $file (@otherFiles) {
1521 push(@files, "$DirName/$file");
1524 # just execute rlog if there are any files
1529 if (defined($tag)) {
1530 #can't use -r<tag> as - is allowed in tagnames, but misinterpreated by rlog..
1531 if (! open($fh, "-|")) {
1532 open(STDERR, "> /dev/null"); # rlog may complain; ignore.
1533 exec($no_rlog ? ( "cvs", "log") : ( "rlog" ) ,@files);
1537 my $kidpid = open($fh, "-|");
1539 open(STDERR, "> /dev/null"); # rlog may complain; ignore.
1540 exec($no_rlog ? ( "cvs", "log" ) : ( "rlog" ),"-r",@files);
1545 if ($state eq "start") {
1546 #Next file. Initialize file variables
1550 $branchpoint = undef;
1560 print "$state:$_" if ($verbose);
1562 if ($state eq "head") {
1563 #$rcsfile = $1 if (/^RCS file: (.+)$/); #not used (yet)
1564 $filename = $1 if (/^Working file: (.+)$/);
1565 $head = $1 if (/^head: (.+)$/);
1566 $branch = $1 if (/^branch: (.+)$/);
1568 if ($state eq "head" && /^symbolic names/) {
1570 ($branch = $head) =~ s/\.\d+$// if (!defined($branch));
1571 $branch =~ s/(\.?)(\d+)$/${1}0.$2/;
1572 $symrev{MAIN} = $branch;
1573 $symrev{HEAD} = $branch;
1576 push (@filetags, "MAIN", "HEAD");
1579 if ($state eq "tags" &&
1580 /^\s+(.+):\s+([\d\.]+)\s+$/) {
1581 push (@filetags, $1);
1586 if ($state eq "tags" && /^\S/) {
1587 if (defined($tag) && (defined($symrev{$tag}) || $tag eq "HEAD")) {
1588 $revwanted = $tag eq "HEAD" ? $symrev{"MAIN"} : $symrev{$tag};
1589 ($branch = $revwanted) =~ s/\.0\././;
1590 ($branchpoint = $branch) =~ s/\.?\d+$//;
1591 $revwanted = undef if ($revwanted ne $branch);
1593 elsif (defined($tag) && $tag ne "HEAD") {
1594 print "Tag not found, skip this file" if ($verbose);
1598 foreach my $tagfound (@filetags) {
1599 $tags{$tagfound} = 1;
1604 if ($state eq "head" && /^----------------------------$/) {
1609 # Try to reconstruct the relative filename if RCS spits out a full path
1610 $filename =~ s%^\Q$DirName\E/%%;
1613 if ($state eq "log") {
1614 if (/^----------------------------$/
1615 || /^=============================/) {
1616 # End of a log entry.
1618 ($revbranch = $rev) =~ s/\.\d+$//;
1619 print "$filename $rev Wanted: $revwanted "
1620 . "Revbranch: $revbranch Branch: $branch "
1621 . "Branchpoint: $branchpoint\n" if ($verbose);
1622 if (!defined($revwanted) && defined($branch)
1623 && $branch eq $revbranch || !defined($tag)) {
1624 print "File revision $rev found for branch $branch\n"
1628 if (defined($revwanted) ? $rev eq $revwanted :
1629 defined($branchpoint) ? $rev eq $branchpoint :
1630 0 && ($rev eq $head)) { # Don't think head is needed here..
1631 print "File info $rev found for $filename\n" if ($verbose);
1632 my @finfo = ($rev,$date,$log,$author,$filename);
1634 ($name = $filename) =~ s%/.*%%;
1635 $fileinfo{$name} = [ @finfo ];
1636 $state = "done" if (defined($revwanted) && $rev eq $revwanted);
1642 elsif (!defined($date) && m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);|) {
1644 # damn 2-digit year routines :-)
1648 $date = &Time::Local::timegm($6,$5,$4,$3,$2 - 1,$yr);
1649 ($author) = /author: ([^;]+)/;
1654 elsif (!defined($rev) && m/^revision (.*)$/) {
1662 if (/^===============/) {
1668 fatal("500 Internal Error",
1669 "Failed to spawn GNU rlog on <em>'".join(", ", @files)."'</em><p>did you set the <b>\$ENV{PATH}</b> in your configuration file correctly ?");
1675 my($fullname,$revision) = @_;
1676 my ($symnames, $head, $rev, $br, $brp, $branch, $branchrev);
1677 my $fh = do {local(*FH);};
1679 if (defined($revision)) {
1680 $revision = "-r$revision";
1688 undef @allrevisions;
1695 print("Going to rlog '$fullname'\n") if ($verbose);
1696 if (! open($fh, "-|")) { # child
1697 if ($revision ne '') {
1698 exec($no_rlog ? ( "cvs", "log" ) : ( "rlog" ),$revision,$fullname);
1701 exec($no_rlog ? ( "cvs", "log" ) : ( "rlog" ),$fullname);
1705 print if ($verbose);
1707 if (/^\s+([^:]+):\s+([\d\.]+)/) {
1714 elsif (/^head:\s+([\d\.]+)/) {
1717 elsif (/^branch:\s+([\d\.]+)/) {
1720 elsif (/^symbolic names/) {
1727 ($curbranch = $head) =~ s/\.\d+$// if (!defined($curbranch));
1729 # each log entry is of the form:
1730 # ----------------------------
1732 # date: 1995/11/29 22:15:52; author: fenner; state: Exp; lines: +5 -3
1734 # ----------------------------
1736 while (!/^=========/) {
1738 last logentry if (!defined($_)); # EOF
1739 print "R:", $_ if ($verbose);
1740 if (/^revision ([\d\.]+)/) {
1742 unshift(@allrevisions,$rev);
1744 elsif (/^========/ || /^----------------------------$/) {
1748 # The rlog output is syntactically ambiguous. We must
1749 # have guessed wrong about where the end of the last log
1751 # Since this is likely to happen when people put rlog output
1752 # in their commit messages, don't even bother keeping
1753 # these lines since we don't know what revision they go with
1756 # &fatal("500 Internal Error","Error parsing RCS output: $_");
1759 print "D:", $_ if ($verbose);
1760 if (m|^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+author:\s+(\S+);\s+state:\s+(\S+);\s+(lines:\s+([0-9\s+-]+))?|) {
1762 # damn 2-digit year routines :-)
1766 $date{$rev} = &Time::Local::timegm($6,$5,$4,$3,$2 - 1,$yr);
1769 $difflines{$rev} = $10;
1772 &fatal("500 Internal Error", "Error parsing RCS output: $_");
1776 print "L:", $_ if ($verbose);
1777 next line if (/^branches:\s/);
1778 last line if (/^----------------------------$/ || /^=========/);
1781 print "E:", $_ if ($verbose);
1784 print "Done reading RCS file\n" if ($verbose);
1786 @revorder = reverse sort {revcmp($a,$b)} @allrevisions;
1787 print "Done sorting revisions",join(" ",@revorder),"\n" if ($verbose);
1790 # HEAD is an artificial tag which is simply the highest tag number on the main
1791 # branch, unless there is a branch tag in the RCS file in which case it's the
1792 # highest revision on that branch. Find it by looking through @revorder; it
1793 # is the first commit listed on the appropriate branch.
1794 # This is not neccesary the same revision as marked as head in the RCS file.
1795 my $headrev = $curbranch || "1";
1796 ($symrev{"MAIN"} = $headrev) =~ s/(\.?)(\d+)$/${1}0.$2/;
1798 foreach $rev (@revorder) {
1799 if ($rev =~ /^(\S*)\.\d+$/ && $headrev eq $1) {
1800 $symrev{"HEAD"} = $rev;
1804 ($symrev{"HEAD"} = $headrev) =~ s/\.\d+$//
1805 if (!defined($symrev{"HEAD"}));
1806 print "Done finding HEAD\n" if ($verbose);
1808 # Now that we know all of the revision numbers, we can associate
1809 # absolute revision numbers with all of the symbolic names, and
1810 # pass them to the form so that the same association doesn't have
1817 foreach (reverse sort keys %symrev) {
1819 if ($rev =~ /^((.*)\.)0\.(\d+)$/) {
1820 push(@branchnames, $_);
1822 # A revision number of A.B.0.D really translates into
1823 # "the highest current revision on branch A.B.D".
1825 # If there is no branch A.B.D, then it translates into
1828 # This reasoning also applies to the main branch A.B,
1829 # with the branch number 0.A, with the exception that
1830 # it has no head to translate to if there is nothing on
1831 # the branch, but I guess this can never happen?
1833 # Since some stupid people actually import/check in
1834 # files with version 0.X we assume that the above cannot
1835 # happen, and regard 0.X(.*) as a revision and not a branch.
1837 $head = defined($2) ? $2 : "";
1839 $branchrev = $head . ($head ne "" ? "." : "") . $branch;
1841 ($regex = $branchrev) =~ s/\./\\./g;
1845 foreach my $r (@revorder) {
1846 if ($r =~ /^${regex}\b/) {
1851 next if ($rev eq "");
1852 if ($rev ne $head && $head ne "") {
1853 $branchpoint{$head} .= ", " if ($branchpoint{$head});
1854 $branchpoint{$head} .= $_;
1857 $revsym{$rev} .= ", " if ($revsym{$rev});
1858 $revsym{$rev} .= $_;
1859 $sel .= "<OPTION VALUE=\"${rev}:${_}\">$_\n";
1861 print "Done associating revisions with branches\n" if ($verbose);
1863 my ($onlyonbranch, $onlybranchpoint);
1864 if ($onlyonbranch = $input{'only_with_tag'}) {
1865 $onlyonbranch = $symrev{$onlyonbranch};
1866 if ($onlyonbranch =~ s/\.0\././) {
1867 ($onlybranchpoint = $onlyonbranch) =~ s/\.\d+$//;
1870 $onlybranchpoint = $onlyonbranch;
1872 if (!defined($onlyonbranch) || $onlybranchpoint eq "") {
1873 fatal("404 Tag not found","Tag $input{'only_with_tag'} not defined");
1879 foreach (@allrevisions) {
1880 ($br = $_) =~ s/\.\d+$//;
1881 ($brp = $br) =~ s/\.\d+$//;
1882 next if ($onlyonbranch && $br ne $onlyonbranch &&
1883 $_ ne $onlybranchpoint);
1884 unshift(@revisions,$_);
1887 if ($logsort eq "date") {
1888 # Sort the revisions in commit order an secondary sort on revision
1889 # (secondary sort needed for imported sources, or the first main
1890 # revision gets before the same revision on the 1.1.1 branch)
1891 @revdisplayorder = sort {$date{$b} <=> $date{$a} || -revcmp($a, $b)} @revisions;
1893 elsif ($logsort eq "rev") {
1894 # Sort the revisions in revision order, highest first
1895 @revdisplayorder = reverse sort {revcmp($a,$b)} @revisions;
1898 # No sorting. Present in the same order as rlog / cvs log
1899 @revdisplayorder = @revisions;
1905 my ($link, $br, $brp);
1907 ($br = $_) =~ s/\.\d+$//;
1908 ($brp = $br) =~ s/\.?\d+$//;
1909 my ($isDead, $prev);
1911 $link = 1 if (!defined($link));
1912 $isDead = ($state{$_} eq "dead");
1914 if ($link && !$isDead) {
1916 ($filename = $where) =~ s/^.*\///;
1917 my ($fileurl) = urlencode($filename);
1918 print "<a NAME=\"rev$_\"></a>";
1919 if (defined($revsym{$_})) {
1920 foreach my $sym (split(", ", $revsym{$_})) {
1921 print "<a NAME=\"$sym\"></a>";
1924 if (defined($revsym{$br}) && $revsym{$br} && !defined($nameprinted{$br})) {
1925 foreach my $sym (split(", ", $revsym{$br})) {
1926 print "<a NAME=\"$sym\"></a>";
1928 $nameprinted{$br} = 1;
1930 print "\n Revision ";
1931 &download_link($fileurl, $_, $_,
1932 $defaultViewable ? "text/x-cvsweb-markup" : undef);
1933 if ($defaultViewable) {
1935 &download_link($fileurl, $_, "(download)", $mimetype);
1937 if (not $defaultTextPlain) {
1939 &download_link($fileurl, $_, "(as text)",
1942 if (!$defaultViewable) {
1944 &download_link($fileurl, $_, "(view)", "text/x-cvsweb-markup");
1946 if ($allow_annotate) {
1947 print " - <a href=\"" . $scriptname . "/" . urlencode($where) . "?annotate=$_$barequery\">";
1948 print "annotate</a>";
1950 # Plus a select link if enabled, and this version isn't selected
1951 if ($allow_version_select) {
1952 if ((!defined($input{"r1"}) || $input{"r1"} ne $_)) {
1953 print " - <A HREF=\"${scriptwhere}?r1=$_$barequery" .
1954 "\">[select for diffs]</A>\n";
1957 print " - <b>[selected]</b>";
1962 print "Revision <B>$_</B>";
1964 if (/^1\.1\.1\.\d+$/) {
1965 print " <i>(vendor branch)</i>";
1967 if (defined @mytz) {
1968 my ($est) = $mytz[(localtime($date{$_}))[8]];
1969 print ", <i>" . scalar localtime($date{$_}) . " $est</i> (";
1971 print ", <i>" . scalar gmtime($date{$_}) . " UTC</i> (";
1973 print readableTime(time() - $date{$_},1) . " ago)";
1975 print "<i>" . $author{$_} . "</i>\n";
1976 print "<BR>Branch: <b>",$link?link_tags($revsym{$br}):$revsym{$br},"</b>\n"
1978 print "<BR>CVS Tags: <b>",$link?link_tags($revsym{$_}):$revsym{$_},"</b>"
1980 print "<BR>Branch point for: <b>",$link?link_tags($branchpoint{$_}):$branchpoint{$_},"</b>\n"
1981 if ($branchpoint{$_});
1982 # Find the previous revision
1983 my @prevrev = split(/\./, $_);
1985 if (--$prevrev[$#prevrev] <= 0) {
1986 # If it was X.Y.Z.1, just make it X.Y
1990 $prev = join(".", @prevrev);
1991 } until (defined($date{$prev}) || $prev eq "");
1993 if ($difflines{$_}) {
1994 print "<BR>Changes since <b>$prev: $difflines{$_} lines</b>";
1998 print "<BR><B><I>FILE REMOVED</I></B>\n";
2006 # Offer diff to previous revision
2008 $diffrev{$prev} = 1;
2009 print " to previous <A HREF=\"${scriptwhere}.diff?r1=$prev";
2010 print "&r2=$_" . $barequery . "\">$prev</A>\n";
2011 if (!$hr_default) { # offer a human readable version if not default
2012 print "(<A HREF=\"${scriptwhere}.diff?r1=$prev";
2013 print "&r2=$_" . $barequery . "&f=h\">colored</A>)\n";
2017 # Plus, if it's on a branch, and it's not a vendor branch,
2018 # offer a diff with the branch point.
2019 if ($revsym{$brp} && !/^1\.1\.1\.\d+$/ && !defined($diffrev{$brp})) {
2020 print " to branchpoint <A HREF=\"${scriptwhere}.diff?r1=$brp";
2021 print "&r2=$_" . $barequery . "\">$brp</A>\n";
2022 if (!$hr_default) { # offer a human readable version if not default
2023 print "(<A HREF=\"${scriptwhere}.diff?r1=$brp";
2024 print "&r2=$_" . $barequery . "&f=h\">colored</A>)\n";
2028 # Plus, if it's on a branch, and it's not a vendor branch,
2029 # offer to diff with the next revision of the higher branch.
2030 # (e.g. change gets committed and then brought
2032 if (/^\d+\.\d+\.\d+/ && !/^1\.1\.1\.\d+$/) {
2034 for ($i = 0; $i < $#revorder && $revorder[$i] ne $_; $i++){}
2035 my (@tmp2) = split(/\./, $_);
2036 for ($nextmain = ""; $i > 0; $i--) {
2037 my ($next) = $revorder[$i-1];
2038 my (@tmp1) = split(/\./, $next);
2039 if ($#tmp1 < $#tmp2) {
2043 # Only the highest version on a branch should have
2044 # a diff for the "next main".
2045 last if ($#tmp1 == $#tmp2 && join(".",@tmp1[0..$#tmp1-1])
2046 eq join(".",@tmp2[0..$#tmp1-1]));
2048 if (!defined($diffrev{$nextmain})) {
2049 $diffrev{$nextmain} = 1;
2050 print " next main <A HREF=\"${scriptwhere}.diff?r1=$nextmain";
2051 print "&r2=$_" . $barequery .
2052 "\">$nextmain</A>\n";
2053 if (!$hr_default) { # offer a human readable version if not default
2054 print "(<A HREF=\"${scriptwhere}.diff?r1=$nextmain";
2055 print "&r2=$_" . $barequery .
2056 "&f=h\">colored</A>)\n";
2060 # Plus if user has selected only r1, then present a link
2061 # to make a diff to that revision
2062 if (defined($input{"r1"}) && !defined($diffrev{$input{"r1"}})) {
2063 $diffrev{$input{"r1"}} = 1;
2064 print " to selected <A HREF=\"${scriptwhere}.diff?"
2065 . "r1=$input{'r1'}&r2=$_" . $barequery
2066 . "\">$input{'r1'}</A>\n";
2067 if (!$hr_default) { # offer a human readable version if not default
2068 print "(<A HREF=\"${scriptwhere}.diff?r1=$input{'r1'}";
2069 print "&r2=$_" . $barequery .
2070 "&f=h\">colored</A>)\n";
2076 print &htmlify($log{$_});
2082 my ($diffrev, $upwhere, $filename, $backurl);
2086 html_header("CVS log for $where");
2087 ($upwhere = $where) =~ s|(Attic/)?[^/]+$||;
2088 ($filename = $where) =~ s|^.*/||;
2089 $backurl = $scriptname . "/" . urlencode($upwhere) . $query;
2090 print &link($backicon, "$backurl#$filename"),
2091 " <b>Up to ", &clickablePath($upwhere, 1), "</b><p>\n";
2092 print "<A HREF=\"#diff\">Request diff between arbitrary revisions</A>\n";
2093 print "<HR NOSHADE>\n";
2095 print "Default branch: ";
2096 print ($revsym{$curbranch} || $curbranch);
2099 print "No default branch";
2102 if ($input{only_with_tag}) {
2103 print "Current tag: $input{only_with_tag}<BR>\n";
2108 for (my $i = 0; $i <= $#revdisplayorder; $i++) {
2109 print "<HR size=1 NOSHADE>";
2110 printLog($revdisplayorder[$i]);
2113 print "<HR NOSHADE>";
2114 print "<A NAME=diff>\n";
2115 print "This form allows you to request diff's between any two\n";
2116 print "revisions of a file. You may select a symbolic revision\n";
2117 print "name using the selection box or you may type in a numeric\n";
2118 print "name using the type-in text box.\n";
2120 print "<FORM METHOD=\"GET\" ACTION=\"${scriptwhere}.diff\" NAME=\"diff_select\">\n";
2121 foreach (@stickyvars) {
2122 print "<INPUT TYPE=HIDDEN NAME=\"$_\" VALUE=\"$input{$_}\">\n"
2123 if (defined($input{$_})
2124 && ((!defined($DEFAULTVALUE{$_})
2125 || $input{$_} ne $DEFAULTVALUE{$_})
2126 && $input{$_} ne ""));
2128 print "Diffs between \n";
2129 print "<SELECT NAME=\"r1\">\n";
2130 print "<OPTION VALUE=\"text\" SELECTED>Use Text Field\n";
2132 print "</SELECT>\n";
2133 $diffrev = $revdisplayorder[$#revdisplayorder];
2134 $diffrev = $input{"r1"} if (defined($input{"r1"}));
2135 print "<INPUT TYPE=\"TEXT\" SIZE=\"$inputTextSize\" NAME=\"tr1\" VALUE=\"$diffrev\" onChange='document.diff_select.r1.selectedIndex=0'>\n";
2137 print "<SELECT NAME=\"r2\">\n";
2138 print "<OPTION VALUE=\"text\" SELECTED>Use Text Field\n";
2140 print "</SELECT>\n";
2141 $diffrev = $revdisplayorder[0];
2142 $diffrev = $input{"r2"} if (defined($input{"r2"}));
2143 print "<INPUT TYPE=\"TEXT\" SIZE=\"$inputTextSize\" NAME=\"tr2\" VALUE=\"$diffrev\" onChange='document.diff_select.r2.selectedIndex=0'>\n";
2144 print "<BR>Type of Diff should be a ";
2146 print "<INPUT TYPE=SUBMIT VALUE=\" Get Diffs \">\n";
2148 print "<HR noshade>\n";
2150 print "<A name=branch></A>\n";
2151 print "<FORM METHOD=\"GET\" ACTION=\"$scriptwhere\">\n";
2152 foreach (@stickyvars) {
2153 next if ($_ eq "only_with_tag");
2154 next if ($_ eq "logsort");
2155 print "<INPUT TYPE=HIDDEN NAME=\"$_\" VALUE=\"$input{$_}\">\n"
2156 if (defined($input{$_})
2157 && (!defined($DEFAULTVALUE{$_})
2158 || $input{$_} ne $DEFAULTVALUE{$_})
2159 && $input{$_} ne "");
2161 print "View only Branch: \n";
2162 print "<SELECT NAME=\"only_with_tag\"";
2163 print " onchange=\"submit()\"" if ($use_java_script);
2165 print "<OPTION VALUE=\"\"";
2166 print " SELECTED" if (defined($input{"only_with_tag"}) &&
2167 $input{"only_with_tag"} eq "");
2168 print ">Show all branches\n";
2169 foreach (reverse sort @branchnames) {
2171 print " SELECTED" if (defined($input{"only_with_tag"})
2172 && $input{"only_with_tag"} eq $_);
2175 print "</SELECT>\n";
2176 print "<INPUT TYPE=SUBMIT VALUE=\" View Branch \">\n";
2179 print "<A name=logsort></A>\n";
2180 print "<FORM METHOD=\"GET\" ACTION=\"$scriptwhere\">\n";
2181 foreach (@stickyvars) {
2182 next if ($_ eq "only_with_tag");
2183 next if ($_ eq "logsort");
2184 print "<INPUT TYPE=HIDDEN NAME=\"$_\" VALUE=\"$input{$_}\">\n"
2185 if (defined($input{$_})
2186 && (!defined($DEFAULTVALUE{$_})
2187 || $input{$_} ne $DEFAULTVALUE{$_})
2188 && $input{$_} ne "");
2190 print "Sort log by: \n";
2191 print "<SELECT NAME=\"logsort\"";
2192 print " onchange=\"submit()\"" if ($use_java_script);
2194 print "<OPTION VALUE=cvs",$logsort eq "cvs" ? " SELECTED" : "", ">Not sorted";
2195 print "<OPTION VALUE=date",$logsort eq "date" ? " SELECTED" : "", ">Commit date";
2196 print "<OPTION VALUE=rev",$logsort eq "rev" ? " SELECTED" : "", ">Revision";
2197 print "</SELECT>\n";
2198 print "<INPUT TYPE=SUBMIT VALUE=\" Sort \">\n";
2201 print "</BODY></HTML>\n";
2204 sub flush_diff_rows ($$$$)
2207 my ($leftColRef,$rightColRef,$leftRow,$rightRow) = @_;
2209 if (!defined($state)) {
2213 if ($state eq "PreChangeRemove") { # we just got remove-lines before
2214 for ($j = 0 ; $j < $leftRow; $j++) {
2215 print "<tr><td bgcolor=\"$diffcolorRemove\">@$leftColRef[$j]</td>";
2216 print "<td bgcolor=\"$diffcolorEmpty\"> </td></tr>\n";
2219 elsif ($state eq "PreChange") { # state eq "PreChange"
2220 # we got removes with subsequent adds
2221 for ($j = 0; $j < $leftRow || $j < $rightRow ; $j++) { # dump out both cols
2223 if ($j < $leftRow) {
2224 print "<td bgcolor=\"$diffcolorChange\">@$leftColRef[$j]</td>";
2227 print "<td bgcolor=\"$diffcolorDarkChange\"> </td>";
2229 if ($j < $rightRow) {
2230 print "<td bgcolor=\"$diffcolorChange\">@$rightColRef[$j]</td>";
2233 print "<td bgcolor=\"$diffcolorDarkChange\"> </td>";
2241 # Function to generate Human readable diff-files
2242 # human_readable_diff(String revision_to_return_to);
2244 sub human_readable_diff($){
2245 my ($i,$difftxt, $where_nd, $filename, $pathname, $scriptwhere_nd);
2246 my ($fh, $rev) = @_;
2247 my ($date1, $date2, $r1d, $r2d, $r1r, $r2r, $rev1, $rev2, $sym1, $sym2);
2248 my (@rightCol, @leftCol);
2250 ($where_nd = $where) =~ s/.diff$//;
2251 ($filename = $where_nd) =~ s/^.*\///;
2252 ($pathname = $where_nd) =~ s/(Attic\/)?[^\/]*$//;
2253 ($scriptwhere_nd = $scriptwhere) =~ s/.diff$//;
2255 navigateHeader ($scriptwhere_nd, $pathname, $filename, $rev, "diff");
2257 # Read header to pick up read revision and date, if possible
2259 ($r1d,$r1r) = /\t(.*)\t(.*)$/ if (/^--- /);
2260 ($r2d,$r2r) = /\t(.*)\t(.*)$/ if (/^\+\+\+ /);
2261 last if (/^\+\+\+ /);
2263 if (defined($r1r) && $r1r =~ /^(\d+\.)+\d+$/) {
2267 if (defined($r2r) && $r2r =~ /^(\d+\.)+\d+$/) {
2272 print "<h3 align=center>Diff for /$where_nd between version $rev1 and $rev2</h3>\n";
2274 print "<table border=0 cellspacing=0 cellpadding=0 width=\"100%\">\n";
2275 print "<tr bgcolor=\"#ffffff\">\n";
2276 print "<th width=\"50%\" valign=TOP>";
2277 print "version $rev1";
2278 print ", $date1" if (defined($date1));
2279 print "<br>Tag: $sym1\n" if ($sym1);
2281 print "<th width=\"50%\" valign=TOP>";
2282 print "version $rev2";
2283 print ", $date2" if (defined($date2));
2284 print "<br>Tag: $sym2\n" if ($sym1);
2287 my $fs = "<font face=\"$difffontface\" size=\"$difffontsize\">";
2292 my ($oldline, $newline, $funname, $diffcode, $rest);
2295 # The diffrows are could make excellent use of
2296 # cascading style sheets because we've to set the
2297 # font and color for each row. anyone ...?
2302 if ($difftxt =~ /^@@/) {
2303 ($oldline,$newline,$funname) = $difftxt =~ /@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/;
2304 print "<tr bgcolor=\"$diffcolorHeading\"><td width=\"50%\">";
2305 print "<table width=\"100%\" border=1 cellpadding=5><tr><td><b>Line $oldline</b>";
2306 print " <font size=-1>$funname</font></td></tr></table>";
2307 print "</td><td width=\"50%\">";
2308 print "<table width=\"100%\" border=1 cellpadding=5><tr><td><b>Line $newline</b>";
2309 print " <font size=-1>$funname</font></td></tr></table>";
2316 ($diffcode,$rest) = $difftxt =~ /^([-+ ])(.*)/;
2317 $_ = spacedHtmlText ($rest);
2319 # Add fontface, size
2320 $_ = "$fs $_$fe";
2323 # little state machine to parse unified-diff output (Hen, zeller@think.de)
2324 # in order to get some nice 'ediff'-mode output
2326 # "dump" - just dump the value
2327 # "PreChangeRemove" - we began with '-' .. so this could be the start of a 'change' area or just remove
2328 # "PreChange" - okey, we got several '-' lines and moved to '+' lines -> this is a change block
2331 if ($diffcode eq '+') {
2332 if ($state eq "dump") { # 'change' never begins with '+': just dump out value
2333 print "<tr><td bgcolor=\"$diffcolorEmpty\"> </td><td bgcolor=\"$diffcolorAdd\">$_</td></tr>\n";
2335 else { # we got minus before
2336 $state = "PreChange";
2337 $rightCol[$rightRow++] = $_;
2340 elsif ($diffcode eq '-') {
2341 $state = "PreChangeRemove";
2342 $leftCol[$leftRow++] = $_;
2344 else { # empty diffcode
2345 flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow;
2346 print "<tr><td>$_</td><td>$_</td></tr>\n";
2353 flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow;
2355 # state is empty if we didn't have any change
2357 print "<tr><td colspan=2> </td></tr>";
2358 print "<tr bgcolor=\"$diffcolorEmpty\" >";
2359 print "<td colspan=2 align=center><b>- No viewable Change -</b></td></tr>";
2364 print "<br><hr noshade width=\"100%\">\n";
2366 print "<table border=0>";
2370 print "<table border=1><tr><td>";
2371 print "Legend:<br><table border=0 cellspacing=0 cellpadding=1>\n";
2372 print "<tr><td align=center bgcolor=\"$diffcolorRemove\">Removed from v.$rev1</td><td bgcolor=\"$diffcolorEmpty\"> </td></tr>";
2373 print "<tr bgcolor=\"$diffcolorChange\"><td align=center colspan=2>changed lines</td></tr>";
2374 print "<tr><td bgcolor=\"$diffcolorEmpty\"> </td><td align=center bgcolor=\"$diffcolorAdd\">Added in v.$rev2</td></tr>";
2375 print "</table></td></tr></table>\n";
2378 # Print format selector
2379 print "<FORM METHOD=\"GET\" ACTION=\"${scriptwhere}\">\n";
2380 foreach my $var (keys %input) {
2381 next if ($var eq "f");
2382 next if (defined($DEFAULTVALUE{$var})
2383 && $DEFAULTVALUE{$var} eq $input{$var});
2384 print "<INPUT TYPE=HIDDEN NAME=\"",urlencode($var),"\" VALUE=\"",
2385 urlencode($input{$var}),"\">\n";
2387 printDiffSelect($use_java_script);
2388 print "<INPUT TYPE=SUBMIT VALUE=\"Show\">\n";
2392 print "</tr></table>";
2395 sub navigateHeader ($$$$$) {
2396 my ($swhere,$path,$filename,$rev,$title) = @_;
2397 $swhere = "" if ($swhere eq $scriptwhere);
2398 $swhere = urlencode($filename) if ($swhere eq "");
2399 print "<\!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">";
2400 print "<HTML>\n<HEAD>\n";
2401 local $charset = $config{'cvsenc'} || &get_charset();
2403 print "<meta http-equiv=\"Content-Type\" content=\"text/html; Charset=$charset\">\n";
2405 print '<!-- hennerik CVSweb $Revision: 1.112 $ -->';
2406 print "\n<TITLE>$path$filename - $title - $rev</TITLE></HEAD>\n";
2407 print "<BODY BGCOLOR=\"$backcolor\">\n";
2408 print "<table width=\"100%\" border=0 cellspacing=0 cellpadding=1 bgcolor=\"$navigationHeaderColor\">";
2409 print "<tr valign=bottom><td>";
2410 print "<a href=\"$swhere$query#rev$rev\">$backicon";
2411 print "</a> <b>Return to ", &link("$filename","$swhere$query#rev$rev")," CVS log";
2412 print "</b> $fileicon</td>";
2414 print "<td align=right>$diricon <b>Up to ", &clickablePath($path, 1), "</b></td>";
2415 print "</tr></table>";
2418 sub plural_write ($$)
2420 my ($num,$text) = @_;
2422 $text = $text . "s";
2425 return $num . " " . $text;
2433 # print readable timestamp in terms of
2435 # H. Zeller <zeller@think.de>
2437 sub readableTime($$) {
2438 my ($i, $break, $retval);
2439 my ($secs,$long) = @_;
2441 # this function works correct for time >= 2 seconds
2443 return "very little time";
2446 my %desc = (1 , 'second',
2453 my @breaks = sort {$a <=> $b} keys %desc;
2455 while ($i <= $#breaks && $secs >= 2 * $breaks[$i]) {
2459 $break = $breaks[$i];
2460 $retval = plural_write(int ($secs / $break), $desc{"$break"});
2462 if ($long == 1 && $i > 0) {
2463 my $rest = $secs % $break;
2465 $break = $breaks[$i];
2466 my $resttime = plural_write(int ($rest / $break),
2469 $retval = $retval . ", " . $resttime;
2477 # clickablePath(String pathname, boolean last_item_clickable)
2479 # returns a html-ified path whereas each directory is a link for
2480 # faster navigation. last_item_clickable controls whether the
2481 # basename (last directory/file) is a link as well
2483 sub clickablePath($$) {
2484 my ($pathname,$clickLast) = @_;
2487 if ($pathname eq '/') {
2488 # this should never happen - chooseCVSRoot() is
2489 # intended to do this
2490 $retval = "[$cvstree]";
2493 $retval = $retval . " <a href=\"${scriptname}/${query}#dirlist\">[$cvstree]</a>";
2495 my ($lastslash) = $pathname =~ m|/$|;
2496 foreach (split(/\//, $pathname)) {
2497 $retval = $retval . " / ";
2498 $wherepath = $wherepath . '/' . $_;
2499 my ($last) = "$wherepath/" eq "/$pathname"
2500 || "$wherepath" eq "/$pathname";
2501 if ($clickLast || !$last) {
2502 $retval = $retval . "<a href=\"${scriptname}"
2503 . urlencode($wherepath)
2504 . (!$last || $lastslash ? '/' : '')
2506 . (!$last || $lastslash ? "#dirlist" : "")
2509 else { # do not make a link to the current dir
2510 $retval = $retval . $_;
2517 sub chooseCVSRoot() {
2519 foreach (sort keys %CVSROOT) {
2520 if (-d $CVSROOT{$_}) {
2526 print "<form method=\"GET\" action=\"${scriptwhere}\">\n";
2527 foreach $k (keys %input) {
2528 print "<input type=hidden NAME=$k VALUE=$input{$k}>\n"
2529 if ($input{$k}) && ($k ne "cvsroot");
2531 # Form-Elements look wierd in Netscape if the background
2532 # isn't gray and the form elements are not placed
2533 # within a table ...
2534 print "<table><tr>";
2535 print "<td>CVS Root:</td>";
2536 print "<td>\n<select name=\"cvsroot\"";
2537 print " onchange=\"submit()\"" if ($use_java_script);
2540 print "<option value=\"$k\"";
2541 print " selected" if ("$k" eq "$cvstree");
2542 print ">" . ($CVSROOTdescr{"$k"} ? $CVSROOTdescr{"$k"} :
2545 print "</select>\n</td>";
2546 print "<td><input type=submit value=\"Go\"></td>";
2547 print "</tr></table></form>";
2551 print "CVS Root: <b>[$cvstree]</b>";
2555 sub chooseMirror() {
2556 my ($mirror,$moremirrors);
2558 # This code comes from the original BSD-cvsweb
2559 # and may not be useful for your site; If you don't
2560 # set %MIRRORS this won't show up, anyway
2562 # Should perhaps exlude the current site somehow..
2563 if (keys %MIRRORS) {
2564 print "\nThis cvsweb is mirrored in:\n";
2565 foreach $mirror (keys %MIRRORS) {
2566 print ", " if ($moremirrors);
2567 print qq(<a href="$MIRRORS{$mirror}">$mirror</A>\n);
2578 ($af = $a) =~ s/,v$//;
2579 ($bf = $b) =~ s/,v$//;
2580 my ($rev1,$date1,$log1,$author1,$filename1) = @{$fileinfo{$af}}
2581 if (defined($fileinfo{$af}));
2582 my ($rev2,$date2,$log2,$author2,$filename2) = @{$fileinfo{$bf}}
2583 if (defined($fileinfo{$bf}));
2585 if (defined($filename1) && defined($filename2) && $af eq $filename1 && $bf eq $filename2) {
2587 $comp = -revcmp($rev1, $rev2) if ($byrev && $rev1 && $rev2);
2588 $comp = ($date2 <=> $date1) if ($bydate && $date1 && $date2);
2589 $comp = ($log1 cmp $log2) if ($bylog && $log1 && $log2);
2590 $comp = ($author1 cmp $author2) if ($byauthor && $author1 && $author2);
2593 # Directories first, then sorted on name if no other sort critera
2595 my $ad = ((-d "$fullname/$a")?"D":"F");
2596 my $bd = ((-d "$fullname/$b")?"D":"F");
2599 $comp = ("$ad$c" cmp "$bd$d");
2604 # make A url for downloading
2605 sub download_url($$$) {
2606 my ($url,$revision,$mimetype) = @_;
2608 $revision =~ s/\.0\././;
2610 if (defined($checkout_magic)
2611 && (!defined($mimetype) || $mimetype ne "text/x-cvsweb-markup")) {
2613 ($path = $where) =~ s|/[^/]*$|/|;
2614 $url = "$scriptname/$checkoutMagic/${path}$url";
2616 $url .= "?rev=$revision";
2617 $url .= "&content-type=$mimetype" if (defined($mimetype));
2622 # Presents a link to download the
2624 sub download_link($$$$) {
2625 my ($url,$revision,$textlink,$mimetype) = @_;
2626 my ($fullurl) = download_url($url,$revision,$mimetype);
2627 my ($paren) = $textlink =~ /^\(/;
2628 $textlink =~ s/^\(// if ($paren);
2629 $textlink =~ s/\)$// if ($paren);
2630 print "(" if ($paren);
2631 print "<A HREF=\"$fullurl";
2634 if ($open_extern_window && (!defined($mimetype) || $mimetype ne "text/x-cvsweb-markup")) {
2635 print " target=\"cvs_checkout\"";
2637 # 'if (document.cvswin==null) document.cvswin=window.open(...'
2638 # in order to allow the user to resize the window; otherwise
2639 # the user may resize the window, but on next checkout - zap -
2640 # its original (configured s. cvsweb.conf) size is back again
2641 # .. annoying (if $extern_window_(width|height) is defined)
2642 # but this if (..) solution is far from perfect
2643 # what we need to do as well is
2644 # 1) save cvswin in an invisible frame that always exists
2645 # (document.cvswin will be void on next load)
2646 # 2) on close of the cvs_checkout - window set the cvswin
2647 # variable to 'null' again - so that it will be
2648 # reopenend with the configured size
2649 # anyone a JavaScript programmer ?
2650 # .. so here without if (..):
2651 # currently, the best way is to comment out the size parameters
2652 # ($extern_window...) in cvsweb.conf.
2653 if ($use_java_script) {
2654 print " onClick=\"window.open('$fullurl','cvs_checkout',";
2655 print "'resizeable,scrollbars";
2656 print ",status,toolbar" if (defined($mimetype)
2657 && $mimetype eq "text/html");
2658 print ",width=$extern_window_width" if (defined($extern_window_width));
2659 print ",height=$extern_window_height" if (defined($extern_window_height));
2663 print "><b>$textlink</b></A>";
2664 print ")" if ($paren);
2667 # Returns a Query string with the
2668 # specified parameter toggled
2669 sub toggleQuery($$) {
2670 my ($toggle,$value) = @_;
2671 my ($newquery,$var);
2674 if (defined($value)) {
2675 $vars{$toggle} = $value;
2678 $vars{$toggle} = $vars{$toggle} ? 0 : 1;
2680 # Build a new query of non-default paramenters
2682 foreach $var (@stickyvars) {
2683 my ($value) = defined($vars{$var}) ? $vars{$var} : "";
2684 my ($default) = defined($DEFAULTVALUE{$var}) ? $DEFAULTVALUE{$var} : "";
2685 if ($value ne $default) {
2686 $newquery .= "&" if ($newquery ne "");
2687 $newquery .= urlencode($var) . "=" . urlencode($value);
2691 return '?' . $newquery;
2699 ($out = $in) =~ s/([\000-+{-\377])/sprintf("%%%02x", ord($1))/ge;
2703 sub http_header(;$) {
2704 my $content_type = shift || "text/html";
2705 my $is_mod_perl = defined($ENV{'MOD_PERL'});
2706 if (defined($moddate)) {
2708 Apache->request->header_out("Last-Modified" => scalar gmtime($moddate) . " GMT");
2711 print "Last-Modified: " . scalar gmtime($moddate) . " GMT\r\n";
2715 Apache->request->content_type($content_type);
2718 print "Content-type: $content_type\r\n";
2720 if ($allow_compress && $maycompress) {
2721 if ($has_zlib || (defined($GZIPBIN) && open(GZIP, "|$GZIPBIN -1 -c"))) {
2723 Apache->request->content_encoding("x-gzip");
2724 Apache->request->header_out(Vary => "Accept-Encoding");
2725 Apache->request->send_http_header;
2728 print "Content-encoding: x-gzip\r\n";
2729 print "Vary: Accept-Encoding\r\n"; #RFC 2068, 14.43
2730 print "\r\n"; # Close headers
2732 $| = 1; $| = 0; # Flush header output
2734 tie *GZIP, __PACKAGE__, \*STDOUT;
2738 # print "<!-- gzipped -->" if ($content_type eq "text/html");
2742 Apache->request->send_http_header;
2745 print "\r\n"; # Close headers
2747 print "<font size=-1>Unable to find gzip binary in the \$PATH to compress output</font><br>";
2752 Apache->request->send_http_header;
2755 print "\r\n"; # Close headers
2760 sub html_header($) {
2761 &ui_print_header(undef, $text{'cvsweb_title'}, "");
2762 # if (!&has_command("rlog")) {
2763 # print "<p>",&text('cvsweb_ecmd', "<tt>rlog</tt>"),"<p>\n";
2764 # &ui_print_footer("", $text{'index_return'});
2767 if (!&has_command("rlog")) {
2774 &ui_print_footer("", $text{'index_return'});
2782 my ($fileurl,$filename);
2784 ($filename = $where) =~ s/^.*\///;
2785 $fileurl = urlencode($filename);
2787 foreach my $sym (split(", ", $tags)) {
2788 $ret .= ",\n" if ($ret ne "");
2789 $ret .= "<A HREF=\"$fileurl"
2790 . toggleQuery('only_with_tag',$sym) . "\">$sym</A>";
2796 # See if a module is listed in the config file's @HideModule list.
2798 sub forbidden_module($) {
2800 return checkForbidden($module, @HideModules);
2803 sub forbidden_file($) {
2806 return checkForbidden($file, @DissallowRead);
2809 sub checkForbidden($@) {
2810 my($item, @list) = @_;
2811 for (my $i=0; $i < @list; $i++) {
2812 return 1 if $item =~ $list[$i];
2817 # Close the GZIP handle remove the tie.
2828 # implement a gzipped file handle via the Compress:Zlib compression
2831 sub MAGIC1() { 0x1f }
2832 sub MAGIC2() { 0x8b }
2836 my ($class, $out) = @_;
2837 my ($d) = Compress::Zlib::deflateInit(-Level => Compress::Zlib::Z_BEST_COMPRESSION(),
2838 -WindowBits => -Compress::Zlib::MAX_WBITS()) or return undef;
2845 my ($header) = pack("c10", MAGIC1, MAGIC2, Compress::Zlib::Z_DEFLATED(), 0,0,0,0,0,0, OSCODE);
2846 print {$o->{handle}} $header;
2847 return bless($o, $class);
2852 my ($buf) = join(defined $, ? $, : "",@_);
2853 my ($len) = length($buf);
2854 my ($compressed, $status) = $o->{dh}->deflate($buf);
2855 print {$o->{handle}} $compressed if defined($compressed);
2856 $o->{crc} = Compress::Zlib::crc32($buf, $o->{crc});
2864 my ($buf) = sprintf($fmt, @_);
2865 my ($len) = length($buf);
2866 my ($compressed, $status) = $o->{dh}->deflate($buf);
2867 print {$o->{handle}} $compressed if defined($compressed);
2868 $o->{crc} = Compress::Zlib::crc32($buf, $o->{crc});
2874 my ($o, $buf, $len, $off) = @_;
2875 my ($compressed, $status) = $o->{dh}->deflate(substr($buf, 0, $len));
2876 print {$o->{handle}} $compressed if defined($compressed);
2877 $o->{crc} = Compress::Zlib::crc32(substr($buf, 0, $len), $o->{crc});
2884 return if !defined( $o->{dh});
2885 my ($buf) = $o->{dh}->flush();
2886 $buf .= pack("V V", $o->{crc}, $o->{len});
2887 print {$o->{handle}} $buf;