#!/usr/bin/perl -w # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Netscape Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/NPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bonsai CVS tool. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): # cvslog.cgi -- cvslog with logs as popups and allowing html in comments. # # Created: Steve Lamm , 31-Mar-98. # # Arguments (passed via GET or POST): # file - path to file name (e.g. ns/cmd/xfe/Makefile) # root - cvs root (e.g. /warp/webroot) # rev - revision (default is the latest version) # mark - highlight a revision # author - filter based on author # use strict; # Shut up misguided -w warnings about "used only once". "use vars" just # doesn't work for me. sub sillyness { my $zz; $zz = $::CVS_ROOT; $zz = $::head_revision; $zz = $::revision_ctime; $zz = $::revision_log; } require 'CGI.pl'; require 'cvsblame.pl'; use Date::Parse; use Date::Format; # Some Globals # $| = 1; my @src_roots = getRepositoryList(); # Handle the "file" argument # my $filename = ''; $filename = $::FORM{'file'} if defined($::FORM{'file'}); if ($filename eq '') { print "Content-Type:text/html\n\n"; &print_usage; PutsTrailer(); exit; } my ($file_head, $file_tail) = $filename =~ m@(.*/)?(.+)@; my $url_filename = url_quote($filename); my $url_file_tail = url_quote($file_tail); # Handle the "rev" argument # $::opt_rev = ""; $::opt_rev = &SanitizeRevision($::FORM{'rev'}) if defined $::FORM{'rev'} && $::FORM{'rev'} !~ m/^(HEAD|MAIN)$/; my $revstr = ''; $revstr = "&rev=$::opt_rev" unless $::opt_rev eq ''; my $browse_revtag = 'HEAD'; $browse_revtag = $::opt_rev if ($::opt_rev =~ /[A-Za-z]|^\d+(?:\.\d+)*$/); my $revision = ''; # Handle the "root" argument # my $root = $::FORM{'root'}; if (defined $root && $root ne '') { $root =~ s|/$||; validateRepository($root); if (-d $root) { unshift(@src_roots, $root); } else { print "Content-Type:text/html\n\n"; &print_top; print "Error: Root, $root, is not a directory.

\n"; PutsTrailer(); exit; } } # Find the rcs file # my $rcs_filename; my $found_rcs_file = 0; foreach (@src_roots) { $root = $_; $rcs_filename = "$root/$filename,v"; $rcs_filename = Fix_BonsaiLink($rcs_filename); $found_rcs_file = 1, last if -r $rcs_filename; $rcs_filename = "$root/${file_head}Attic/$file_tail,v"; $found_rcs_file = 1, last if -r $rcs_filename; } # File not found unless ($found_rcs_file) { print "Content-Type:text/html\n\n"; &print_top; my $escaped_filename = html_quote($filename); print "Rcs file, $escaped_filename, does not exist.

