diff --git a/mozilla/webtools/bonsai/CGI.tcl b/mozilla/webtools/bonsai/CGI.tcl new file mode 100755 index 00000000000..f4f5fd00547 --- /dev/null +++ b/mozilla/webtools/bonsai/CGI.tcl @@ -0,0 +1,128 @@ +# -*- Mode: tcl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Netscape Public License +# Version 1.0 (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. + +source globals.tcl + +# Takes something that was quoted for a URL line (%20 for spaces and stuff), +# and returns back the intended string. +proc url_decode {buf} { + regsub -all {\\(.)} $buf {\1} buf ; regsub -all {\\} $buf {\\\\} buf ; + regsub -all { } $buf {\ } buf ; regsub -all {\+} $buf {\ } buf ; + regsub -all {\$} $buf {\$} buf ; regsub -all \n $buf {\n} buf ; + regsub -all {;} $buf {\;} buf ; regsub -all {\[} $buf {\[} buf ; + regsub -all \" $buf \\\" buf ; regsub ^\{ $buf \\\{ buf ; + regsub -all -nocase {%([a-fA-F0-9][a-fA-F0-9])} $buf {[format %c 0x\1]} buf + regsub -all "\r\n" $buf "\n" buf + regsub -all "\r" $buf "\n" buf + eval return \"$buf\" +} + +proc lookup { a key } { + global $a + set ref [format %s(%s) $a $key] + if { [ info exists $ref] } { + eval return \$$ref + } else { + return "" + } +} + +proc FormData { field } { + global FORM + return [join $FORM($field)] +} + +proc ProcessFormFields {buffer} { + global FORM treeid + catch {unset FORM} + foreach item [ split $buffer & ] { + set el [ split $item = ] + set value [url_decode [lindex $el 1]] + set name [lindex $el 0] + if { $value != "" } { + lappend FORM($name) $value + } else { + set isnull($name) 1 + } + } + if {[info exists isnull]} { + foreach name [array names isnull] { + if {![info exists FORM($name)]} { + set FORM($name) "" + } + } + } + if {[info exists FORM(treeid)]} { + set treeid $FORM(treeid) + } + if {[info exists FORM(batchid)]} { + global batchid readonly + source [DataDir]/batchid + if {$batchid != $FORM(batchid)} { + set batchid $FORM(batchid) + set readonly 1 + } + } +} + + +proc BatchIdPart {{initstr ""}} { + global treeid batchid readonly + set result "" + if {![cequal $treeid "default"] || $readonly} { + set result $initstr + } + if {![cequal $treeid "default"]} { + append result "&treeid=$treeid" + } + if {$readonly} { + append result "&batchid=$batchid" + } + return $result +} + + +if { [info exists env(REQUEST_METHOD) ] } { + if { $env(REQUEST_METHOD) == "GET" } { + set buffer [lookup env QUERY_STRING] + } else { set buffer [ read stdin $env(CONTENT_LENGTH) ] } + ProcessFormFields $buffer +} + +proc value_quote {var} { + regsub -all {&} "$var" {\&} var + regsub -all {"} "$var" {\"} var + regsub -all {<} "$var" {\<} var + regsub -all {>} "$var" {\>} var + return $var +} + +foreach pair [ split [lookup env HTTP_COOKIE] ";" ] { + set pair [string trim $pair] + set eq [string first = $pair ] + if {$eq == -1} { + set COOKIE($pair) "" + } else { + set COOKIE([string range $pair 0 [expr $eq - 1]]) [string range $pair [expr $eq + 1] end] + } +} + + +proc PutsTrailer {} { + puts "
Error: $message
"; + $error_header = ''; + } + + +sub print_query_building_form() + { + print $query->start_multipart_form; + + print '...to modify or create a remote dictionary with words from one or more local files.
'; + + print '...i.e., the dictionary to be created or modified.
'; + + print $query->textfield( -name=>'dictionary', + -default=>'', + -override=>1, + -size=>30 ); + print '-- the path to dictionary.'; + + print '...that will be uploaded to the server, so their contents can be added to the dictionary.
'; + + print 'Questions, Comments, Feature requests? mail ltabb +
In the course of software development, it is necessary to form a branch +to do development on for a period of time. Sometimes you want to merge these +changes back into the trunk in one shot. Sometime you want to have the developers +merge the changes themselves, individually. This tool makes sure the developers +have merged their changes in individually. + +
The CVS Branch Spammer goes out and figures out what changes were made on +a branch and then looks to see if these changes where also made on the tip. It +formulates a mail message and send the mail to the indivual developers. The +individual developers look at the mail and reply that they have made their +changes in the tip. + +
To run this program answer the following questions and bonk the spam button. + + +
+
"; + diff --git a/mozilla/webtools/bonsai/branchspammer.cgi b/mozilla/webtools/bonsai/branchspammer.cgi new file mode 100755 index 00000000000..73dfff2e5a6 --- /dev/null +++ b/mozilla/webtools/bonsai/branchspammer.cgi @@ -0,0 +1,100 @@ +#!/usr/bonsaitools/bin/perl -- +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Netscape Public License +# Version 1.0 (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. + + +require 'lloydcgi.pl'; +require 'timelocal.pl'; +require 'cvsquery.pl'; + +$| = 1; + +print "Content-type: text/html + +"; + +$CHECKIN_DATA_FILE = 'data/checkinlog_m_src'; +$CHECKIN_INDEX_FILE = 'data/index_m_src'; + +# +# build a module map +# +$query_module = $form{'module'}; + +@query_dirs = split(/[;, \t]+/, $form{'dir'}); + +$query_date_type = $form{'date'}; +$query_date_min = time-(24*60*60*15); +$query_who ='' ; +$query_branch = $form{'branch'}; + + +print "";
+for $ci (@$result) {
+ $ci->[$CI_LOG] = '';
+ $s = join("|",@$ci);
+ print "$s\n";
+}
+
diff --git a/mozilla/webtools/bonsai/changebar.tcl b/mozilla/webtools/bonsai/changebar.tcl
new file mode 100755
index 00000000000..a50d775de92
--- /dev/null
+++ b/mozilla/webtools/bonsai/changebar.tcl
@@ -0,0 +1,83 @@
+#!/usr/bonsaitools/bin/mysqltcl
+# -*- Mode: tcl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+# Takes two files, and creates a file with changebars.
+
+set file1 [lvarpop argv]
+set file2 [lvarpop argv]
+
+set data(foo) {}
+unset data(foo)
+
+set count 0
+for_file line $file1 {
+ incr count
+ set data($count) $line
+ set mark($count) " "
+}
+
+set fid [open "|/usr/local/bin/diff -b -e $file1 $file2" "r"]
+
+
+proc Delete {first last} {
+ global data
+ for {set i $first} {$i<=$last} {incr i} {
+ unset data($i)
+ }
+}
+
+proc Add {first fid} {
+ global data mark
+ set sub 10000
+ while {[gets $fid line] >= 0} {
+ if {[cequal $line "."]} {
+ break
+ }
+ set data($first.$sub) $line
+ set mark($first.$sub) "|"
+ incr sub
+ }
+}
+
+
+
+while {[gets $fid line] >= 0} {
+ if {![regexp {^(.*),(.*)(a|c|d)$} $line foo first last]} {
+ set first [crange $line 0 end-1]
+ set last $first
+ }
+ switch [cindex $line end] {
+ d {
+ Delete $first $last
+ }
+ c {
+ Delete $first $last
+ Add $first $fid
+ }
+ a {
+ Add $first $fid
+ }
+ }
+}
+
+catch {close $fid}
+
+foreach i [lsort -real [array names data]] {
+ puts "$mark($i) $data($i)"
+}
diff --git a/mozilla/webtools/bonsai/closemessage b/mozilla/webtools/bonsai/closemessage
new file mode 100644
index 00000000000..997eb53b7fc
--- /dev/null
+++ b/mozilla/webtools/bonsai/closemessage
@@ -0,0 +1,23 @@
+From: bonsai-daemon
+To: %name%
+Subject: [Bonsai] Hey! You checked in while the tree was closed!
+Mime-Version: 1.0
+Content-Type: text/html
+
+
+
+Boy, you better have had permission!
+
+You just checked into %dir% the files %files%. The
+tree is currently frozen. You better have had permission from the build group
+to make a checkin; otherwise, you're in deep doo-doo.
+
+
+
+Your contact info and other vital data is listed below. Please
+update
+this info immediately if it is at all inaccurate or incorrect.
+
+
+
+%profile%
diff --git a/mozilla/webtools/bonsai/contacthelp.html b/mozilla/webtools/bonsai/contacthelp.html
new file mode 100644
index 00000000000..7c3574c50a5
--- /dev/null
+++ b/mozilla/webtools/bonsai/contacthelp.html
@@ -0,0 +1,27 @@
+
+Changing other people's contact info.
+
+
+
+Changing other people's contact info.
+
+Occasionally, you need to change the "contact info" listed for some
+other person. (Like, they just called you on their celphone from the
+horrible traffic accident they just got in, and need you to go on the
+hook for them.) Well, it's easy. Go ahead onto their contact page,
+change the contact info field, and type your own username and UNIX
+password to the form. It'll work.
+
+Note that you're only allowed to change the "Current Contact Info"
+field this way. It won't let you change anything else.
+
+
+
+Back to the top of Bonsai
+
+
+Terry Weissman <terry@netscape.com>
+
+Last modified: Wed Oct 30 13:03:35 1996
+
+
diff --git a/mozilla/webtools/bonsai/countcheckins.cgi b/mozilla/webtools/bonsai/countcheckins.cgi
new file mode 100755
index 00000000000..84273a42d31
--- /dev/null
+++ b/mozilla/webtools/bonsai/countcheckins.cgi
@@ -0,0 +1,88 @@
+#!/usr/bonsaitools/bin/mysqltcl
+# -*- Mode: tcl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+source CGI.tcl
+
+set maxsize 400
+
+LoadCheckins
+
+puts "Content-type: text/html
+
+
+Beancounter central.
+
+Meaningless checkin statistics
+
+| Tree closed | +Number of people making changes |
+Number of checkins | +||
|---|---|---|---|---|
| $donetime($i) | " + puts "$numpeople($i) | " + puts "$num($i) | " + puts "||
";
+} else {
+ # We don't want your stinking Windows font
+ #$font_tag = "";
+ $font_tag = "";
+}
+
+# Print link at top for directory browsing
+#
+$output = "";
+foreach $path (split('/',$rcs_path)) {
+ $link_path .= $path;
+ $output .= "$path/ ";
+ $link_path .= '/';
+}
+$output .= "$file_tail "
+ ." (";
+$output .= "$browse_revtag:" unless $browse_revtag eq 'HEAD';
+$output .= $revision if $revision;
+$output .= ")";
+
+$output .= "";
+EmitHtmlHeader("CVS Blame", $output);
+print "
\n";
+
+print $font_tag;
+
+# Print each line of the revision, preceded by its annotation.
+#
+$start_of_mark = 0;
+$end_of_mark = 0;
+$line_num_width = int(log($#revision_map)/log(10)) + 1;
+$revision_width = 3;
+$author_width = 5;
+$line = 0;
+$usedlog{$revision} = 1;
+foreach $revision (@revision_map)
+{
+ $text = $text[$line++];
+ $usedlog{$revision} = 1;
+
+
+ if ($opt_html_comments) {
+ # Don't escape HTML in C/C++ comments
+ $text = &leave_html_comments($text);
+ } elsif ( $opt_sanitize ){
+ # Mark filty words and Escape HTML meta-characters
+ $text = markup_line($text);
+ } else {
+ $text =~ s/&/&/g;
+ $text =~ s/</g;
+ $text =~ s/>/>/g;
+ }
+ # Add a link to traverse to included files
+ $text = &link_includes($text) if $opt_includes;
+
+
+ $output = "";
+
+ # Highlight lines
+ if (defined($mark_cmd = $mark_line{$line})
+ && $mark_cmd ne 'end') {
+
+ $output .= "$font_tag";
+ }
+
+ $output .= sprintf("%${line_num_width}s ", $line) if $opt_line_nums;
+
+ if ($old_revision ne $revision || $rev_count > 20) {
+
+ $author_width = max($author_width,length($revision_author{$revision}));
+ $revision_width = max($revision_width,length($revision));
+
+ $output .= ($rev_count > 20 ? '| ' : '+ ');
+
+ $output .= sprintf("%-${author_width}s ",$revision_author{$revision});
+
+ $output .= "$revision ";
+ $output .= ' ' x ($revision_width - length($revision));
+
+ $old_revision = $revision;
+ $rev_count = 0;
+ } else {
+ $output .= '| ' . ' ' x ($author_width + $revision_width);
+ }
+ $rev_count++;
+
+ $output .= "$text";
+
+ # Close the highlighted section
+ if (defined($mark_cmd) && $mark_cmd ne 'begin') {
+ chop($output);
+ $output .= " ";
+ #if( defined($prev_revision{$file_rev})) {
+ $output .= "Previous Revision ($prev_revision{$file_rev})   ";
+ #}
+ $output .= "
";
+ }
+
+ print $output;
+}
+print "
\n";
+
+# Write out cvs log messages as a JS variables
+#
+print "";
+
+&print_bottom;
+
+print "
(This page is much cooler with a layers enabled browser) ";
+
+if ( $opt_sanitize )
+{
+ dbmclose %SourceChecker::token_dictionary;
+}
+
+## END of main script
+
+sub max {
+ local ($a, $b) = @_;
+ return ($a > $b ? $a : $b);
+}
+
+sub print_top {
+ local ($title_text) = "for $file_tail (";
+ $title_text .= "$browse_revtag:" unless $browse_revtag eq 'HEAD';
+ $title_text .= $revision if $revision;
+ $title_text .= ")";
+ $title_text =~ s/\(\)//;
+
+ local ($diff_dir_link) =
+ "cvsview2.cgi?subdir=$rcs_path&files=$file_tail&command=DIRECTORY";
+ $diff_dir_link .= "&root=$form{'root'}" if defined $form{'root'};
+ $diff_dir_link .= "&branch=$browse_revtag" unless $browse_revtag eq 'HEAD';
+
+ local ($diff_link) = "cvsview2.cgi?diff_mode=context&whitespace_mode=show";
+ $diff_link .= "&root=$form{'root'}" if defined $form{'root'};
+ $diff_link .= "&subdir=$rcs_path&command=DIFF_FRAMESET&file=$file_tail";
+
+ print <<__TOP__;
+
+
+CVS Blame $title_text
+
+
+
+
+__TOP__
+} # print_top
+
+sub print_usage {
+ local ($linenum_message) = '';
+ local ($new_linenum, $src_roots_list);
+ local ($title_text) = "Usage";
+
+ if ($ENV{"REQUEST_METHOD"} eq 'POST' && defined($form{'set_line'})) {
+
+ # Expire the cookie 5 months from now
+ $set_cookie = "Set-Cookie: line_nums=$form{'set_line'}; expires="
+ .&toGMTString(time + 86400 * 152)."; path=/";
+ }
+ if (!defined($cookie_jar{'line_nums'}) && !defined($form{'set_line'})) {
+ $new_linenum = 'on';
+ } elsif ($cookie_jar{'line_nums'} eq 'off' || $form{'set_line'} eq 'off') {
+ $linenum_message = 'Line numbers are currently off.';
+ $new_linenum = 'on';
+ } else {
+ $linenum_message = 'Line numbers are currently on.';
+ $new_linenum = 'off';
+ }
+ $src_roots_list = join('
', @src_roots);
+
+ print <<__USAGE__;
+
+
+CVS Blame $title_text
+
+CVS Blame 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
+
+ line_nums
+ off *
+ on
+
off
+ line numbers
+
+ #<line_number>
+ --
+ #111
+ jump to a line
+
+
+
+Examples:
+
+You may also begin a query with the CVS Query Form.
+
+__USAGE__
+ &print_bottom;
+} # sub print_usage
+
+sub print_bottom {
+ print <<__BOTTOM__;
+
+
+Page configuration and help.
+Mail feedback to <slamm\@netscape.com>.
+
+
+__BOTTOM__
+} # print_bottom
+
+
+sub link_includes {
+ local ($text) = $_[0];
+
+ if ($text =~ /\#(\s*)include(\s*)"(.*?)"/) {
+ foreach $trial_root (($rcs_path, 'ns/include',
+ "$rcs_path/Attic", "$rcs_path/..")) {
+ if (-r "$root/$trial_root/$3,v") {
+ $text = "$`#$1include$2\"$3\";$'";
+ last;
+ }
+ }
+ }
+ return $text;
+}
+
+sub html_comments_init {
+ return 0 unless defined($form{'use_html'}) && $form{'use_html'};
+
+ # Initialization for C comment context switching
+ $in_comments = 0;
+ $open_delim = '\/\*';
+ $close_delim = '\*\/';
+
+ # Initialize the next expected delim
+ $expected_delim = $open_delim;
+
+ return 1;
+}
+
+sub leave_html_comments {
+ local ($text) = $_[0];
+ # Allow HTML in the comments.
+ #
+ $newtext = "";
+ $oldtext = $text;
+ while ($oldtext =~ /(.*$expected_delim)(.*\n)/) {
+ $a = $1;
+ $b = $2;
+ # pay no attention to C++ comments within C comment context
+ if ($in_comments == 0) {
+ $a =~ s/</g;
+ $a =~ s/>/>/g;
+ $expected_delim = $close_delim;
+ $in_comments = 1;
+ }
+ else {
+ $expected_delim = $open_delim;
+ $in_comments = 0;
+ }
+ $newtext = $newtext . $a;
+ $oldtext = $b;
+ }
+ # Handle thre remainder
+ if ($in_comments == 0){
+ $oldtext =~ s/</g;
+ $oldtext =~ s/>/>/g;
+ }
+ $text = $newtext . $oldtext;
+
+ # Now fix the breakage of stuff on xfe. -byrd
+ if ($text =~ /(.*)<(.*@.*)>(.*\n)/) {
+ $text = $1 . "$2" . $3;
+ }
+
+ return $text;
+}
diff --git a/mozilla/webtools/bonsai/cvsblame.pl b/mozilla/webtools/bonsai/cvsblame.pl
new file mode 100755
index 00000000000..c731dcb8a40
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsblame.pl
@@ -0,0 +1,816 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+##############################################################################
+#
+# cvsblame.pl - Shamelessly adapted from Scott Furman's cvsblame script
+# by Steve Lamm (slamm@netscape.com)
+# - Annotate each line of a CVS file with its author,
+# revision #, date, etc.
+#
+# Report problems to Steve Lamm (slamm@netscape.com)
+#
+##############################################################################
+
+# $Id: cvsblame.pl,v 1.1 1998-06-16 21:42:56 terry Exp $
+
+require 'timelocal.pl'; # timestamps
+require 'ctime.pl'; # human-readable dates
+
+$debug = 0;
+
+# Extract base part of this script's name
+($progname) = $0 =~ /([^\/]+)$/;
+
+&cvsblame_init;
+
+1;
+
+sub cvsblame_init {
+ # Use default formatting options if none supplied
+ if (!$opt_A && !$opt_a && !$opt_d && !$opt_v) {
+ $opt_a = 1;
+ $opt_v = 1;
+ }
+
+ $time = time;
+ $SECS_PER_DAY = 60 * 60 * 24;
+
+ # Timestamp threshold at which annotations begin to occur, if -m option present.
+ $opt_m_timestamp = $time - $opt_m * $SECS_PER_DAY;
+}
+
+# Generic traversal of a CVS tree. Invoke callback function for
+# individual directories that contain CVS files.
+sub traverse_cvs_tree {
+ local ($dir, *callback, $nlink) = @_;
+ local ($dev, $ino, $mode, $subcount);
+
+ # Get $nlink for top-level directory
+ ($dev, $ino, $mode, $nlink) = stat($dir) unless $nlink;
+
+ # Read directory
+ opendir(DIR, $dir) || die "Can't open $dir\n";
+ local(@filenames) = readdir(DIR);
+ closedir(DIR);
+
+ return if ! -d "$dir/CVS";
+
+ &callback($dir);
+
+ # This dir has subdirs
+ if ($nlink != 2) {
+ $subcount = $nlink - 2; # Number of subdirectories
+ for (@filenames) {
+ last if $subcount == 0;
+ next if $_ eq '.';
+ next if $_ eq '..';
+ next if $_ eq 'CVS';
+ $name = "$dir/$_";
+
+ ($dev, $ino, $mode, $nlink) = lstat($name);
+ next unless -d _;
+ if (-x _ && -r _) {
+ print STDERR "$progname: Entering $name\n";
+ &traverse_cvs_tree($name, *callback, $nlink);
+ } else {
+ warn("Couldn't chdir to $name");
+ }
+ --$subcount;
+ }
+ }
+}
+
+# Consume one token from the already opened RCSFILE filehandle.
+# Unescape string tokens, if necessary.
+sub get_token {
+ # Erase all-whitespace lines.
+ while ($line_buffer =~ /^$/) {
+ die ("Unexpected EOF") if eof(RCSFILE);
+ $line_buffer = ;
+ $line_buffer =~ s/^\s+//; # Erase leading whitespace
+ }
+
+ # A string of non-whitespace characters is a token ...
+ return $1 if ($line_buffer =~ s/^([^;@][^;\s]*)\s*//o);
+
+ # ...and so is a single semicolon ...
+ return ';' if ($line_buffer =~ s/^;\s*//o);
+
+ # ...or an RCS-encoded string that starts with an @ character.
+ $line_buffer =~ s/^@([^@]*)//o;
+ $token = $1;
+
+ # Detect single @ character used to close RCS-encoded string.
+ while ($line_buffer !~ /@/o || # Short-circuit optimization
+ $line_buffer !~ /([^@]|^)@([^@]|$)/o) {
+ $token .= $line_buffer;
+ die ("Unexpected EOF") if eof(RCSFILE);
+ $line_buffer = ;
+ }
+
+ # Retain the remainder of the line after the terminating @ character.
+ $i = rindex($line_buffer, '@');
+ $token .= substr($line_buffer, 0, $i);
+ $line_buffer = substr($line_buffer, $i + 1);
+
+ # Undo escape-coding of @ characters.
+ $token =~ s/@@/@/og;
+
+ return $token;
+}
+
+# Consume a token from RCS filehandle and ensure that it matches
+# the given string constant.
+sub match_token {
+ local ($match) = @_;
+
+ local ($token) = &get_token;
+ die "Unexpected parsing error in RCS file $rcs_pathname.\n",
+ "Expected token: $match, but saw: $token\n"
+ if ($token ne $match);
+}
+
+# Push RCS token back into the input buffer.
+sub unget_token {
+ local ($token) = @_;
+ $line_buffer = $token . " " . $line_buffer;
+}
+
+# Parses "administrative" header of RCS files, setting these globals:
+#
+# $head_revision -- Revision for which cleartext is stored
+# $principal_branch
+# $file_description
+# %revision_symbolic_name -- mapping from numerical revision # to symbolic tag
+# %tag_revision -- mapping from symbolic tag to numerical revision #
+#
+sub parse_rcs_admin {
+ local ($token, $tag, $tag_name, $tag_revision);
+
+ # Undefine variables, because we may have already read another RCS file
+ undef %tag_revision;
+ undef %revision_symbolic_name;
+
+ while (1) {
+ # Read initial token at beginning of line
+ $token = &get_token(RCSFILE);
+
+ # We're done once we reach the description of the RCS tree
+ if ($token =~ /^\d/o) {
+ &unget_token($token);
+ return;
+ }
+
+# print "token: $token\n";
+
+ if ($token eq "head") {
+ $head_revision = &get_token;
+ &get_token; # Eat semicolon
+ } elsif ($token eq "branch") {
+ $principal_branch = &get_token;
+ &get_token; # Eat semicolon
+ } elsif ($token eq "symbols") {
+
+ # Create an associate array that maps from tag name to
+ # revision number and vice-versa.
+ while (($tag = &get_token) ne ';') {
+ ($tag_name, $tag_revision) = split(':', $tag);
+
+ $tag_revision{$tag_name} = $tag_revision;
+ $revision_symbolic_name{$tag_revision} = $tag_name;
+ }
+ } elsif ($token eq "comment") {
+ $file_description = &get_token;
+ &get_token; # Eat semicolon
+
+ # Ignore all these other fields - We don't care about them.
+ } elsif (($token eq "locks") ||
+ ($token eq "strict") ||
+ ($token eq "expand") ||
+ ($token eq "access")) {
+ (1) while (&get_token ne ';');
+ } else {
+ warn ("Unexpected RCS token: $token\n");
+ }
+ }
+
+ die "Unexpected EOF";
+}
+
+# Construct associative arrays that represent the topology of the RCS tree
+# and other arrays that contain info about individual revisions.
+#
+# The following associative arrays are created, keyed by revision number:
+# %revision_date -- e.g. "96.02.23.00.21.52"
+# %timestamp -- seconds since 12:00 AM, Jan 1, 1970 GMT
+# %revision_author -- e.g. "tom"
+# %revision_branches -- descendant branch revisions, separated by spaces,
+# e.g. "1.21.4.1 1.21.2.6.1"
+# %prev_revision -- revision number of previous *ancestor* in RCS tree.
+# Traversal of this array occurs in the direction
+# of the primordial (1.1) revision.
+# %prev_delta -- revision number of previous revision which forms the
+# basis for the edit commands in this revision.
+# This causes the tree to be traversed towards the
+# trunk when on a branch, and towards the latest trunk
+# revision when on the trunk.
+# %next_delta -- revision number of next "delta". Inverts %prev_delta.
+#
+# Also creates %last_revision, keyed by a branch revision number, which
+# indicates the latest revision on a given branch,
+# e.g. $last_revision{"1.2.8"} == 1.2.8.5
+#
+sub parse_rcs_tree {
+ local($revision, $date, $author, $branches, $next);
+ local($branch, $is_trunk_revision);
+ local($mon,$day,$hhmm,$year);
+ # Undefine variables, because we may have already read another RCS file
+ undef %timestamp;
+ undef %revision_age;
+ undef %revision_author;
+ undef %revision_branches;
+ undef %revision_ctime;
+ undef %revision_date;
+ undef %prev_revision;
+ undef %prev_delta;
+ undef %next_delta;
+ undef %last_revision;
+
+ while (1) {
+ $revision = &get_token;
+
+ # End of RCS tree description ?
+ if ($revision eq 'desc') {
+ &unget_token($revision);
+ return;
+ }
+
+ $is_trunk_revision = ($revision =~ /^[0-9]+\.[0-9]+$/);
+
+ $tag_revision{$revision} = $revision;
+ ($branch) = $revision =~ /(.*)\.[0-9]+/o;
+ $last_revision{$branch} = $revision;
+
+ # Parse date
+ &match_token('date');
+ $date = &get_token;
+ $revision_date{$revision} = $date;
+ &match_token(';');
+
+ # Convert date into timestamp
+ @date_fields = reverse(split(/\./, $date));
+ $date_fields[4]--; # Month ranges from 0-11, not 1-12
+ $timestamp{$revision} = &timegm(@date_fields);
+
+ # Compute date string; Format it the way I like.
+ ($mon, $day, $hhmm, $year)
+ = &ctime($timestamp{$revision})
+ =~ /... (...) (..) (..:..):.. \S* (....)/;
+ $revision_ctime{$revision} = "$day $mon $year $hhmm";
+# $revision_ctime{$revision} = &ctime($timestamp{$revision});
+
+ # Save age
+ $revision_age{$revision} =
+ ($time - $timestamp{$revision}) / $SECS_PER_DAY;
+
+ # Parse author
+ &match_token('author');
+ $author = &get_token;
+ $revision_author{$revision} = $author;
+ &match_token(';');
+
+ # Parse state;
+ &match_token('state');
+ (1) while (&get_token ne ';');
+
+ # Parse branches
+ &match_token('branches');
+ $branches = '';
+ while (($token = &get_token) ne ';') {
+ $prev_revision{$token} = $revision;
+ $prev_delta{$token} = $revision;
+ $branches .= "$token ";
+ }
+ $revision_branches{$revision} = $branches;
+
+ # Parse revision of next delta in chain
+ &match_token('next');
+ $next = '';
+ if (($token = &get_token) ne ';') {
+ $next = $token;
+ &get_token; # Eat semicolon
+ $next_delta{$revision} = $next;
+ $prev_delta{$next} = $revision;
+ if ($is_trunk_revision) {
+ $prev_revision{$revision} = $next;
+ } else {
+ $prev_revision{$next} = $revision;
+ }
+ }
+
+ if ($debug >= 3) {
+ print "revision = $revision\n";
+ print "date = $date\n";
+ print "author = $author\n";
+ print "branches = $branches\n";
+ print "next = $next
\n\n";
+ }
+ }
+}
+
+sub parse_rcs_description {
+ &match_token('desc');
+ $rcs_file_description = &get_token;
+}
+
+# Construct associative arrays containing info about individual revisions.
+#
+# The following associative arrays are created, keyed by revision number:
+# %revision_log -- log message
+# %revision_deltatext -- Either the complete text of the revision,
+# in the case of the head revision, or the
+# encoded delta between this revision and another.
+# The delta is either with respect to the successor
+# revision if this revision is on the trunk or
+# relative to its immediate predecessor if this
+# revision is on a branch.
+sub parse_rcs_deltatext {
+ undef %revision_log;
+ undef %revision_deltatext;
+
+ while (!eof(RCSFILE)) {
+ $revision = &get_token;
+ print "Reading delta for revision: $revision\n" if ($debug >= 3);
+ &match_token('log');
+ $revision_log{$revision} = &get_token;
+ &match_token('text');
+ $revision_deltatext{$revision} = &get_token;
+ }
+}
+
+# Reads and parses complete RCS file from already-opened RCSFILE descriptor.
+sub parse_rcs_file {
+ print "Reading RCS admin...\n" if ($debug >= 2);
+ &parse_rcs_admin();
+ print "Reading RCS revision tree topology...\n" if ($debug >= 2);
+ &parse_rcs_tree();
+
+ if( $debug >= 3 ){
+ print "Keys:\n\n";
+ for $i (keys %tag_revision ){
+ $k = $tag_revision{$i};
+ print "yoyuo $i: $k\n";
+ }
+ print "\n";
+ }
+
+ &parse_rcs_description();
+ print "Reading RCS revision deltas...\n" if ($debug >= 2);
+ &parse_rcs_deltatext();
+ print "Done reading RCS file...\n" if ($debug >= 2);
+}
+
+# Map a tag to a numerical revision number. The tag can be a symbolic
+# branch tag, a symbolic revision tag, or an ordinary numerical
+# revision number.
+sub map_tag_to_revision {
+ local($tag_or_revision) = @_;
+
+ local ($revision) = $tag_revision{$tag_or_revision};
+
+ # Is this a branch tag, e.g. xxx.yyy.0.zzz
+ if ($revision =~ /(.*)\.0\.([0-9]+)/o) {
+ $branch = $1 . '.' . $2;
+ # Return latest revision on the branch, if any.
+ return $last_revision{$branch} if (defined($last_revision{$branch}));
+ return $1; # No revisions on branch - return branch point
+ } else {
+ return $revision;
+ }
+}
+
+# Construct an ordered list of ancestor revisions to the given
+# revision, starting with the immediate ancestor and going back
+# to the primordial revision (1.1).
+#
+# Note: The generated path does not traverse the tree the same way
+# that the individual revision deltas do. In particular,
+# the path traverses the tree "backwards" on branches.
+
+sub ancestor_revisions {
+ local ($revision) = @_;
+ local (@ancestors);
+
+ $revision = $prev_revision{$revision};
+ while ($revision) {
+ push(@ancestors, $revision);
+ $revision = $prev_revision{$revision};
+ }
+
+ return @ancestors;
+}
+
+# Extract the given revision from the digested RCS file.
+# (Essentially the equivalent of cvs up -rXXX)
+sub extract_revision {
+ local ($revision) = @_;
+ local (@path);
+
+ # Compute path through tree of revision deltas to most recent trunk revision
+ while ($revision) {
+ push(@path, $revision);
+ $revision = $prev_delta{$revision};
+ }
+ @path = reverse(@path);
+ shift @path; # Get rid of head revision
+
+ # Get complete contents of head revision
+ local (@text) = split(/^/, $revision_deltatext{$head_revision});
+
+ # Iterate, applying deltas to previous revision
+ foreach $revision (@path) {
+ $adjust = 0;
+ @diffs = split(/^/, $revision_deltatext{$revision});
+
+ local ($lines_added) = 0;
+ local ($lines_removed) = 0;
+
+ foreach $command (@diffs) {
+ if ($add_lines_remaining > 0) {
+ # Insertion lines from a prior "a" command.
+ splice(@text, $start_line + $adjust,
+ 0, $command);
+ $add_lines_remaining--;
+ $adjust++;
+ } elsif ($command =~ /^d(\d+)\s(\d+)/) {
+ # "d" - Delete command
+ ($start_line, $count) = ($1, $2);
+ splice(@text, $start_line + $adjust - 1, $count);
+ $adjust -= $count;
+ $lines_removed += $count;
+ } elsif ($command =~ /^a(\d+)\s(\d+)/) {
+ # "a" - Add command
+ ($start_line, $count) = ($1, $2);
+ $add_lines_remaining = $count;
+ $lines_added += $lines_added;
+ } else {
+ die "Error parsing diff commands";
+ }
+ }
+ $lines_removed{$revision} += $lines_removed;
+ $lines_added{$revision} += $lines_added;
+ }
+
+ return @text;
+}
+
+sub parse_cvs_file {
+ local($rcs_pathname) = @_;
+
+ # Args in: $opt_rev - requested revision
+ # $opt_m - time since modified
+ # Args out: @revision_map
+ # $revision
+ # %timestamp
+ # (%revision_deltatext)
+
+ @revision_map = ();
+ die "$progname: error: This file appeared to be under CVS control, " .
+ "but the RCS file is inaccessible.\n(Couldn't open '$rcs_pathname')\n"
+ if !open (RCSFILE, "< $rcs_pathname");
+ &parse_rcs_file();
+ close(RCSFILE);
+
+ if (!defined($opt_rev) || $opt_rev eq '' || $opt_rev eq 'HEAD') {
+ # Explicitly specified topmost revision in tree
+ $revision = $head_revision;
+ } else {
+ # Symbolic tag or specific revision number specified.
+ $revision = &map_tag_to_revision($opt_rev);
+ die "$progname: error: -r: No such revision: $opt_rev\n"
+ if ($revision eq '');
+ }
+
+ # The primordial revision is not always 1.1! Go find it.
+ my $primordial = $revision;
+ while ($prev_revision{$primordial} != "") {
+ $primordial = $prev_revision{$primordial};
+ }
+
+ # Don't display file at all, if -m option is specified and no
+ # changes have been made in the specified file.
+ return if ($opt_m && $timestamp{$revision} < $opt_m_timestamp);
+
+ # Figure out how many lines were in the primordial, i.e. version 1.1,
+ # check-in by moving backward in time from the head revision to the
+ # first revision.
+ $line_count = split(/^/, $revision_deltatext{$head_revision});
+ for ($rev = $prev_revision{$head_revision}; $rev;
+ $rev = $prev_revision{$rev}) {
+ @diffs = split(/^/, $revision_deltatext{$rev});
+ foreach $command (@diffs) {
+ if ($skip > 0) {
+ # Skip insertion lines from a prior "a" command.
+ $skip--;
+ } elsif ($command =~ /^d(\d+)\s(\d+)/) {
+ # "d" - Delete command
+ ($start_line, $count) = ($1, $2);
+ $line_count -= $count;
+ } elsif ($command =~ /^a(\d+)\s(\d+)/) {
+ # "a" - Add command
+ ($start_line, $count) = ($1, $2);
+ $skip = $count;
+ $line_count += $count;
+ } else {
+ die "$progname: error: illegal RCS file $rcs_pathname\n",
+ " error appears in revision $rev\n";
+ }
+ }
+ }
+
+ # Now, play the delta edit commands *backwards* from the primordial
+ # revision forward, but rather than applying the deltas to the text of
+ # each revision, apply the changes to an array of revision numbers.
+ # This creates a "revision map" -- an array where each element
+ # represents a line of text in the given revision but contains only
+ # the revision number in which the line was introduced rather than
+ # the line text itself.
+ #
+ # Note: These are backward deltas for revisions on the trunk and
+ # forward deltas for branch revisions.
+
+ # Create initial revision map for primordial version.
+ while ($line_count--) {
+ push(@revision_map, $primordial);
+ }
+
+ @ancestors = &ancestor_revisions($revision);
+ unshift (@ancestors, $revision); #
+ pop @ancestors; # Remove "1.1"
+ $last_revision = $primordial;
+ foreach $revision (reverse @ancestors) {
+ $is_trunk_revision = ($revision =~ /^[0-9]+\.[0-9]+$/);
+
+ if ($is_trunk_revision) {
+ @diffs = split(/^/, $revision_deltatext{$last_revision});
+
+ # Revisions on the trunk specify deltas that transform a
+ # revision into an earlier revision, so invert the translation
+ # of the 'diff' commands.
+ foreach $command (@diffs) {
+ if ($skip > 0) {
+ $skip--;
+ } else {
+ if ($command =~ /^d(\d+)\s(\d+)$/) { # Delete command
+ ($start_line, $count) = ($1, $2);
+
+ $#temp = -1;
+ while ($count--) {
+ push(@temp, $revision);
+ }
+ splice(@revision_map, $start_line - 1, 0, @temp);
+ } elsif ($command =~ /^a(\d+)\s(\d+)$/) { # Add command
+ ($start_line, $count) = ($1, $2);
+ splice(@revision_map, $start_line, $count);
+ $skip = $count;
+ } else {
+ die "Error parsing diff commands";
+ }
+ }
+ }
+ } else {
+ # Revisions on a branch are arranged backwards from those on
+ # the trunk. They specify deltas that transform a revision
+ # into a later revision.
+ $adjust = 0;
+ @diffs = split(/^/, $revision_deltatext{$revision});
+ foreach $command (@diffs) {
+ if ($skip > 0) {
+ $skip--;
+ } else {
+ if ($command =~ /^d(\d+)\s(\d+)$/) { # Delete command
+ ($start_line, $count) = ($1, $2);
+ splice(@revision_map, $start_line + $adjust - 1, $count);
+ $adjust -= $count;
+ } elsif ($command =~ /^a(\d+)\s(\d+)$/) { # Add command
+ ($start_line, $count) = ($1, $2);
+
+ $skip = $count;
+ $#temp = -1;
+ while ($count--) {
+ push(@temp, $revision);
+ }
+ splice(@revision_map, $start_line + $adjust, 0, @temp);
+ $adjust += $skip;
+ } else {
+ die "Error parsing diff commands";
+ }
+ }
+ }
+ }
+ $last_revision = $revision;
+ }
+ $revision;
+}
+
+__END__
+#
+# The following are parts of the original cvsblame script that are not
+# used for cvsblame.pl
+#
+
+# Read CVS/Entries and CVS/Repository files.
+#
+# Creates these associative arrays, keyed by the CVS file pathname
+#
+# %cvs_revision -- Revision # present in working directory
+# %cvs_date
+# %cvs_sticky_revision -- Sticky tag, if any
+#
+# Also, creates %cvs_files, keyed by the directory path, which contains
+# a space-separated list of the files under CVS control in the directory
+sub read_cvs_entries
+{
+ local ($directory) = @_;
+ local ($filename, $rev, $date, $idunno, $sticky, $pathname);
+
+ $cvsdir = $directory . '/CVS';
+
+ return if (! -d $cvsdir);
+
+ return if !open(ENTRIES, "< $cvsdir/Entries");
+
+ while() {
+ chop;
+ ($filename, $rev, $date, $idunno, $sticky) = split("/", substr($_, 1));
+ ($pathname) = $directory . "/" . $filename;
+ $cvs_revision{$pathname} = $rev;
+ $cvs_date{$pathname} = $date;
+ $cvs_sticky_revision{$pathname} = $sticky;
+ $cvs_files{$directory} .= "$filename\\";
+ }
+ close(ENTRIES);
+
+ return if !open(REPOSITORY, "< $cvsdir/Repository");
+ $repository = ;
+ chop($repository);
+ close(REPOSITORY);
+ $repository{$directory} = $repository;
+}
+
+# Given path to file in CVS working directory, compute path to RCS
+# repository file. Cache that info for future use.
+sub rcs_pathname {
+ ($pathname) = @_;
+
+ if ($pathname =~ m@/@) {
+ ($directory,$filename) = $pathname =~ m@(.*)/([^/]+)$@;
+ } else {
+ ($directory,$filename) = ('.',$pathname);
+ $pathname = "./" . $pathname;
+ }
+ if (!defined($repository{$directory})) {
+ &read_cvs_entries($directory);
+ }
+
+ if (!defined($cvs_revision{$pathname})) {
+ die "$progname: error: File '$pathname' does not appear to be under" .
+ " CVS control.\n"
+ }
+
+ print STDERR "file: $filename\n" if $debug;
+ local ($rcs_path) = $repository{$directory} . '/' . $filename . ',v';
+ return $rcs_path if (-r $rcs_path);
+
+ # A file that exists only on the branch, not on the trunk, is found
+ # in the Attic subdir.
+ return $repository{$directory} . '/Attic/' . $filename . ',v';
+}
+
+sub show_annotated_cvs_file {
+ local($pathname) = @_;
+ local(@output) = ();
+
+ $revision = &parse_cvs_file($pathname);
+
+ @text = &extract_revision($revision);
+ die "$progname: Internal consistency error" if ($#text != $#revision_map);
+
+ # Set total width of line annotation.
+ # Warning: field widths here must match format strings below.
+ $annotation_width = 0;
+ $annotation_width += 8 if $opt_a; # author
+ $annotation_width += 7 if $opt_v; # revision
+ $annotation_width += 6 if $opt_A; # age
+ $annotation_width += 12 if $opt_d; # date
+ $blank_annotation = ' ' x $annotation_width;
+
+ if ($multiple_files_on_command_line) {
+ print "\n", "=" x (83 + $annotation_width);
+ print "\n$progname: Listing file: $pathname\n"
+ }
+
+ # Print each line of the revision, preceded by its annotation.
+ $line = 0;
+ foreach $revision (@revision_map) {
+ $text = $text[$line++];
+ $annotation = '';
+
+ # Annotate with revision author
+ $annotation .= sprintf("%-8s", $revision_author{$revision}) if $opt_a;
+
+ # Annotate with revision number
+ $annotation .= sprintf(" %-6s", $revision) if $opt_v;
+
+ # Date annotation
+ $annotation .= " $revision_ctime{$revision}" if $opt_d;
+
+ # Age annotation ?
+ $annotation .= sprintf(" (%3s)",
+ int($revision_age{$revision})) if $opt_A;
+
+ # -m (if-modified-since) annotion ?
+ if ($opt_m && ($timestamp{$revision} < $opt_m_timestamp)) {
+ $annotation = $blank_annotation;
+ }
+
+ # Suppress annotation of whitespace lines, if requested;
+ $annotation = $blank_annotation if $opt_w && ($text =~ /^\s*$/);
+
+# printf "%4d ", $line if $opt_l;
+# print "$annotation - $text";
+ push(@output, sprintf("%4d ", $line)) if $opt_l;
+ push(@output, "$annotation - $text");
+ }
+ @output;
+}
+
+sub usage {
+ die
+"$progname: usage: [options] [file|dir]...\n",
+" Options:\n",
+" -r Specify CVS revision of file to display\n",
+" can be any of:\n",
+" + numeric tag, e.g. 1.23,\n",
+" + symbolic branch or revision tag, e.g. CHEDDAR,\n",
+" + HEAD keyword (most recent revision on trunk)\n",
+" -a Annotate lines with author (username)\n",
+" -A Annotate lines with age, in days\n",
+" -v Annotate lines with revision number\n",
+" -d Annotate lines with date, in local time zone\n",
+" -l Annotate lines with line number\n",
+" -w Don't annotate all-whitespace lines\n",
+" -m <# days> Only annotate lines modified within last <# days>\n",
+" -h Print help (this message)\n\n",
+" (-a -v assumed, if none of -a, -v, -A, -d supplied)\n"
+;
+}
+
+&usage if (!&Getopts('r:m:Aadhlvw'));
+&usage if ($opt_h); # help option
+
+$multiple_files_on_command_line = 1 if ($#ARGV != 0);
+
+&cvsblame_init;
+
+sub annotate_cvs_directory
+{
+ local($dir) = @_;
+ &read_cvs_entries($dir);
+ foreach $file (split(/\\/, $cvs_files{$dir})) {
+ &show_annotated_cvs_file("$dir/$file");
+ }
+}
+
+# No files on command-line ? Use current directory.
+push(@ARGV, '.') if ($#ARGV == -1);
+
+# Iterate over files/directories on command-line
+while ($#ARGV >= 0) {
+ $pathname = shift @ARGV;
+ # Is it a directory ?
+ if (-d $pathname) {
+ &traverse_cvs_tree($pathname, *annotate_cvs_directory);
+
+ # No, it must be a file.
+ } else {
+ &show_annotated_cvs_file($pathname);
+ }
+}
diff --git a/mozilla/webtools/bonsai/cvsguess.cgi b/mozilla/webtools/bonsai/cvsguess.cgi
new file mode 100755
index 00000000000..bce58a5aa83
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsguess.cgi
@@ -0,0 +1,72 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+require 'lloydcgi.pl';
+require 'utils.pl';
+
+$file= $form{'file'};
+$mark= $form{'mark'};
+$ln = ($mark > 10 ? $mark-10 : 1 );
+$rev = $form{'rev'};
+$debug = $form{'debug'};
+
+print "Content-Type: text/html\n\n";
+
+$CVS_ROOT = $form{'root'};
+if( $CVS_ROOT eq '' ){
+ $CVS_ROOT = pickDefaultRepository();
+}
+validateRepository($CVS_ROOT);
+
+$CVS_REPOS_SUFIX = $CVS_ROOT;
+$CVS_REPOS_SUFIX =~ s/\//_/g;
+
+$db = ConnectToDatabase();
+
+$f = SqlQuote($file);
+$qstring = "select distinct dirs.dir from checkins,dirs,files,repositories where dirs.id=dirid and files.id=fileid and repositories.id=repositoryid and repositories.repository='$CVS_ROOT' and files.file='$f' order by dirs.dir";
+
+if ($debug) {
+ print "$qstring
\n";
+}
+
+$query = $db->Query($qstring) || die $Mysql::db_errstr;
+
+while(@row = $query->fetchrow){
+ $d = $row[0];
+ push @fl, "$d/$file";
+}
+
+if( @fl == 0 ){
+ print "No files matched this file name. It may have been added recently.
";
+}
+elsif( @fl == 1 ){
+ $s = $fl[0];
+ print "
+
+
+ ";
+}
+else {
+ print "Pick the file that best matches the one you are looking for:
\n";
+ for $s (@fl) {
+ print "$s";
+ }
+}
diff --git a/mozilla/webtools/bonsai/cvsindex.pl b/mozilla/webtools/bonsai/cvsindex.pl
new file mode 100755
index 00000000000..f9628cfd16f
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsindex.pl
@@ -0,0 +1,91 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+
+# Figure out which directory bonsai is in by looking at argv[0]
+
+$bonsaidir = $0;
+$bonsaidir =~ s:/[^/]*$::; # Remove last word, and slash before it.
+if ($bonsaidir eq "") {
+ $bonsaidir = ".";
+}
+
+chdir $bonsaidir || die "Couldn't chdir to $bonsaidir";
+require "utils.pl";
+
+if( $ARGV[0] eq '' ){
+ $CVS_ROOT = pickDefaultRepository();
+}
+else {
+ $CVS_ROOT = $ARGV[0];
+}
+
+$CVS_REPOS_SUFIX = $CVS_ROOT;
+$CVS_REPOS_SUFIX =~ s:/:_:g;
+
+
+$CHECKIN_DATA_FILE = "$bonsaidir/data/checkinlog${CVS_REPOS_SUFIX}";
+$CHECKIN_INDEX_FILE = "$bonsaidir/data/index${CVS_REPOS_SUFIX}";
+
+&build_index;
+&print_keys;
+
+sub build_index {
+ open(CI, "<$CHECKIN_DATA_FILE") || die "could not open checkin data file\n";
+
+ $file_pos = 0;
+ $lastlog = 0;
+ $last_date = 0;
+ $index = {};
+ $now = time;
+ while( ){
+ if( /^LOGCOMMENT/ ){
+ $done = 0;
+ $file_pos += length;
+ while( !$done && ($line = ) ){
+ if( $line =~ /^:ENDLOGCOMMENT/ ){
+ $done = 1;
+ }
+ $file_pos += length($line);
+ }
+ }
+ else {
+ #print $_ . "\n";
+ $ci = [split(/\|/)];
+ $d = $ci->[1];
+
+ if( $d < $now && $d > ($last_date + 60*60*4) ){
+ $index->{$d} = $file_pos;
+ $last_date = $d;
+ }
+ $file_pos += length;
+ }
+ }
+ close( CI );
+}
+
+sub print_keys {
+ Lock();
+ open(INDEX , ">$CHECKIN_INDEX_FILE");
+ for $i (sort {$b <=> $a} keys %{$index}) {
+ print INDEX "$index->{$i}|$i\n";
+ }
+ close(INDEX);
+ Unlock();
+}
diff --git a/mozilla/webtools/bonsai/cvslog.cgi b/mozilla/webtools/bonsai/cvslog.cgi
new file mode 100755
index 00000000000..9f1c43611a2
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvslog.cgi
@@ -0,0 +1,493 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+# 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
+#
+
+require 'lloydcgi.pl';
+require 'cvsblame.pl';
+require 'utils.pl';
+use SourceChecker;
+
+# Some Globals
+#
+
+$| = 1;
+
+print "Content-Type:text/html\n\n";
+
+@src_roots = getRepositoryList();
+
+
+# Handle the "file" argument
+#
+$filename = '';
+$filename = $form{'file'} if defined($form{'file'});
+if ($filename eq '')
+{
+ &print_usage;
+ exit;
+}
+($file_head, $file_tail) = $filename =~ m@(.*/)?(.+)@;
+
+
+# Handle the "rev" argument
+#
+$opt_rev = $form{'rev'} if defined($form{'rev'} && $form{'rev'} ne 'HEAD');
+$browse_revtag = 'HEAD';
+$browse_revtag = $opt_rev if ($opt_rev =~ /[A-Za-z]/);
+$revision = '';
+
+
+# Handle the "root" argument
+#
+if (defined($root = $form{'root'}) && $root ne '') {
+ $root =~ s|/$||;
+ validateRepository($root);
+ if (-d $root) {
+ unshift(@src_roots, $root);
+ } else {
+ &print_top;
+ print "Error: Root, $root, is not a directory.
\n";
+ print "\n";
+ &print_bottom;
+ exit;
+ }
+}
+
+
+
+# Find the rcs file
+#
+foreach (@src_roots) {
+ $root = $_;
+ $rcs_filename = "$root/$filename,v";
+ goto found_file if -r $rcs_filename;
+ $rcs_filename = "$root/${file_head}Attic/$file_tail,v";
+ goto found_file if -r $rcs_filename;
+}
+# File not found
+&print_top;
+print "Rcs file, $filename, does not exist.
\n";
+print "\n";
+&print_bottom;
+exit;
+
+found_file:
+ ($rcs_path) = $rcs_filename =~ m@$root/(.*)/.+?,v@;
+
+
+# Parse the rcs file ($opt_rev is passed as a global)
+#
+$revision = &parse_cvs_file($rcs_filename);
+$file_rev = $revision;
+
+
+# Handle the "mark" argument
+#
+$mark_arg = '';
+$mark_arg = $form{'mark'} if defined($form{'mark'});
+foreach $rev (split(',',$mark_arg)) {
+ $mark{$rev} = 1;
+}
+
+
+# Handle the "author" argument
+#
+$author_arg = '';
+$author_arg = $form{'author'} if defined($form{'author'});
+foreach $author (split(',',$author_arg)) {
+ $use_author{$author} = 1;
+}
+
+
+# Handle the "sort" argument
+$opt_sort = '';
+$opt_sort = $form{'sort'};
+
+
+# Start printing out the page
+#
+&print_top;
+
+if ($ENV{'HTTP_USER_AGENT'} =~ /Win/) {
+ $font_tag = "";
+} else {
+ # We don't want your stinking Windows font
+ $font_tag = "";
+}
+
+# Print link at top for directory browsing
+#
+$output = "";
+
+foreach $path (split('/',$rcs_path)) {
+ $link_path .= $path;
+ $output .= "$path/ ";
+ $link_path .= '/';
+}
+$output .= "$file_tail (";
+$output .= "$browse_revtag:" unless $browse_revtag eq 'HEAD';
+$output .= $revision if $revision;
+$output .= ")";
+
+EmitHtmlHeader("CVS Log", $output);
+
+&print_useful_links($filename);
+
+# Create a table with header links to sort by column.
+#
+$table_tag = "";
+if ($opt_sort eq 'author') {
+ $table_header_tag .= "Rev Author Date Log";
+} else {
+ $table_header_tag .= " Rev Author Date Log";
+}
+
+print "$font_tag$table_tag$table_header_tag";
+
+# Print each line of the revision, preceded by its annotation.
+#
+if ($browse_revtag eq 'HEAD') {
+ $start_rev = $head_revision; # $head_revision is a global from cvsblame.pl
+} else {
+ $start_rev = map_tag_to_revision($browse_revtag);
+}
+$row_count = 0;
+$max_rev_length = length($start_rev);
+$max_author_length = 8;
+@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';
+foreach $revision (@revisions)
+{
+ $author = $revision_author{$revision};
+ next unless $author_arg eq '' || $use_author{$author};
+
+ $log = $revision_log{$revision};
+ $log =~ s/&/&/g;
+ $log =~ s/</g;
+ $log =~ s/>/>/g;
+ eval ('$log =~ s@\d{4,6}@' . $BUGSYSTEMEXPR . '@g;');
+ $log =~ s/\n|\r|\r\n/
/g;
+
+ if ($bgcolor eq '') {
+ #$bgcolor = ' BGCOLOR="#EEEEEE"';# My browser translates this to white.
+ $bgcolor = ' BGCOLOR="#E7E7E7"'; # Pick a grey that shows up on 8-bit.
+ } else {
+ $bgcolor = '';
+ }
+
+ $output = '';
+ $row_count++;
+ if ($row_count > 20) {
+ $output .= "
\n$table_tag";
+ $row_count = 0;
+ }
+
+ $output .= ""
+ ."';
+
+ $output .= " ".$author
+ .' ' x ($max_author_length - length($author)).' ';
+ $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;
+ my ($rday, $rmon, $ryear, $rhour, $rmin) =
+ m/([0-9]+) ([A-Z][a-z]+) ([0-9][0-9]+) +([0-9]+):([0-9]+)/;
+ $rmon =~ s/^(...).*$/$1/;
+
+ if (!$rday) {
+ # parse error -- be annoying so somebody complains.
+ $rev_time = "";
+ } elsif ($cyear ne $ryear) {
+ $rev_time = sprintf("%s %2d %04d", $rmon, $rday, $ryear);
+ } else {
+ $rev_time = sprintf("%s %2d %02d:%02d",
+ $rmon, $rday, $rhour, $rmin);
+ }
+
+ $rev_time = "$rev_time";
+ }
+
+ $output .= "$rev_time ";
+ $output .= " $log ";
+
+ $output .= " \n";
+
+ print $output;
+}
+print "";
+&print_bottom;
+
+## END of main script
+
+sub by_revision {
+ local (@a_parts) = split(/\./,$a);
+ local (@b_parts) = split(/\./,$b);
+ while(1) {
+ local ($aa) = shift @a_parts;
+ local ($bb) = shift @b_parts;
+ return 1 if $aa eq '';
+ return -1 if $bb eq '';
+ return $bb <=> $aa if $aa ne $bb;
+ }
+}
+
+sub by_author {
+ local ($a_author) = $revision_author{$a};
+ local ($b_author) = $revision_author{$b};
+
+ return $a_author cmp $b_author if $a_author ne $b_author;
+ return by_revision;
+}
+
+sub revision_pad {
+ local ($revision) = @_;
+ return ' ' x ($max_rev_length - length($revision));
+}
+
+sub sprint_author {
+ local ($revision) = @_;
+ local ($author) = $revision_author{$revision};
+
+ return
+}
+
+
+sub print_top {
+ local ($title_text) = "for $file_tail (";
+ $title_text .= "$browse_revtag:" unless $browse_revtag eq 'HEAD';
+ $title_text .= $revision if $revision;
+ $title_text .= ")";
+ $title_text =~ s/\(\)//;
+
+ print <<__TOP__;
+
+
+CVS Log $title_text
+
+
+
+
+
+
+
+__TOP__
+} # print_top
+
+sub print_usage {
+ local ($linenum_message) = '';
+ local ($new_linenum, $src_roots_list);
+ local ($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
+ Filter out these authors
+
+
+ #<rev_number>
+ --
+ #1.2
+ Jump to a revision
+
+
+
+Examples:
+
+You may also begin a query with the CVS Query Form.
+
+__USAGE__
+ &print_bottom;
+} # sub print_usage
+
+sub print_bottom {
+ print <<__BOTTOM__;
+
+
+Page configuration and help.
+Mail feedback to <slamm\@netscape.com>.
+
+
+__BOTTOM__
+} # print_bottom
+
+sub print_useful_links {
+ my ($path) = @_;
+ my ($dir, $file) = $path =~ m@(.*/)?(.+)@;
+ $dir =~ s@/$@@;
+
+ my $lxr_base = "http://cvs-mirror.mozilla.org/webtools/lxr/source";
+ my $diff_base = "cvsview2.cgi";
+ my $blame_base = "cvsblame.cgi";
+
+ # total kludge!! lxr omits the top-level "mozilla" directory...
+ my $lxr_path = $path;
+ $lxr_path =~ s@^ns/@@;
+ $lxr_path =~ s@^mozilla/@@;
+
+ my $lxr_link = "$lxr_base/$lxr_path";
+ my $diff_link = "$diff_base?command=DIRECTORY\&subdir=$dir\&files=$file";
+ my $blame_link = "$blame_base?root=$CVS_ROOT\&file=$path";
+
+print "
+
+
+
+
+
+ lxr:
+ browse the source code as hypertext.
+
+
+ diff:
+ compare any two versions.
+
+
+ blame:
+ annotate the author of each line.
+
+
+
+
+
+
+";
+}
diff --git a/mozilla/webtools/bonsai/cvsmenu.pl b/mozilla/webtools/bonsai/cvsmenu.pl
new file mode 100644
index 00000000000..6f8057df577
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsmenu.pl
@@ -0,0 +1,64 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+1;
+
+require 'utils.pl';
+
+sub cvsmenu {
+my($extra) = @_;
+loadConfigData();
+print "
+Menu
+";
+
+my $pass;
+my $i;
+foreach $pass ("cvsqueryform|Query",
+ "rview|Browse",
+ "moduleanalyse|Examine Modules") {
+ ($page, $title) = split(/\|/, $pass);
+ print "$title
\n";
+ foreach $i (@treelist) {
+ my $branch = $treeinfo{$i}->{'branch'};
+ if ($branch ne "") {
+ $branch = "&branch=" . $branch;
+ }
+ $desc = $treeinfo{$i}->{'shortdesc'};
+ if ($desc eq "") {
+ $desc = $treeinfo{$i}->{'description'};
+ }
+ print "- {'repository'}&module=$treeinfo{$i}->{'module'}$branch>$desc\n";
+ };
+ print "
\n";
+};
+
+if (open(EXTRA, ") {
+ print $_;
+ }
+ close EXTRA;
+}
+
+print "
+ Questions, Comments, Feature requests? mail terry
+
+";
+
+}
+
diff --git a/mozilla/webtools/bonsai/cvsquery.cgi b/mozilla/webtools/bonsai/cvsquery.cgi
new file mode 100755
index 00000000000..f1ccf3b5e28
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsquery.cgi
@@ -0,0 +1,647 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+require 'lloydcgi.pl';
+require 'utils.pl';
+use Date::Parse;
+
+loadConfigData();
+$CVS_ROOT = $form{"cvsroot"};
+
+require 'timelocal.pl';
+require 'cvsquery.pl';
+
+$| = 1;
+
+$sm_font_tag = "";
+
+print "Content-type: text/html
+
+";
+
+$script_str='';
+&setup_script;
+
+print "$script_str";
+
+#print "";
+
+
+$CVS_REPOS_SUFIX = $CVS_ROOT;
+$CVS_REPOS_SUFIX =~ s/\//_/g;
+
+$CHECKIN_DATA_FILE = "data/checkinlog${CVS_REPOS_SUFIX}";
+$CHECKIN_INDEX_FILE = "data/index${CVS_REPOS_SUFIX}";
+
+
+$SORT_HEAD="bgcolor=\"#DDDDDD\"";
+
+#
+# Log the query
+open( LOG, ">>data/querylog.txt");
+$t = time;
+print LOG "$ENV{'REMOTE_ADDR'} $t $ENV{'QUERY_STRING'}\n";
+close(LOG);
+
+#
+# build a module map
+#
+$query_module = $form{'module'};
+
+#
+# allow ?file=/a/b/c/foo.c to be synonymous with ?dir=/a/b/c&file=foo.c
+#
+if ( $form{'dir'} eq '' ) {
+ my ( $d, $f ) = $form{'file'} =~ m@(.*?/)([^/]*)$@;
+ $form{'dir'} = $d;
+ $form{'file'} = $f;
+}
+
+#
+# build a directory map
+#
+@query_dirs = split(/[;, \t]+/, $form{'dir'});
+
+$query_file = $form{'file'};
+$query_filetype = $form{'filetype'};
+$query_logexpr = $form{'logexpr'};
+
+
+
+
+#
+# date
+#
+$query_date_type = $form{'date'};
+if( $query_date_type eq 'hours' ){
+ $query_date_min = time - $form{'hours'}*60*60;
+}
+elsif( $query_date_type eq 'day' ){
+ $query_date_min = time - 24*60*60;
+}
+elsif( $query_date_type eq 'week' ){
+ $query_date_min = time - 7*24*60*60;
+}
+elsif( $query_date_type eq 'month' ){
+ $query_date_min = time - 30*24*60*60;
+}
+elsif( $query_date_type eq 'all' ){
+ $query_date_min = 0;
+}
+elsif( $query_date_type eq 'explicit' ){
+ if ($form{'mindate'} ne "") {
+ $query_date_min = parse_date($form{'mindate'});
+ }
+
+ if ($form{'maxdate'} ne "") {
+ $query_date_max = parse_date($form{'maxdate'});
+ }
+}
+else {
+ $query_date_min = time-60*60*2;
+}
+
+#
+# who
+#
+$query_who = $form{'who'};
+$query_whotype = $form{'whotype'};
+
+
+$show_raw = $form{'raw'} ne '';
+
+#
+# branch
+#
+$query_branch = $form{'branch'};
+$query_branchtype = $form{'branchtype'};
+
+
+#
+# tags
+#
+$query_begin_tag = $form{'begin_tag'};
+$query_end_tag = $form{'end_tag'};
+
+
+#
+# Get the query in english and print it.
+#
+$t = $e = &query_to_english;
+$t =~ s/<[^>]*>//g;
+
+$query_debug = $form{'debug'};
+
+$result= &query_checkins( $mod_map );
+
+for $i (@{$result}) {
+ $w{"$i->[$CI_WHO]\@netscape.com"} = 1;
+}
+
+@p = sort keys %w;
+$pCount = @p;
+$s = join(",%20", @p);
+
+$e =~ s/Checkins in/In/;
+
+my $menu = "
+$e
+
+Modify Query
+
Mail everyone on this page
+($pCount people)
+";
+
+EmitHtmlTitleAndHeader($t, "CVS Checkins", "$menu");
+
+#
+# Test code to print the results
+#
+
+$|=1;
+
+if( !$show_raw ) {
+
+ if( $form{"sortby"} eq "Who" ){
+ $result = [sort {
+ $a->[$CI_WHO] cmp $b->[$CI_WHO]
+ || $b->[$CI_DATE] <=> $a->[$CI_DATE]
+ } @{$result}] ;
+ $head_who = $SORT_HEAD;
+ }
+ elsif( $form{"sortby"} eq "File" ){
+ $result = [sort {
+ $a->[$CI_FILE] cmp $b->[$CI_FILE]
+ || $b->[$CI_DATE] <=> $a->[$CI_DATE]
+ || $a->[$CI_DIRECTORY] cmp $b->[$CI_DIRECTORY]
+ } @{$result}] ;
+ $head_file = $SORT_HEAD;
+ }
+ elsif( $form{"sortby"} eq "Directory" ){
+ $result = [sort {
+ $a->[$CI_DIRECTORY] cmp $b->[$CI_DIRECTORY]
+ || $a->[$CI_FILE] cmp $b->[$CI_FILE]
+ || $b->[$CI_DATE] <=> $a->[$CI_DATE]
+ } @{$result}] ;
+ $head_directory = $SORT_HEAD;
+ }
+ elsif( $form{"sortby"} eq "Change Size" ){
+ $result = [sort {
+
+ ($b->[$CI_LINES_ADDED]- $b->[$CI_LINES_REMOVED])
+ <=> ($a->[$CI_LINES_ADDED]- $a->[$CI_LINES_REMOVED])
+ #|| $b->[$CI_DATE] <=> $a->[$CI_DATE]
+ } @{$result}] ;
+ $head_delta = $SORT_HEAD;
+ }
+ else{
+ $result = [sort {$b->[$CI_DATE] <=> $a->[$CI_DATE]} @{$result}] ;
+ $head_date = $SORT_HEAD;
+ }
+
+ &print_result($result);
+}
+else {
+ print "
";
+ for $ci (@$result) {
+ $ci->[$CI_LOG] = '';
+ $s = join("|",@$ci);
+ print "$s\n";
+ }
+}
+
+
+#
+# code to debug the modules_map
+#
+#print "\n";
+#while( ($k,$v) = each(%{$mod_map})) {
+# print "$k=$v\n";
+#}
+
+#
+#
+#
+sub print_tcl {
+ local($result) = @_;
+ local($t, $count,$first,$i, $k, $files);
+
+ $t = time;
+ print TCLOUT "set treeopen 0\n" .
+ "set lastgoodtimestamp $t\n" .
+ "set closetimestamp $t\n";
+ $count = 0;
+ $first = 0;
+ $i = 1;
+
+
+ $max = @{$result}+1;
+
+ while( $i < $max ){
+ $c1 = $result->[$first];
+ $c2 = $result->[$i];
+ if( $i == $max-1
+ || $c1->[$CI_DATE] != $c2->[$CI_DATE]
+ || $c1->[$CI_DIR] ne $c2->[$CI_DIR]
+ || $c1->[$CI_WHO] ne $c2->[$CI_WHO]
+ ) {
+ $files = '{';
+ $fu = '{';
+ $k = $first;
+ while( $k < $i ){
+ $files .= $result->[$k][$CI_FILE] . " ";
+ $fu .= &make_fullinfo( $result->[$k] );
+ $k++;
+ }
+ $files .= '}';
+ $fu .= '}';
+ print TCLOUT "set ci-${count}(date) $c1->[$CI_DATE]\n" .
+ "set ci-${count}(dir) $c1->[$CI_DIR]\n" .
+ "set ci-${count}(person) $c1->[$CI_WHO]\n" .
+ "set ci-${count}(files) $files\n" .
+ "set ci-${count}(fullinfo) $fu\n" .
+ "set ci-${count}(log) \{$c1->[$CI_LOG]\}\n" .
+ "set ci-${count}(treeopen) 1\n";
+ $count++;
+ $first = $i;
+ }
+ $i++;
+ }
+}
+
+sub make_fullinfo{
+ local( $ci ) = @_;
+ local( $s );
+
+ $a = &tcl_value( $ci->[$CI_FILE] );
+ $b = &tcl_value( $ci->[$CI_REV] );
+ $c = &tcl_value( $ci->[$CI_LINES_ADDED] );
+ $d = &tcl_value( $ci->[$CI_LINES_REMOVED] );
+ $e = &tcl_value( $ci->[$CI_STICKY] );
+
+ return "{$a $b $c $d $e}";
+}
+
+sub tcl_value {
+ local( $a ) = @_;
+
+ if( $a eq '' ){
+ return '{}';
+ }
+ else {
+ return $a;
+ }
+}
+
+#
+#
+#
+sub print_result {
+ local($result) = @_;
+ local($ci,$i,$k,$j,$max, $l, $span);
+
+ &print_head;
+
+ $i = 20;
+ $k = 0;
+ $max = @{$result};
+
+ while( $k < $max ){
+ $ci = $result->[$k];
+ $span = 1;
+ if( ($l = $ci->[$CI_LOG]) ne '' ){
+ #
+ # Calculate the number of consequitive logs that are
+ # the same and nuke them
+ #
+ $j = $k+1;
+ while( $j < $max && $result->[$j]->[$CI_LOG] eq $l ){
+ $result->[$j]->[$CI_LOG] = '';
+ $j++;
+ }
+
+ #
+ # Make sure we don't break over a description block
+ #
+ $span = $j-$k;
+ if( $span-1 > $i ){
+ $i = $j-$k;
+ }
+ }
+
+ &print_ci( $ci, $span );
+
+
+ if( $i <= 0 ){
+ $i = 20;
+ print "
";
+}
+
+sub html_log {
+ local( $log ) = @_;
+ $log =~ s/&/&/g;
+ $log =~ s/</g;
+ return $log;
+}
+
+sub PrevRev {
+ local( $rev ) = @_;
+ local( $i, $j, $ret, @r );
+
+ @r = split( /\./, $rev );
+
+ $i = @r-1;
+
+ $r[$i]--;
+ if( $r[$i] == 0 ){
+ $i -= 2;
+ }
+
+ $j = 0;
+ while( $j < $i ){
+ $ret .= "$r[$j]\.";
+ $j++
+ }
+ $ret .= $r[$i];
+}
+
+
+sub parse_date {
+ my($d) = @_;
+
+ my($result) = str2time($d);
+ if (defined $result) {
+ return $result;
+ } elsif ($d > 7000000) {
+ return $d;
+ }
+ return 0;
+}
+
+
+
+sub setup_script {
+
+$script_str =<<'ENDJS';
+
+
+
+
+
+ENDJS
+
+}
+
+#
+# Actually do the query
+#
+sub query_to_english {
+ my $english = 'Checkins ';
+
+ if( $query_module eq 'allrepositories' ){
+ $english .= "to All Repositories ";
+ }
+ elsif( $query_module ne 'all' && @query_dirs == 0 ){
+ $english .= "to module $query_module ";
+ }
+ elsif( $form{dir} ne "" ) {
+ my $word = "directory";
+ if (@query_dirs > 1) {
+ $word = "directories";
+ }
+ $english .= "to $word $form{dir} ";
+ }
+
+ if ($query_file ne "") {
+ if ($english ne 'Checkins ') {
+ $english .= "and ";
+ }
+ $english .= "to file $query_file ";
+ }
+
+ if( ! ($query_branch =~ /^[ ]*HEAD[ ]*$/i) ){
+ if($query_branch eq '' ){
+ $english .= "on all branches ";
+ }
+ else {
+ $english .= "on branch $query_branch ";
+ }
+ }
+
+ if( $query_who ne '' ){
+ $english .= "by $query_who ";
+ }
+
+ $query_date_type = $form{'date'};
+ if( $query_date_type eq 'hours' ){
+ $english .="in the last $form{hours} hours";
+ }
+ elsif( $query_date_type eq 'day' ){
+ $english .="in the last day";
+ }
+ elsif( $query_date_type eq 'week' ){
+ $english .="in the last week";
+ }
+ elsif( $query_date_type eq 'month' ){
+ $english .="in the last month";
+ }
+ elsif( $query_date_type eq 'all' ){
+ $english .="since the beginning of time";
+ }
+ elsif( $query_date_type eq 'explicit' ){
+ if ( $form{mindate} ne "" && $form{maxdate} ne "" ) {
+ $w1 = "between";
+ $w2 = "and" ;
+ }
+ else {
+ $w1 = "since";
+ $w2 = "before";
+ }
+
+ if( $form{'mindate'} ne "" ){
+ $dd = &parse_date($form{'mindate'});
+ ($sec,$minute,$hour,$mday,$mon,$year) = localtime( $dd );
+ $t = sprintf("%02d/%02d/%02d %02d:%02d",$mon+1,$mday,$year,$hour,$minute);
+ $english .= "$w1 $t ";
+ }
+
+ if( $form{'maxdate'} ne "" ){
+ $dd = &parse_date($form{'maxdate'});
+ ($sec,$minute,$hour,$mday,$mon,$year) = localtime( $dd );
+ $t = sprintf("%02d/%02d/%02d %02d:%02d",$mon+1,$mday,$year,$hour,$minute);
+ $english .= "$w2 $t ";
+ }
+ }
+ return $english . ":";
+}
+
diff --git a/mozilla/webtools/bonsai/cvsquery.pl b/mozilla/webtools/bonsai/cvsquery.pl
new file mode 100755
index 00000000000..454f70139eb
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsquery.pl
@@ -0,0 +1,443 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+require 'utils.pl';
+
+#
+# Constants
+#
+$CI_CHANGE=0;
+$CI_DATE=1;
+$CI_WHO=2;
+$CI_REPOSITORY=3;
+$CI_DIR=4;
+$CI_FILE=5;
+$CI_REV=6;
+$CI_STICKY=7;
+$CI_BRANCH=8;
+$CI_LINES_ADDED=9;
+$CI_LINES_REMOVED=10;
+$CI_LOG=11;
+
+$NOT_LOCAL = 1;
+$IS_LOCAL = 2;
+
+
+if( $CVS_ROOT eq "" ){
+ $CVS_ROOT = pickDefaultRepository();
+}
+
+#global variables
+
+$lines_added = 0;
+$lines_removed = 0;
+
+$modules = {};
+
+if( $ENV{"OS"} eq "Windows_NT" ){
+ # for debugging purposes
+ $CVS_MODULES='k:/warp/projects/bonsai/modules';
+}
+else {
+ $CVS_MODULES="${CVS_ROOT}/CVSROOT/modules";
+ #$CVS_MODULES='data/modules';
+}
+
+open( MOD, "<$CVS_MODULES") || die "can't open ${CVS_MODULES}";
+&parse_modules;
+close( MOD );
+
+1;
+
+#
+# Actually do the query
+#
+sub query_checkins {
+ local($mod_map) = @_;
+ local($ci,$result,$lastlog,$rev,$begin_tag,$end_tag);
+
+ if( $query_module ne 'all' && $query_module ne 'allrepositories' && @query_dirs == 0 ){
+ $have_mod_map = 1;
+ $mod_map = &get_module_map( $query_module );
+ }
+ else {
+ $have_mod_map = 0;
+ $mod_map = {};
+ }
+
+ for $i (@query_dirs ){
+ $i =~ s:^/::; # Strip leading slash.
+ $i =~ s:/$::; # Strip trailing slash.
+
+ if( !$have_mod_map ){
+ $mod_map = {};
+ $have_mod_map = 1;
+ }
+ $mod_map->{$i} = $NOT_LOCAL;
+ }
+
+ if( $query_branch =~ /^[ ]*HEAD[ ]*$/i ){
+ $query_branch_head = 1;
+ }
+
+ $begin_tag = "";
+ $end_tag = "";
+
+ if ( $query_begin_tag ne '') {
+ $begin_tag = load_tag($query_begin_tag);
+ }
+
+ if ( $query_end_tag ne '') {
+ $end_tag = load_tag($query_end_tag);
+ }
+
+
+ $result = [];
+
+ my $db = ConnectToDatabase();
+
+ my $qstring = "select type, UNIX_TIMESTAMP(when), people.who, repositories.repository, dirs.dir, files.file, revision, stickytag, branches.branch, addedlines, removedlines, descs.description from checkins,people,repositories,dirs,files,branches,descs where people.id=whoid and repositories.id=repositoryid and dirs.id=dirid and files.id=fileid and branches.id=branchid and descs.id=descid";
+
+ if( $query_module ne 'allrepositories' ){
+ $qstring .= " and repositories.repository = '$CVS_ROOT'";
+ }
+
+ if ($query_date_min) {
+ my $t = formatSqlTime($query_date_min);
+ $qstring .= " and when >= '$t'";
+ }
+ if ($query_date_max) {
+ my $t = formatSqlTime($query_date_max);
+ $qstring .= " and when <= '$t'";
+ }
+ if ($query_branch_head) {
+ $qstring .= " and branches.branch = ''";
+ } elsif ($query_branch ne '') {
+ my $q = SqlQuote($query_branch);
+ if ($query_branchtype eq 'regexp') {
+ $qstring .=
+ " and branches.branch regexp '$q'";
+ } else {
+ $qstring .=
+ " and (branches.branch = '$q' or branches.branch = 'T$q')";
+ }
+ }
+
+ if( $query_file ne '') {
+ my $q = SqlQuote($query_file);
+ if ($query_filetype eq 'regexp') {
+ $qstring .= " and files.file regexp '$q'";
+ } else {
+ $qstring .= " and files.file = '$q'";
+ }
+ }
+ if ($query_who ne '') {
+ my $q = SqlQuote($query_who);
+ if ($query_whotype eq 'regexp') {
+ $qstring .= " and people.who regexp '$q'";
+ } else {
+ $qstring .= " and people.who = '$q'";
+ }
+ }
+
+ if ($query_logexpr ne '') {
+ my $q = SqlQuote($query_logexpr);
+ $qstring .= " and descs.description regexp '$q'";
+ }
+
+ if ($query_debug) {
+ print " Query: $qstring
";
+ }
+
+ $query = $db->Query($qstring) || die $Mysql::db_errstr;
+
+ $lastlog = 0;
+ while(@row = $query->fetchrow) {
+ $ci = [];
+# print "";
+ for ($i=0 ; $i<=$CI_LOG ; $i++) {
+ $ci->[$i] = $row[$i];
+# print "$row[$i] ";
+ }
+# print "";
+
+
+ if( $have_mod_map &&
+ !&in_module( $mod_map, $ci->[$CI_DIR], $ci->[$CI_FILE] ) ){
+ next;
+ }
+
+ if( $begin_tag) {
+ $key = "$ci->[$CI_DIR]/$ci->[$CI_FILE]";
+ $rev = $begin_tag->{$key};
+ print "
$key begintag is $rev
\n";
+ if ($rev == "" || rev_is_after($ci->[$CI_REV], $rev)) {
+ next;
+ }
+ }
+
+ if( $end_tag) {
+ $key = "$ci->[$CI_DIR]/$ci->[$CI_FILE]";
+ $rev = $end_tag->{$key};
+ print "
$key endtag is $rev
\n";
+ if ($rev == "" || rev_is_after($rev, $ci->[$CI_REV])) {
+ next;
+ }
+ }
+
+ if( $query_logexpr ne '' && !($ci->[$CI_LOG] =~ /$query_logexpr/i) ){
+ next;
+ }
+
+ push( @$result, $ci );
+ }
+
+ for $ci (@{$result}) {
+ $lines_added += $ci->[$CI_LINES_ADDED];
+ $lines_removed += $ci->[$CI_LINES_REMOVED];
+ $versioninfo .= "$ci->[$CI_WHO]|$ci->[$CI_DIR]|$ci->[$CI_FILE]|$ci->[$CI_REV],";
+ }
+ return $result;
+}
+
+sub load_tag {
+ my $tagname = @_[0];
+
+ my $tagfile;
+ my $cvssuffix;
+ my $s;
+ my @line;
+ my $time;
+ my $cmd;
+ my $dir;
+
+ $cvssuffix = $CVS_ROOT;
+ $cvssuffix =~ s/\//_/g;
+
+ $s = $tagname;
+
+ $s =~ s/ /\%20/g;
+ $s =~ s/\%/\%25/g;
+ $s =~ s/\//\%2f/g;
+ $s =~ s/\?/\%3f/g;
+ $s =~ s/\*/\%2a/g;
+
+ $tagfile = "data/taginfo/$cvssuffix/$s";
+
+ open(TAG, "<$tagfile") || die "Unknown tag $tagname";
+ $result = {};
+
+
+print "
parsing tag $tagname\n";
+ while ( ) {
+ chop;
+ @line = split(/\|/);
+ $time = shift @line;
+ $cmd = shift @line;
+ if ($cmd != "add") {
+ # We ought to be able to cope with these... XXX
+ next;
+ }
+ $dir = shift @line;
+ $dir =~ s:^$CVS_ROOT/::;
+ $dir =~ s:^\./::;
+
+ while (@line) {
+ $file = shift @line;
+ $file = "$dir/$file";
+ $version = shift @line;
+ $result->{$file} = $version;
+print "
Added ($file,$version) for tag $tagname
\n";
+ }
+ }
+
+ return $result;
+}
+
+
+
+sub rev_is_after {
+ my $r1 = shift @_;
+ my $r2 = shift @_;
+
+ my @a = split /:/, $r1;
+ my @b = split /:/, $r2;
+
+ if (@b > @a) {
+ return 1;
+ }
+
+ if (@b < @a) {
+ return 0;
+ }
+
+ for (my $i=0 ; $i<@a ; $i++) {
+ if ($a[$i] > $b[$i]) {return 1;}
+ if ($a[$i] < $b[$i]) {return 0;}
+ }
+ return 0;
+}
+
+
+
+sub find_date_offset {
+ local( $o, $d, $done, $line );
+ $done = 0;
+ local($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
+ $atime,$mtime,$ctime,$blksize,$blocks) = stat($CHECKIN_INDEX_FILE);
+ if ($mtime eq "" || time() - $mtime> 24 * 60 * 60) {
+ print "Please wait -- rebuilding index file...
\n";
+ system "./cvsindex.pl $CVS_ROOT";
+ print "...OK, done.
\n";
+ }
+ Lock();
+ if(! open(IDX , "<$CHECKIN_INDEX_FILE") ){
+ print "can't open index
";
+ Unlock();
+ return 0;
+ }
+ $i = 0;
+ while( ($line = ) && !$done){
+ chop($line);
+ ($o,$d) = split(/\|/,$line);
+ if( $d && $query_date_min > $d ){
+ $done = 1;
+ }
+ $i++;
+ }
+ if( $F_DEBUG ){
+ print "seekdate($d) seekoffset($o) readcount($i)\n";
+ }
+ close IDX;
+ Unlock();
+ return $o;
+}
+
+
+sub in_module {
+ local($mod_map, $dirname, $filename ) = @_;
+ local( @path );
+ local( $i, $fp, $local );
+
+ #
+ #quick check if it is already in there.
+ #
+ if( $mod_map{$dirname} ){
+ return 1;
+ }
+
+
+ @path = split(/\//, $dirname);
+
+ $fp = '';
+
+ for( $i = 0; $i < @path; $i++){
+
+ $fp .= ($fp ne '' ? '/' : '') . $path[$i];
+
+ if( $local = $mod_map->{$fp} ){
+ if( $local == $IS_LOCAL ){
+ if( $i == (@path-1) ){
+ return 1;
+ }
+ }
+ else {
+ # Add directories to the map as we encounter them so we go
+ # faster
+ if( $mod_map{$dirname} == 0 ){
+ $mod_map{$dirname} = $IS_LOCAL;
+ }
+ return 1;
+ }
+ }
+ }
+ if( $mod_map->{ $fp . '/' . $filename} ) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+
+sub get_module_map {
+ local($name) = @_;
+ local($mod_map);
+ $mod_map = {};
+ &build_map( $name, $mod_map );
+ return $mod_map;
+}
+
+sub parse_modules {
+ while( $l = &get_line ){
+ ($mod_name, $flag, @params) = split(/[ \t]+/,$l);
+ if( $flag ne '-a' ){
+ next;
+ }
+ $modules->{$mod_name} = [@params];
+ }
+}
+
+
+sub build_map {
+ local($name,$mod_map) = @_;
+ local($bFound, $local);
+
+ $local = $NOT_LOCAL;
+ $bFound = 0;
+
+ for $i ( @{$modules->{$name}} ){
+ $bFound = 1;
+ if( $i eq '-l' ){
+ $local = $IS_LOCAL;
+ }
+ elsif( !build_map($i, $mod_map )){
+ $mod_map->{$i} = $local;
+ }
+ }
+ return $bFound;
+}
+
+
+
+sub get_line {
+ local($l, $save);
+
+ $bContinue = 1;
+
+ while( $bContinue && ($l = ) ){
+ chop($l);
+ if( $l =~ /^[ \t]*\#/
+ || $l =~ /^[ \t]*$/ ){
+ $l='';
+ }
+ elsif( $l =~ /\\[ \t]*$/ ){
+ chop ($l);
+ $save .= $l . ' ';
+ }
+ elsif( $l eq '' && $save eq ''){
+ # ignore blank lines
+ }
+ else {
+ $bContinue = 0;
+ }
+ }
+ return $save . $l;
+}
+
+
diff --git a/mozilla/webtools/bonsai/cvsqueryform.cgi b/mozilla/webtools/bonsai/cvsqueryform.cgi
new file mode 100755
index 00000000000..6a6a2ae81b2
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsqueryform.cgi
@@ -0,0 +1,321 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+# Query the CVS database.
+#
+require 'lloydcgi.pl';
+require 'cvsmenu.pl';
+require 'utils.pl';
+
+
+$|=1;
+
+$CVS_ROOT = $form{"cvsroot"};
+
+print "Content-type: text/html\n\n";
+
+require 'modules.pl';
+
+
+EmitHtmlHeader("CVS Query Form", $CVS_ROOT);
+
+
+print "
+
+
";
+
+sub sortTest {
+ if( $_[0] eq $form{sortby} ){
+ return " SELECTED";
+ }
+ else {
+ return "";
+ }
+}
+
+refigureStartDateIfNecessary($CVS_ROOT);
+
+sub dateTest {
+ if( $_[0] eq $form{date} ){
+ return " CHECKED value=$_[0]";
+ }
+ else {
+ return "value=$_[0]";
+ }
+}
+
+sub regexpradio {
+ my($name) = @_;
+ my $useregexp = ($form{$name} eq 'regexp');
+ my $c1 = $useregexp ? "" : "checked";
+ my $c2 = $useregexp ? "checked" : "";
+ return "
+Exact match
+
+Regular expression";
+}
+
+
+sub fetchCachedStartDate {
+ my ($repository) = @_;
+ open(CACHE, ") {
+ chop();
+ my($rep,$date,$cachedate) = split(/\|/);
+ if ($rep eq $repository) {
+ $rememberedcachedate = $cachedate;
+ return $date;
+ }
+ }
+ return "unknown";
+}
+
+sub refigureStartDateIfNecessary {
+ my ($repository) = @_;
+ my $now = time();
+ if ((defined $rememberedcachedate) &&
+ $now - $rememberedcachedate < 24*60*60 &&
+ $rememberedcachedate < $now) {
+ return;
+ }
+
+ my $db = ConnectToDatabase();
+
+ my $query = $db->Query("select min(when) from checkins,repositories where repositories.id = repositoryid and repository = '$CVS_ROOT'") || die $Mysql::db_errstr;
+
+ my @row = $query->fetchrow();
+ my $startdate = $row[0];
+ if ($startdate eq "") {
+ $startdate = "nonexistant";
+ }
+ open(OUTCACHE, ">data/cachedstartdates.$$") || die "Can't open output date cache file";
+ if (open(INCACHE, ") {
+ chop();
+ my($rep,$date,$cachedate) = split(/\|/);
+ if ($rep ne $repository) {
+ print OUTCACHE "$_\n";
+ }
+ }
+ close INCACHE;
+ }
+ print OUTCACHE "$repository|$startdate|$now\n";
+ close OUTCACHE;
+ rename "data/cachedstartdates.$$", "data/cachedstartdates";
+}
diff --git a/mozilla/webtools/bonsai/cvsregexp.html b/mozilla/webtools/bonsai/cvsregexp.html
new file mode 100644
index 00000000000..b9d777f0ccc
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsregexp.html
@@ -0,0 +1,257 @@
+
+
+
+
+
+ Regular expressions in the cvs query tool
+
+
+
+
+Description of MySQL regular expression syntax.
+Regular expressions are a powerful way of specifying complex searches.
+
+MySQL uses regular Henry Spencers inplementation of regular expressions.
+And that is aimed to conform to POSIX 1003.2. MySQL uses the extended
+version.
+
+
To get more exact information see Henry Spencers regex.7 manual.
+
+
This is a simplistic reference that skips the details. From here on
+a regualr expressions is called a regexp.
+
+
A regular expression describes a set of strings. The simplest case is
+one that has no special characters in it. For example the regexp hello
+matches hello and nothing else.
+
+
Nontrivial regular expressions use certain special constructs so that
+they can match more than one string. For example, the regexp hello|word
+matches either the string hello or the string word.
+
+
And a more comples example regexp B[an]*s matches any of the
+strings Bananas, Baaaaas, Bs and all other string
+starting with a B and continuing with any number of a
+n and ending with a s.
+
+
The following special characters/constructs are known.
+
+-
+^
+
+-
+Start of whole string.
+
+mysql> select "fo\nfo" regexp "^fo$"; -> 0
+mysql> select "fofo" regexp "^fo"; -> 1
+
+-
+$
+
+-
+End of whole string.
+
+mysql> select "fo\no" regexp "^fo\no$"; -> 1
+mysql> select "fo\no" regexp "^fo$"; -> 0
+
+-
+.
+
+-
+Any character (including newline).
+
+mysql> select "fofo" regexp "^f.*"; -> 1
+mysql> select "fo\nfo" regexp "^f.*"; -> 1
+
+-
+a*
+
+-
+Any sequence of zero or more a's.
+
+mysql> select "Ban" regexp "^Ba*n"; -> 1
+mysql> select "Baaan" regexp "^Ba*n"; -> 1
+mysql> select "Bn" regexp "^Ba*n"; -> 1
+
+-
+a+
+
+-
+Any sequence of one or more a's.
+
+mysql> select "Ban" regexp "^Ba+n"; -> 1
+mysql> select "Bn" regexp "^Ba+n"; -> 0
+
+-
+a?
+
+-
+Either zero or one a.
+
+mysql> select "Bn" regexp "^Ba?n"; -> 1
+mysql> select "Ban" regexp "^Ba?n"; -> 1
+mysql> select "Baan" regexp "^Ba?n"; -> 0
+
+-
+de|abc
+
+-
+Either the sequence de or abc.
+
+mysql> select "pi" regexp "pi|apa"; -> 1
+mysql> select "axe" regexp "pi|apa"; -> 0
+mysql> select "apa" regexp "pi|apa"; -> 1
+mysql> select "apa" regexp "^(pi|apa)$"; -> 1
+mysql> select "pi" regexp "^(pi|apa)$"; -> 1
+mysql> select "pix" regexp "^(pi|apa)$"; -> 0
+
+-
+(abc)*
+
+-
+Zero or more times the sequence abc.
+
+mysql> select "pi" regexp "^(pi)+$"; -> 1
+mysql> select "pip" regexp "^(pi)+$"; -> 0
+mysql> select "pipi" regexp "^(pi)+$"; -> 1
+
+-
+{1}
+
+-
+{2,3}
+
+-
+The is a more general way of writing regexps that match many occurences.
+
+
+-
+a*
+
+-
+Can be written as a{0,}.
+
+-
++
+
+-
+Can be written as a{1,}.
+
+-
+?
+
+-
+Can be written as a{0,1}.
+
+To be more precice an atom followed by a bound containing one integer i
+and no comma matches a sequence of exactly i matches of the atom.
+An atom followed by a bound containing one integer i and a comma
+matches a sequence of i or more matches of the atom. An atom followed
+by a bound containing two integers i and j matches a
+sequence of i through j (inclusive) matches of the atom.
+Both arguments must 0 >= value <= RE_DUP_MAX (default 255),
+and if there are two of them, the second must be bigger or equal to the
+first.
+-
+[a-dX]
+
+-
+[^a-dX]
+
+-
+Any character which is (not if ^ is used) either a, b,
+c, d or X. To include ] it has to be
+written first. To include - it has to be written first or last.
+So [0-9] matches any decimal digit. All character that does not
+have a defined mening inside a [] pair has no special meaning
+and matches only itself.
+
+mysql> select "aXbc" regexp "[a-dXYZ]"; -> 1
+mysql> select "aXbc" regexp "^[a-dXYZ]$"; -> 0
+mysql> select "aXbc" regexp "^[a-dXYZ]+$"; -> 1
+mysql> select "aXbc" regexp "^[^a-dXYZ]+$"; -> 0
+mysql> select "gheis" regexp "^[^a-dXYZ]+$"; -> 1
+mysql> select "gheisa" regexp "^[^a-dXYZ]+$"; -> 0
+
+-
+[[.characters.]]
+
+-
+The sequence of characters of that collating element. The sequence is a
+single element of the bracket expression's list. A bracket expression containing
+a multi-character collating element can thus match more than one character,
+e.g. if the collating sequence includes a ch collating element,
+then the RE [[.ch.]]*c matches the first five characters of chchcc.
+
+-
+[=character-class=]
+
+-
+An equivalence class, standing for the sequences of characters of all collating
+elements equivalent to that one, including itself. For example, if o
+and (+) are the members of an equivalence class, then [[=o=]],
+[[=(+)=]], and [o(+)] are all synonymous. An equivalence
+class may not be an endpoint of a range.
+
+-
+[:character_class:]
+
+-
+Within a bracket expression, the name of a character class enclosed in
+[: and :] stands for the list of all characters belonging
+to that class. Standard character class names are:
+
+
+
+alnum
+
+digit
+
+punct
+
+
+
+alpha
+
+graph
+
+space
+
+
+
+blank
+
+lower
+
+upper
+
+
+
+cntrl
+
+print
+
+xdigit
+
+
+These stand for the character classes defined in ctype(3). A locale may
+provide others. A character class may not be used as an endpoint of a range.
+mysql> select "justalnums" regexp "[[:alnum:]]+"; -> 1
+mysql> select "!!" regexp "[[:alnum:]]+"; -> 0
+
+
+[[:<:]]
+
+
+[[:>:]] These match the null string at the beginning and end of a word
+respectively. A word is defined as a sequence of word characters which
+is neither preceded nor followed by word characters. A word character is
+an alnum character (as defined by ctype(3)) or an underscore.
+
+mysql> select "a word a" regexp "[[:<:]]word[[:>:]]"; -> 1
+mysql> select "a xword a" regexp "[[:<:]]word[[:>:]]"; -> 0
+
+
+mysql> select "weeknights" regexp "^(wee|week)(knights|nights)$"; -> 1
+
+
+
diff --git a/mozilla/webtools/bonsai/cvsview2.cgi b/mozilla/webtools/bonsai/cvsview2.cgi
new file mode 100755
index 00000000000..ac903c7c2af
--- /dev/null
+++ b/mozilla/webtools/bonsai/cvsview2.cgi
@@ -0,0 +1,963 @@
+#!/usr/bonsaitools/bin/perl --
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+# cvsview.cgi - fake up some HTML based on RCS logs and diffs
+#
+# The contents of this file are subject to the Netscape Public License
+# Version 1.0 (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.
+
+# brendan and fur
+#
+# TODO in no particular order:
+# - Mocha-automate the main page's form so clicking on rev links in the table
+# change the default filename and revisions.
+# - Add a tab width input to the main page's form.
+# - Include log message in wasted horizontal real-estate of Shortcuts frame.
+# - Make old and new diff lines go to separate, side-by-side frames, and use
+# Mocha to slave their scrollbars together.
+# - Allow expansion of the top-level table to include the revision histories
+# of all the files in the directory.
+# - More more more xdiff/gdiff-like features...
+#
+
+#
+# SRCROOTS is an array of repository roots under which to look for CVS files.
+#
+
+# Figure out which directory bonsai is in by looking at argv[0]
+
+$bonsaidir = $0;
+$bonsaidir =~ s:/[^/]*$::; # Remove last word, and slash before it.
+if ($bonsaidir eq '') {
+ $bonsaidir = '.';
+}
+
+chdir $bonsaidir || die "Couldn't chdir to $bonsaidir";
+require 'utils.pl';
+
+loadConfigData();
+
+NEXTTREE: foreach $i (@treelist) {
+ $r = $treeinfo{$i}->{'repository'};
+ foreach $j (@SRCROOTS) {
+ if ($r eq $j) {
+ next NEXTTREE;
+ }
+ }
+ push @SRCROOTS, $r;
+}
+
+
+$MAX_REVS = 8;
+
+#
+# Make sure both kinds of standard output go to STDOUT.
+# XXX dup stdout onto stderr and flush stdout after the following prints
+#
+# Until then, replace standard die built-in with our own.
+sub die {
+ print 'fatal error: ';
+ print @_;
+ exit;
+}
+
+# Consume one token from the already opened RCSFILE filehandle.
+# Unescape string tokens, if necessary.
+sub get_token {
+ # Erase all-whitespace lines.
+ while ($line_buffer =~ /^$/) {
+ &die ('Unexpected EOF') if eof(RCSFILE);
+ $line_buffer = ;
+ $line_buffer =~ s/^\s+//; # Erase leading whitespace
+ }
+
+ # A string of non-whitespace characters is a token ...
+ return $1 if ($line_buffer =~ s/^([^;@][^;\s]*)\s*//o);
+
+ # ...and so is a single semicolon ...
+ return ';' if ($line_buffer =~ s/^;\s*//o);
+
+ # ...or an RCS-encoded string that starts with an @ character.
+ $line_buffer =~ s/^@([^@]*)//o;
+ $token = $1;
+
+ # Detect single @ character used to close RCS-encoded string.
+ while ($line_buffer !~ /^@[^@]*$/o) {
+ $token .= $line_buffer;
+ &die ('Unexpected EOF') if eof(RCSFILE);
+ $line_buffer = ;
+ }
+
+ # Retain the remainder of the line after the terminating @ character.
+ ($line_buffer) = ($line_buffer =~ /^@\s*([^@]*)/o);
+
+ # Undo escape-coding of @ characters.
+ $token =~ s/@@/@/og;
+
+ return $token;
+}
+
+# Consume a token from RCS filehandle and ensure that it matches
+# the given string constant.
+sub match_token {
+ local ($match) = @_;
+
+ local ($token) = &get_token;
+ &die ("Unexpected parsing error in RCS file.\n",
+ "Expected token: $match, but saw: $token\n")
+ if ($token ne $match);
+}
+
+# Push RCS token back into the input buffer.
+sub unget_token {
+ local ($token) = @_;
+ $line_buffer = "$token $line_buffer";
+}
+
+# Parses "administrative" header of RCS files, setting these globals:
+#
+# $head_revision -- Revision for which cleartext is stored
+# $principal_branch
+# $file_description
+# %revision_symbolic_name -- mapping from numerical revision # to symbolic tag
+# %tag_revision -- mapping from symbolic tag to numerical revision #
+#
+sub parse_rcs_admin {
+ local ($token, $tag, $tag_name, $tag_revision);
+ local (@tags);
+
+ # Undefine variables, because we may have already read another RCS file
+ undef %tag_revision;
+ undef %revision_symbolic_name;
+
+ while (1) {
+ # Read initial token at beginning of line
+ $token = &get_token(RCSFILE);
+
+ # We're done once we reach the description of the RCS tree
+ if ($token =~ /^\d/o) {
+ &unget_token($token);
+ return;
+ }
+
+# print "token: $token\n";
+
+ if ($token eq 'head') {
+ $head_revision = &get_token;
+ &get_token; # Eat semicolon
+ } elsif ($token eq 'branch') {
+ $principal_branch = &get_token;
+ &get_token; # Eat semicolon
+ } elsif ($token eq 'symbols') {
+
+ # Create an associate array that maps from tag name to
+ # revision number and vice-versa.
+ while (($tag = &get_token) ne ';') {
+ ($tag_name, $tag_revision) = split(':', $tag);
+
+ $tag_revision{$tag_name} = $tag_revision;
+ $revision_symbolic_name{$tag_revision} = $tag_name;
+ }
+ } elsif ($token eq 'comment') {
+ $file_description = &get_token;
+ &get_token; # Eat semicolon
+
+ # Ignore all these other fields - We don't care about them.
+ } elsif (($token eq 'locks') ||
+ ($token eq 'strict') ||
+ ($token eq 'expand') ||
+ ($token eq 'access')) {
+ (1) while (&get_token ne ';');
+ } else {
+ warn ("Unexpected RCS token: $token\n");
+ }
+ }
+
+ &die('Unexpected EOF');
+}
+
+# Construct associative arrays that represent the topology of the RCS tree
+# and other arrays that contain info about individual revisions.
+#
+# The following associative arrays are created, keyed by revision number:
+# %revision_date -- e.g. "96.02.23.00.21.52"
+# %timestamp -- seconds since 12:00 AM, Jan 1, 1970 GMT
+# %revision_author -- e.g. "tom"
+# %revision_branches -- descendant branch revisions, separated by spaces,
+# e.g. "1.21.4.1 1.21.2.6.1"
+# %prev_revision -- revision number of previous *ancestor* in RCS tree.
+# Traversal of this array occurs in the direction
+# of the primordial (1.1) revision.
+# %prev_delta -- revision number of previous revision which forms the
+# basis for the edit commands in this revision.
+# This causes the tree to be traversed towards the
+# trunk when on a branch, and towards the latest trunk
+# revision when on the trunk.
+# %next_delta -- revision number of next "delta". Inverts %prev_delta.
+#
+# Also creates %last_revision, keyed by a branch revision number, which
+# indicates the latest revision on a given branch,
+# e.g. $last_revision{"1.2.8"} == 1.2.8.5
+#
+sub parse_rcs_tree {
+ local($revision, $date, $author, $branches, $next);
+ local($branch, $is_trunk_revision);
+
+ # Undefine variables, because we may have already read another RCS file
+ undef %revision_date;
+ undef %timestamp;
+ undef %revision_author;
+ undef %revision_branches;
+ undef %prev_revision;
+ undef %prev_delta;
+ undef %next_delta;
+ undef %last_revision;
+
+ while (1) {
+ $revision = &get_token;
+
+ # End of RCS tree description ?
+ if ($revision eq 'desc') {
+ &unget_token($revision);
+ return;
+ }
+
+ $is_trunk_revision = ($revision =~ /^[0-9]+\.[0-9]+$/);
+
+ $tag_revision{$revision} = $revision;
+ ($branch) = $revision =~ /(.*)\.[0-9]+/o;
+ $last_revision{$branch} = $revision;
+
+ # Parse date
+ &match_token('date');
+ $date = &get_token;
+ $revision_date{$revision} = $date;
+ &match_token(';');
+
+ # Convert date into timestamp
+# @date_fields = reverse(split(/\./, $date));
+# $date_fields[4]--; # Month ranges from 0-11, not 1-12
+# $timestamp{$revision} = &timegm(@date_fields);
+
+ # Parse author
+ &match_token('author');
+ $author = &get_token;
+ $revision_author{$revision} = $author;
+ &match_token(';');
+
+ # Parse state;
+ &match_token('state');
+ (1) while (&get_token ne ';');
+
+ # Parse branches
+ &match_token('branches');
+ $branches = '';
+ while (($token = &get_token) ne ';') {
+ $prev_revision{$token} = $revision;
+ $prev_delta{$token} = $revision;
+ $branches .= "$token ";
+ }
+ $revision_branches{$revision} = $branches;
+
+ # Parse revision of next delta in chain
+ &match_token('next');
+ $next = '';
+ if (($token = &get_token) ne ';') {
+ $next = $token;
+ &get_token; # Eat semicolon
+ $next_delta{$revision} = $next;
+ $prev_delta{$next} = $revision;
+ if ($is_trunk_revision) {
+ $prev_revision{$revision} = $next;
+ } else {
+ $prev_revision{$next} = $revision;
+ }
+ }
+
+ if ($debug > 1) {
+ print "revision = $revision\n";
+ print "date = $date\n";
+ print "author = $author\n";
+ print "branches = $branches\n";
+ print "next = $next\n\n";
+ }
+ }
+}
+
+# Reads and parses complete RCS file from already-opened RCSFILE descriptor.
+sub parse_rcs_file {
+ local ($file) = @_;
+ &die("Couldn't open $file\n") if !open(RCSFILE, "< $file");
+ $line_buffer = '';
+ print "Reading RCS admin...\n" if ($debug);
+ &parse_rcs_admin();
+ print "Reading RCS revision tree topology...\n" if ($debug);
+ &parse_rcs_tree();
+ print "Done reading RCS file...\n" if ($debug);
+ close(RCSFILE);
+}
+
+# Map a tag to a numerical revision number. The tag can be a symbolic
+# branch tag, a symbolic revision tag, or an ordinary numerical
+# revision number.
+sub map_tag_to_revision {
+ local($tag_or_revision) = @_;
+
+ local ($revision) = $tag_revision{$tag_or_revision};
+
+ # Is this a branch tag, e.g. xxx.yyy.0.zzz
+ if ($revision =~ /(.*)\.0\.([0-9]+)/o) {
+ $branch = $1 . '.' . $2;
+ # Return latest revision on the branch, if any.
+ return $last_revision{$branch} if (defined($last_revision{$branch}));
+ return $1; # No revisions on branch - return branch point
+ } else {
+ return $revision;
+ }
+}
+
+#
+# Print HTTP content-type header and the header-delimiting extra newline.
+#
+print "Content-type: text/html\n\n";
+
+$request_method = $ENV{'REQUEST_METHOD'}; # e.g., "GET", "POST", etc.
+$script_name = $ENV{'SCRIPT_NAME'};
+$prefix = $script_name . $ENV{PATH_INFO} . '?'; # prefix for HREF= entries
+$query_string = $ENV{QUERY_STRING};
+
+# Undo % URL-encoding
+while ($query_string =~ /(.*)\%([0-9a-fA-F][0-9a-fA-F])(.*)/) {
+ # XXX - inefficient
+ $query_string = $1 . pack('c', hex($2)) . $3;
+}
+
+&die("REQUEST_METHOD 'GET' expected: got '$request_method'\n")
+ if ($request_method ne 'GET');
+
+# Default option values
+$opt_diff_mode = 'context';
+$opt_whitespace_mode = 'show';
+
+# Parse options in URL. For example,
+# http://w3/cgi/cvsview.pl?subdir=foo&file=bar would assign
+# $opt_subdir = foo and $opt_file = bar.
+foreach $option (split(/&/, $query_string)) {
+ &die("command $opt_command: garbled option $option\n")
+ if ($option !~ /^([^=]+)=(.*)/);
+ eval('$opt_' . $1 . '="' . $2 . '";');
+}
+
+if( $opt_branch eq 'HEAD' ) { $opt_branch = ''; }
+
+# Configuration colors for diff output.
+
+$stable_bg_color = 'White';
+$skipping_bg_color = '#c0c0c0';
+$header_bg_color = 'Orange';
+$change_bg_color = 'LightBlue';
+$addition_bg_color = 'LightGreen';
+$deletion_bg_color = 'LightGreen';
+$diff_bg_color = 'White';
+
+# Ensure that necessary arguments are present
+&die("command not defined in URL\n") if $opt_command eq '';
+&die("command $opt_command: subdir not defined\n") if $opt_subdir eq '';
+if ($opt_command eq 'DIFF' ||
+ $opt_command eq 'DIFF_FRAMESET' ||
+ $opt_command eq 'DIFF_LINKS') {
+ &die("command $opt_command: file not defined in URL\n") if $opt_file eq '';
+ &die("command $opt_command: rev1 not defined in URL\n") if $opt_rev1 eq '';
+ &die("command $opt_command: rev2 not defined in URL\n") if $opt_rev2 eq '';
+}
+
+# Propagate diff options to created links
+$prefix .= "diff_mode=$opt_diff_mode";
+$prefix .= "&whitespace_mode=$opt_whitespace_mode";
+$prefix .= "&root=$opt_root";
+
+# Create a shorthand for the longest common initial substring of our URL.
+$magic_url = "$prefix&subdir=$opt_subdir";
+
+# Now that we've munged QUERY_STRING into perl variables, set rcsdiff options.
+$rcsdiff = '/tools/ns/bin/rcsdiff -f';
+$rcsdiff .= ' -w' if ($opt_whitespace_mode eq 'ignore');
+
+# Handle the "root" argument
+#
+if (defined($root = $opt_root) && $root ne '') {
+ $root =~ s|/$||;
+ if (-d $root) {
+ unshift(@SRCROOTS, $root);
+ } else {
+ print "Error: Root, $root, is not a directory.
\n";
+ print "\n";
+ exit;
+ }
+}
+foreach $root (@SRCROOTS) {
+ $dir = "$root/$opt_subdir";
+ if (-d $dir) {
+ $found = 1;
+ last;
+ }
+}
+if (!$found) {
+ print "Error: $opt_subdir not found in "
+ .join(',', @SRCROOTS), "\n";
+ exit;
+}
+
+# Create top-level frameset document.
+sub do_diff_frameset {
+ chdir($dir);
+
+ print "$opt_file: $opt_rev1 vs. $opt_rev2 \n";
+ print "\n";
+}
+
+
+# Create links to document created by DIFF command.
+sub do_diff_links {
+
+ chdir($dir);
+
+ open(RCSDIFF, "$rcsdiff -r$opt_rev1 -r$opt_rev2 $opt_file 2>/dev/null |");
+
+ print "\n";
+ print "\n";
+ print "";
+ print "\n";
+
+ print "$opt_file: $opt_rev1 vs. $opt_rev2 \n";
+
+ print '\n";
+}
+
+
+# Default tab width, although it's frequently 4.
+$tab_width = 8;
+
+sub next_tab_stop {
+ local ($pos) = @_;
+
+ return int(($pos + $tab_width) / $tab_width) * $tab_width;
+}
+
+#
+# Look for the magic emacs tab width comment, or for long lines with more
+# than 4 leading tabs in more than 50% of the lines that start with a tab.
+# In the latter case, set $tab_width to 4.
+#
+sub guess_tab_width {
+ local ($opt_file) = @_;
+ local ($found_tab_width) = 0;
+ local ($many_tabs, $any_tabs) = (0, 0);
+
+ open(RCSFILE, "$opt_file");
+ while () {
+ if (/tab-width: (\d)/) {
+ $tab_width = $1;
+ $found_tab_width = 1;
+ last;
+ }
+ if (/^(\t+)/) {
+ $many_tabs++ if (length($1) >= 4);
+ $any_tabs++;
+ }
+ }
+ if (!$found_tab_width && $many_tabs > $any_tabs / 2) {
+ $tab_width = 4;
+ }
+ close(RCSFILE);
+}
+
+# Create gdiff-like output.
+sub do_diff {
+
+ print "";
+ print "$opt_file: $opt_rev1 vs. $opt_rev2 \n";
+ print "";
+ print "";
+
+ chdir($dir);
+
+ local ($rcsfile) = "$opt_file,v";
+ $rcsfile = "Attic/$opt_file,v" if (! -r $rcsfile);
+ &guess_tab_width($rcsfile);
+
+ &html_diff($rcsfile, $opt_rev1, $opt_rev2);
+ print "\n\n";
+}
+
+
+# Show specified CVS log entry.
+sub do_log {
+
+ chdir($dir);
+
+ print "$opt_file: $opt_rev CVS log entry \n";
+ print '';
+ open(RCSLOG, "rlog -r$opt_rev $opt_file |");
+
+ while () {
+ last if (/^revision $opt_rev$/);
+ }
+
+ while () {
+ last if (/^===============================================/);
+ print "$_
";
+ }
+ close(RCSLOG);
+
+ print '
';
+}
+
+
+#
+# Main script: generate a table of revision diff and log message hotlinks
+# for each modified file in $opt_subdir, and a form for choosing a file and any
+# two of its revisions.
+#
+sub do_directory {
+
+ $output = "";
+
+ foreach $path (split('/',$opt_subdir)) {
+ $link_path .= $path;
+ $output .= "$path/ ";
+ $link_path .= '/';
+ }
+ chop ($output);
+
+ if ($opt_branch) {
+ $output .= "
Branch: $opt_branch";
+ }
+ $output .= "";
+
+ EmitHtmlHeader("CVS Differences", $output);
+
+ chdir($dir);
+
+ print "\n";
+
+ foreach $file (split(/\+/, $opt_files)) {
+ local ($path) = "$dir/$file,v";
+ $path = "$dir/Attic/$file,v" if (! -r $path);
+ &parse_rcs_file($path);
+
+ my $lxr_base = "http://cvs-mirror.mozilla.org/webtools/lxr/source";
+
+ # total kludge!! lxr omits the top-level "mozilla" directory...
+ my $lxr_path = "$opt_subdir/$file";
+ $lxr_path =~ s@^ns/@@;
+ $lxr_path =~ s@^mozilla/@@;
+
+ my $lxr_link = "$lxr_base/$lxr_path";
+
+ print "";
+ print "$file
";
+ print "Change Log\n";
+ print " \n";
+
+ if ($opt_branch) {
+ $first_rev = &map_tag_to_revision($opt_branch);
+ &die("$0: error: -r: No such revision: $opt_branch\n")
+ if ($first_rev eq '');
+ } else {
+ $first_rev = $head_revision;
+ }
+
+ $skip = $opt_skip;
+ $revs_remaining = $MAX_REVS;
+ for ($rev = $first_rev; $rev; $rev = $prev) {
+ $prev = $prev_revision{$rev};
+ next if $skip-- > 0;
+ if (!$revs_remaining--) {
+ #print '';
+ print ' ';
+ print "Prior revisions", " \n";
+ last;
+ }
+
+ my $href_open = "";
+ my $href_close = "";
+ if ( $prev && $rev ) {
+ $href_open = "";
+ }
+ print "$href_open$rev$href_close
";
+ print "$revision_author{$rev} ";
+ }
+
+ print " \n";
+
+ if (0) {
+ print "\n";
+ $skip = $opt_skip;
+ $revs_remaining = $MAX_REVS;
+ for ($rev = $first_rev; $rev; $rev = $prev_revision{$rev}) {
+ next if $skip-- > 0;
+ last if !$revs_remaining--;
+ print "$revision_author{$rev}",
+ " \n";
+ }
+ print " \n";}
+ }
+
+ print "
\n";
+ print '\n";
+
+ &print_bottom;
+}
+
+#
+# This function generates a gdiff-style, side-by-side display using HTML.
+# It requires two arguments, each of which must be an open filehandle.
+# The first filehandle, DIFF, must be a `diff -f` style output containing
+# commands to convert the contents of the second filehandle, OLDREV, into
+# a later version of OLDREV's file.
+#
+sub html_diff {
+ local ($file, $rev1, $rev2) = @_;
+ local ($old_line_num) = 1;
+
+ open(DIFF, "$rcsdiff -f -r$rev1 -r$rev2 $file 2>/dev/null |");
+ open(OLDREV, "/tools/ns/bin/co -p$rev1 $file 2>/dev/null |");
+
+ $anchor_num = 0;
+
+ if ($ENV{'HTTP_USER_AGENT'} =~ /Win/) {
+ $font_tag = "";
+ } else {
+ # We don't want your stinking Windows font
+ $font_tag = "";
+ }
+ print "
';
+ print "Version $rev1 Version $rev2 ";
+ while () {
+ $mark = 0;
+ if (/^a(\d+)/) {
+ $point = $1;
+ &skip_to_line($point + 1, *OLDREV, *old_line_num);
+ while () {
+ last if (/^\.$/);
+ &print_row('', $stable_bg_color, $_, $addition_bg_color);
+ }
+ } elsif ((($point, $mark) = /^c(\d+) (\d+)$/) ||
+ (($point) = /^c(\d+)$/)) {
+ $mark = $point if (!$mark);
+ &skip_to_line($point, *OLDREV, *old_line_num);
+ while () {
+ last if (/^\.$/);
+ if ($old_line_num <= $mark) {
+ $old_line = ;
+ $old_line_num++;
+ } else {
+ $old_line = ''
+ }
+ &print_row($old_line, $change_bg_color, $_, $change_bg_color);
+ }
+ while ($old_line_num <= $mark) {
+ $old_line = ;
+ $old_line_num++;
+ &print_row($old_line, $change_bg_color, '', $change_bg_color);
+ }
+ } elsif ((($point, $mark) = /^d(\d+) (\d+)$/) ||
+ (($point) = /^d(\d+)$/)) {
+ $mark = $point if (!$mark);
+ &skip_to_line($point, *OLDREV, *old_line_num);
+ while ($old_line = ) {
+ $old_line_num++;
+ &print_row($old_line, $deletion_bg_color, '', $stable_bg_color);
+ last if ($. == $mark);
+ }
+ } else {
+ print "
Internal error:",
+ " unknown command $_",
+ " at $. in $opt_file $opt_rev1\n";
+ exit;
+ }
+ }
+
+ #
+ # Print the remaining lines in the original file. These are lines that
+ # were not modified in the later revision
+ #
+ local ($base_old_line_num) = $old_line_num;
+ while ($old_line = ) {
+ $old_line_num++;
+ &print_row($old_line, $stable_bg_color, $old_line, $stable_bg_color)
+ if ($opt_diff_mode eq 'full' ||
+ $old_line_num <= $base_old_line_num + 5);
+ }
+
+# print " \n";
+ print " \n";
+
+ &print_bottom;
+
+ close(OLDREV);
+ close(DIFF);
+}
+
+sub skip_to_line {
+ local ($line_num, *OLDREV, *old_line_num) = @_;
+ local ($anchor_printed) = 0;
+ local ($skip_line_printed) = ($line_num - $old_line_num <= 10);
+ local ($base_old_line_num) = $old_line_num;
+
+
+ while ($old_line_num < $line_num) {
+ if (!$anchor_printed && $old_line_num >= $line_num - 10) {
+ print "\n";
+ $anchor_printed = 1;
+ }
+
+ if ($opt_diff_mode eq 'context' && !$skip_line_printed &&
+ $line_num - 5 <= $old_line_num) {
+ print "\n\n";
+ print "';
+ print "",
+ "Skipping to line $old_line_num: ";
+ $skip_line_printed = 1;
+ }
+
+ $old_line = ;
+ $old_line_num++;
+
+ &print_row($old_line, $stable_bg_color, $old_line, $stable_bg_color)
+ if ($opt_diff_mode eq 'full' ||
+ $old_line_num <= $base_old_line_num + 5 ||
+ $line_num - 5 < $old_line_num);
+ }
+
+ print "" if (!$anchor_printed);
+ print '';
+ $anchor_num++;
+}
+
+sub print_cell {
+ local ($line, $color) = @_;
+ local ($i, $j, $k, $n);
+ local ($c, $newline);
+
+ if ($color eq $stable_bg_color) {
+ print "$font_tag";
+ } else {
+ print " $font_tag";
+ }
+
+ while (($c = substr($line, -1)) && ($c eq "\n" || $c eq "\r")) {
+ chop $line;
+ }
+ $n = length($line);
+ $newline = '';
+ for ($i = $j = 0; $i < $n; $i++) {
+ $c = substr($line, $i, 1);
+ if ($c eq "\t") {
+ for ($k = &next_tab_stop($j); $j < $k; $j++) {
+ $newline .= ' ';
+ }
+ } else {
+ $newline .= $c;
+ $j++;
+ }
+ }
+ $newline =~ s/&/&/g;
+ $newline =~ s/</g;
+ $newline =~ s/>/>/g;
+ if (length($newline) <= 80) {
+ $newline = sprintf("%-80.80s", $newline);
+ } else {
+ $newline =~ s/([^\n\r]{80})([^\n\r]*)/$1\n$2/g;
+ }
+ print $newline;
+}
+
+sub print_row {
+ local ($line1, $color1, $line2, $color2) = @_;
+ print "\n ";
+ &print_cell($line1, $color1);
+ &print_cell($line2, $color2);
+}
+
+sub print_bottom {
+ print <<__BOTTOM__;
+
+
+
+
+
+ Mail feedback and feature requests to slamm.
+
+
+