\n"; PutsTrailer(); exit; } my $rcs_path; my $url_rcs_path; ($rcs_path) = $rcs_filename =~ m@$root/(.*)/.+?,v@; $url_rcs_path = &url_quote($rcs_path); # Parse the rcs file ($::opt_rev is passed as a global) # $revision = &parse_cvs_file($rcs_filename); my $file_rev = $revision; my $start_rev; if ($browse_revtag eq 'HEAD') { $start_rev = $::head_revision; # $::head_revision is a global from cvsblame.pl } elsif ($browse_revtag =~ /^\d+(?:\.\d+)*$/) { $start_rev = $browse_revtag; } else { $start_rev = map_tag_to_revision($browse_revtag); } # Handle the "mark" argument # my %mark; my $mark_arg = &SanitizeMark($::FORM{'mark'}); foreach my $rev (split(',',$mark_arg)) { $mark{$rev} = 1 if ($rev =~ m/^\d+$/); } # Handle the "author" argument # my %use_author; my $author_arg = &SanitizeUsernames($::FORM{'author'}); foreach my $author (split(',',$author_arg)) { $use_author{$author} = 1; } my $url_author_arg = url_quote($author_arg); # Handle the "sort" argument my $opt_sort = ''; $opt_sort = $::FORM{'sort'} if defined $::FORM{'sort'}; my $sortstr = $opt_sort eq "author" ? "&sort=author" : ""; print "Last-Modified: ".time2str("%a, %d %b %Y %T %Z", str2time($::revision_ctime{$start_rev}), "GMT")."\n"; print "Expires: ".time2str("%a, %d %b %Y %T %Z", time+1200, "GMT")."\n"; my $ctype = $::FORM{ctype} || "html"; if ($ctype eq "rss") { print "Content-Type: application/rss+xml\n\n"; display_rss(); } else { print "Content-Type: text/html\n\n"; display_html(); } exit; ## END of main script sub display_html { # Start printing out the page # &print_top; print Param('bannerhtml', 1); # Print link at top for directory browsing # print q( --endquote-- print " ("; print "$browse_revtag:" unless $browse_revtag =~ /^(?:HEAD|\d+(?:\.\d+)*)$/; print $revision if $revision; print ")"; print qq(
CVS Log
); my $link_path; my $lxr_path; foreach my $path (split('/',$rcs_path)) { $link_path .= url_encode2($path).'/'; $lxr_path = Fix_LxrLink($link_path); print "$path/ "; } $lxr_path = Fix_LxrLink("$link_path$file_tail"); print "" . &html_quote($file_tail) . " "; my $graph_cell = Param('cvsgraph') ? <<"--endquote--" : "";
graph  View the revision history as a graph
$graph_cell
lxr Browse the source code as hypertext.
diff Compare any two version.
blame  Annotate the author of each line.
RSS  Get an RSS version of this page.
); #&print_useful_links($filename); # Create a table with header links to sort by column. # my $table_tag = ""; my $table_header_tag = ""; if ($opt_sort eq 'author') { $table_header_tag .= "
RevAuthorDateLog"; } else { $table_header_tag .= "RevAuthorDateLog"; } print "$table_tag$table_header_tag"; # Print each line of the revision, preceded by its annotation. # my $row_count = 0; my $max_rev_length = length($start_rev); my $max_author_length = 8; my @revisions = ($start_rev, ancestor_revisions($start_rev)); @revisions = sort by_author @revisions if $opt_sort eq 'author'; #@revisions = sort by_author @revisions if $opt_sort eq 'date' && $rev eq 'all'; my $bgcolor; foreach $revision (@revisions) { my $author = $::revision_author{$revision}; next unless $author_arg eq '' || $use_author{$author}; my $log = $::revision_log{$revision}; $log =~ s/&/&/g; $log =~ s//>/g; $log = MarkUpText($log); $log =~ s/\n|\r|\r\n/
/g; if ($revision eq $::opt_rev) { $bgcolor = ' BGCOLOR="LIGHTBLUE"'; } elsif ($mark{$revision}) { $bgcolor = ' BGCOLOR="LIGHTGREEN"'; } elsif (!defined $bgcolor || $bgcolor eq '') { $bgcolor = ' BGCOLOR="#E7E7E7"'; # Pick a grey that shows up on 8-bit. } else { $bgcolor = ''; } my $output = ''; $row_count++; if ($row_count > 20) { $output .= "
\n$table_tag"; $row_count = 0; } $output .= "" ."$revision" .' ' x ($max_rev_length - length($revision)).''; $output .= "".$author .' ' x ($max_author_length - length($author)).''; my $rev_time = $::revision_ctime{$revision}; # $rev_time =~ s/(19\d\d) (.\d:\d\d)/$1
$2<\/FONT>/; # jwz: print the date the way "ls" does. # # What ls does is actually: print "Mmm DD HH:MM" unless the file is # more than six months old, or more than 1 hour in the future, in # which case, print "Mmm DD YYYY". # # What the following does is: "Mmm DD HH:MM" unless the year is not # the current year; else print "Mmm DD YYYY". # # If we had $rev_time as an actual time_t instead of as a string, # it would be easy to do the "ls" thing (see the code I wrote for # this in "lxr/source"). -jwz, 15-Jun-98. # { my $current_time = time; my @t = gmtime($current_time); my ($csec, $cmin, $chour, $cmday, $cmon, $cyear) = @t; $cyear += 1900; $_ = $rev_time; $rev_time = "$rev_time"; } $output .= "$rev_time"; $output .= " $log"; $output .= "\n"; print $output; } print ""; PutsTrailer(); } sub display_rss { my @revisions = ($start_rev, ancestor_revisions($start_rev)); @revisions = sort by_author @revisions if $opt_sort eq 'author'; # The escaped path+name of the file; included in the channel title. my $html_filename = html_quote($filename); # The canonical URL of the file; we use a link to its revision history, # but arguably this should be a link to the LXR page or, for web pages, # a link to the actual web page. my $link = Param("urlbase") . "cvslog.cgi?file=$url_filename"; $link .= "&root=$root" if $::FORM{'root'} ne ""; $link .= $revstr; $link .= $sortstr; $link .= "&author=$url_author_arg" if $author_arg ne ""; $link = value_quote($link); print <<"END"; Revision History for $html_filename $link Revision History for $html_filename END # We loop over revisions twice. The first time, here, it's just to populate # the "items" list of revisions inside the channel tag. All we need to know # is whether to skip this author and the link to the revision. foreach my $revision (@revisions) { next unless $author_arg eq '' || $use_author{$::revision_author{$revision}}; my $link = value_quote(revision_link($revision)); print <<"END"; END } print <<"END"; END # The second time we loop over revisions, it's to generate "item" resources # for each one. foreach my $revision (@revisions) { my $author = $::revision_author{$revision}; next unless $author_arg eq '' || $use_author{$author}; $author = html_quote($author); my $link = value_quote(revision_link($revision)); my $log = $::revision_log{$revision}; $log = html_quote($log); $log = MarkUpText($log); $log =~ s/\n|\r|\r\n/
/g; my $date = date_to_w3cdtf($::revision_ctime{$revision}); print <<"END"; Version $revision $link $author $date END } print <<"END";
END } sub revision_link { my $revision = shift; my $link = Param('urlbase') . "cvsview2.cgi"; if (defined($::prev_revision{$revision})) { $link .= "?diff_mode=context&whitespace_mode=show&file=$url_file_tail" . "&branch=$::opt_rev&root=$root&subdir=$url_rcs_path" . "&command=DIFF_FRAMESET&rev1=$::prev_revision{$revision}" . "&rev2=$revision"; } else { $link .= "?files=$url_file_tail&root=$root&subdir=$url_rcs_path" . "\&command=DIRECTORY\&rev2=$revision&branch=$::opt_rev"; $link .= "&branch=$browse_revtag" unless $browse_revtag eq 'HEAD'; } return $link; } # Format a date/time in W3CDTF per http://www.w3.org/TR/NOTE-datetime sub date_to_w3cdtf { my ($date) = @_; $date = str2time($date); # Date::Format returns timezone offsets without a colon, while W3CDTF # requires a colon between the hour and minute offsets, so we have to # convert the timezone specially. my $timezone = time2str("%z", $date); $timezone =~ s/([+-])(\d\d)(\d\d)/$1$2:$3/; $date = time2str("%Y-%m-%dT%R", $date) . $timezone; return $date; } sub by_revision { my (@a_parts) = split(/\./,$a); my (@b_parts) = split(/\./,$b); while(1) { my ($aa) = shift @a_parts; my ($bb) = shift @b_parts; return 1 if $aa eq ''; return -1 if $bb eq ''; return $bb <=> $aa if $aa ne $bb; } } sub by_author { my ($a_author) = $::revision_author{$a}; my ($b_author) = $::revision_author{$b}; return $a_author cmp $b_author if $a_author ne $b_author; return by_revision; } sub sprint_author { my ($revision) = @_; my ($author) = $::revision_author{$revision}; return } sub print_top { my ($title_text) = "for " . &html_quote($file_tail) . " ("; $title_text .= "$browse_revtag:" unless $browse_revtag =~ /^(?:HEAD|\d+(?:\.\d+)*)$/; $title_text .= $revision if $revision; $title_text .= ")"; $title_text =~ s/\(\)//; print <<__TOP__; CVS Log $title_text __TOP__ } # print_top sub print_usage { my ($linenum_message) = ''; my ($new_linenum, $src_roots_list); my ($title_text) = "Usage"; $src_roots_list = join('
', @src_roots); print <<__USAGE__; CVS Log $title_text

CVS Log Usage

Add parameters to the query string to view a file.

Param Default Example Description
file -- ns/cmd/Makefile Path to file name
root $src_roots_list /warp/webroot CVS root
rev HEAD 1.3
ACTRA_branch
Revision
author -- slamm,mtoy Only show changes by these authors
#<rev_number> -- #1.2 Jump to a revision

Examples:
  cvslog.cgi?file=ns/cmd/Makefile
  cvslog.cgi?file=ns/cmd/xfe/mozilla.c&rev=Dogbert4xEscalation_BRANCH
  cvslog.cgi?file=projects/bonsai/cvslog.cgi&root=/warp/webroot
  cvslog.cgi?file=ns/cmd/xfe/dialogs.c#1.19

You may also begin a query with the CVS Query Form.

__USAGE__ } sub print_useful_links { my ($path) = @_; my ($dir, $file) = $path =~ m@(.*/)?(.+)@; $dir =~ s@/$@@; my $url_dir = &url_quote($dir); my $url_file = &url_quote($file); my $diff_base = "cvsview2.cgi"; my $blame_base = "cvsblame.cgi"; my $lxr_path = $path; my $lxr_link = Fix_LxrLink($lxr_path); my $url_path = &url_quote($path); my $diff_link = "$diff_base?command=DIRECTORY\&subdir=$url_dir\&files=$url_file\&branch=$::opt_rev"; my $blame_link = "$blame_base?root=$::CVS_ROOT\&file=$url_path\&rev=$::opt_rev"; print "
lxr: browse the source code as hypertext.
diff: compare any two versions.
blame: annotate the author of each line.
"; }