# -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Netscape Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/NPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bonsai CVS tool. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): require 'get_line.pl'; use strict; use DBI; use POSIX qw(strftime mktime); use Time::Local; # Assume that treedata.pl & tbglobals.pl are already loaded my $db = undef; my $NOT_LOCAL = 1; my $IS_LOCAL = 2; #global variables $::lines_added = 0; $::lines_removed = 0; $::modules = {}; if (defined($::CVS_ROOT) && "$::CVS_ROOT" ne "") { my $CVS_MODULES="$::CVS_ROOT/CVSROOT/modules"; open( MOD, "<$CVS_MODULES") || die "can't open ${CVS_MODULES}"; parse_modules(); close( MOD ); } # # Actually do the query # sub query_checkins_bonsai { my ($tree, %mod_map) = @_; my ($ci,$result,$lastlog,$rev,$begin_tag,$end_tag); my $have_mod_map; my @bind_values; my $currentquery; $::query_module = 'all' unless defined $::query_module; 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 my $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; } $begin_tag = ""; $end_tag = ""; if (defined($::query_begin_tag) && $::query_begin_tag ne '') { $begin_tag = load_tag($::query_begin_tag); } if (defined($::query_end_tag) && $::query_end_tag ne '') { $end_tag = load_tag($::query_end_tag); } $result = []; ConnectToDatabase($tree); my $qstring = "SELECT type, UNIX_TIMESTAMP(ci_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 = ?"; push(@bind_values, $::CVS_ROOT); } if ($::query_date_min) { $qstring .= " AND ci_when >= ?"; push(@bind_values, formatSqlTime($::query_date_min)); } if ($::query_date_max) { $qstring .= " AND ci_when <= ?"; push(@bind_values, formatSqlTime($::query_date_max)); } if ($::query_branch_head) { $qstring .= " AND branches.branch = ''"; } elsif ($::query_branch ne '') { if ($::query_branchtype eq 'regexp') { $qstring .= " AND branches.branch REGEXP ?"; push(@bind_values, $::query_branch); } elsif ($::query_branchtype eq 'notregexp') { if ($::query_branch eq 'HEAD') { $qstring .= " AND branches.branch != ''"; } else { $qstring .= " and not (branches.branch REGEXP ?)"; push(@bind_values, $::query_branch); } } else { $qstring .= " AND (branches.branch = ? OR branches.branch = ?)"; push(@bind_values, $::query_branch); push(@bind_values, "T$::query_branch"); } } if (0 < @::query_dirs) { my @list; foreach my $i (@::query_dirs) { if ($::query_dirtype eq 'recurse') { push(@list, "dirs.dir LIKE ?"); push(@bind_values, "$i%"); } else { push(@list, "dirs.dir = ?"); push(@bind_values, "$i"); } } $qstring .= "AND (" . join(" OR ", @list) . ")"; } if (defined $::query_file && $::query_file ne '') { $::query_filetype ||= "exact"; if ($::query_filetype eq 'regexp') { $qstring .= " AND files.file REGEXP ?"; } elsif ($::query_filetype eq 'notregexp') { $qstring .= " AND NOT (files.file REGEXP ?)"; } else { $qstring .= " AND files.file = ?"; } push(@bind_values, $::query_file); } if (defined $::query_who && $::query_who ne '') { $::query_whotype ||= "exact"; if ($::query_whotype eq 'regexp') { $qstring .= " AND people.who REGEXP ?"; } elsif ($::query_whotype eq 'notregexp') { $qstring .= " AND NOT (people.who REGEXP ?)"; } else { $qstring .= " AND people.who = ?"; } push(@bind_values, $::query_who); } if (defined($::query_logexpr) && $::query_logexpr ne '') { $qstring .= " AND descs.description regexp ?"; push(@bind_values, $::query_logexpr); } if ($::query_debug) { print "
 Query: " . value_encode($qstring) . "\n";
        print "With values:\n";
        foreach my $v (@bind_values) {
            print "\t" . value_encode($v) . "\n";
        }
        print "\nTreeID is $::TreeID\n";
        if ($have_mod_map) {
            print "Dump of module map:\n";
            foreach my $k (sort(keys %mod_map)) {
                print value_quote("$k => $mod_map{$k}") . "\n";
            }
            print "\n\nDump of parsed module file:\n";
            foreach my $k(sort(keys %$::modules)) {
                print value_quote("$k => " .
                                  join(",", @{$::modules->{$k}})) . "\n";
            }
        }
        print "
\n"; } SendSQL(\$currentquery, $qstring, @bind_values); $lastlog = 0; my @row; #print "
";
    while (@row = FetchSQLData(\$currentquery)) {
        $ci = [];
        for (my $i=0 ; $i<=$::CI_LOG ; $i++) {
            $ci->[$i] = $row[$i];
#print "$row[$i] ";
        }
#print "
"; my $key = "$ci->[$::CI_DIR]/$ci->[$::CI_FILE]"; if (IsHidden($tree, "$ci->[$::CI_REPOSITORY]/$key")) { next; } next if ($key =~ m@^CVSROOT/@); if( $have_mod_map && !&in_module(\%mod_map, $ci->[$::CI_DIR], $ci->[$::CI_FILE] ) ){ next; } if( $begin_tag) { $rev = $begin_tag->{$key}; print "
$key begintag is $rev
\n"; if ($rev == "" || rev_is_after($ci->[$::CI_REV], $rev)) { next; } } if( $end_tag) { $rev = $end_tag->{$key}; print "
$key endtag is $rev
\n"; if ($rev == "" || rev_is_after($rev, $ci->[$::CI_REV])) { next; } } if (defined($::query_logexpr) && $::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],"; } DisconnectFromDatabase(); return $result; } # ViewVC query function sub query_checkins_viewvc($) { my ($tree) = @_; my @bind_values; my $currentquery; ConnectToDatabase($tree); my $qstring = "SELECT type, UNIX_TIMESTAMP(ci_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 (defined($::query_module) && $::query_module ne 'allrepositories') { $qstring .= " AND repositories.repository = ?"; push(@bind_values, $::query_module); } if (defined($::query_date_min) && $::query_date_min) { $qstring .= " AND ci_when >= ?"; push(@bind_values, formatSqlTime($::query_date_min)); } if (defined($::query_date_max) && $::query_date_max) { $qstring .= " AND ci_when <= ?"; push(@bind_values, formatSqlTime($::query_date_max)); } # print "Query: $qstring\n"; # print "values: @bind_values\n"; SendSQL(\$currentquery, $qstring, @bind_values); my $lastlog = 0; my (@row, $ci, $rev, $result); #print "
";
    while (@row = FetchSQLData(\$currentquery)) {
        $ci = [];
        for (my $i=0 ; $i<=$::CI_LOG ; $i++) {
            if ($i == $::CI_DATE) {
                $ci->[$i] = GMTtoLocaltime($row[$i]);
            } else {
                $ci->[$i] = $row[$i];
            }
        }
#print "
\n"; my $key = "$ci->[$::CI_DIR]/$ci->[$::CI_FILE]"; # if (IsHidden("$ci->[$::CI_REPOSITORY]/$key")) { # next; # } next if ($key =~ m@^CVSROOT/@); if (defined($::query_logexpr) && $::query_logexpr ne '' && !($ci->[$::CI_LOG] =~ /$::query_logexpr/i) ){ next; } push( @$result, $ci ); } DisconnectFromDatabase(); return $result; } # # Common helper functions # sub ConnectToDatabase($) { my ($tree) = @_; my $query = $::global_treedata->{$tree}->{query}; my $query_type = $::QueryInfo{$query}{type}; if (!defined $db) { my ($dsn, $dbdriver, $dbname, $dbhost, $dbport, $dbuser, $dbpasswd); if ($query ne "" && $query_type ne "") { $dbdriver = $::QueryInfo{$query}{dbdriver}; $dbname = $::QueryInfo{$query}{dbname}; $dbhost = $::QueryInfo{$query}{dbhost}; $dbport = $::QueryInfo{$query}{dbport}; $dbuser = $::QueryInfo{$query}{dbuser}; $dbpasswd = $::QueryInfo{$query}{dbpasswd}; } else { # Error die("Cannot determine query system!"); return; } $dsn = "DBI:${dbdriver}:database=${dbname};"; $dsn .= "host=${dbhost};" if (defined($dbhost) && "$dbhost" ne ""); $dsn .= "port=${dbport};" if (defined($dbport) && "$dbport" ne ""); $db = DBI->connect($dsn, $dbuser, $dbpasswd) || die "Can't connect to database server."; } } sub DisconnectFromDatabase { if (defined $db) { $db->disconnect(); undef $db; } } sub SendSQL { my ($query_ref, $str, @bind_values) = (@_); my $status = 0; $$query_ref = $db->prepare($str) || $status++; if ($status) { print STDERR "SendSQL prepare error: '$str' with values ("; foreach my $v (@bind_values) { print STDERR "'" . &shell_escape($v) . "', "; } print STDERR ") :: " . $db->errstr . "\n"; die "Cannot prepare SQL query. Please contact system administrator.\n"; } $$query_ref->execute(@bind_values) || $status++; if ($status) { print STDERR "SendSQL execute error: '$str' with values ("; foreach my $v (@bind_values) { print STDERR "'" . &shell_escape($v) . "', "; } print STDERR ") :: " . $$query_ref->errstr . "\n"; die "Cannot execute SQL query. Please contact system administrator.\n"; } } sub FetchSQLData($) { my ($query_ref) = (@_); return $$query_ref->fetchrow_array(); } sub FetchOneColumn($) { my ($query_ref) = @_; my @row = &FetchSQLData($query_ref); return $row[0]; } sub formatSqlTime { my ($date) = @_; my $time = strftime("%Y/%m/%d %T", gmtime($date)); return $time; } # Use this function to workaround the fact that perl # assumes that seconds since epoch are always in localtime sub GMTtoLocaltime() { my ($time) = @_; my @timeData = localtime(time); my $diff = mktime(gmtime($time)) - mktime(localtime($time)); # Account for DST $diff -= 3600 if ($timeData[8]); return $time - $diff; } # # Bonsai helper functions # sub load_tag { my ($tagname) = @_; 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"; my $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) { my $file = shift @line; $file = "$dir/$file"; my $version = shift @line; $result->{$file} = $version; print "
Added ($file,$version) for tag $tagname
\n"; } } return $result; } # Returns true if the given directory or filename is one of the hidden ones # that we don't want to show users. sub IsHidden { my ($tree, $name) = (@_); my $query = $::global_treedata->{$tree}->{query}; my $bonsai_dir = $::QueryInfo{$query}{directory}; $name =~ s:///*:/:g; # Remove any multiple slashes. if (!defined @::HideList) { if (open(HIDE, "<${bonsai_dir}/data/hidelist")) { while () { chop; $_ = trim($_); s/\#.*//; next if (/^$/); push(@::HideList, $_); } close HIDE; } else { @::HideList = (); } } foreach my $item (@::HideList) { if ($name =~ m/$item/) { return 1; } } return 0; } 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 in_module { my ($mod_map, $dirname, $filename ) = @_; my ( @path ); my ( $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 (!exists($$mod_map{$dirname}) || $$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 { my($name) = @_; my(%mod_map); &build_map( $name, \%mod_map ); return %mod_map; } sub parse_modules { my $l; while( $l = &get_line ){ my ($mod_name, $flag, @params) = split(/[ \t]+/,$l); if ( $#params eq -1 ) { @params = $flag; $flag = ""; } elsif( $flag eq '-d' ){ my $dummy; ($mod_name, $dummy, $dummy, @params) = split(/[ \t]+/,$l); } elsif( $flag ne '-a' ){ next; } $::modules->{$mod_name} = [@params]; } } sub build_map { my ($name,$mod_map) = @_; my ($bFound, $local); $local = $NOT_LOCAL; $bFound = 0; for my $i ( @{$::modules->{$name}} ){ $bFound = 1; if( $i eq '-l' ){ $local = $IS_LOCAL; } elsif($i eq $name || !build_map($i, $mod_map )){ $mod_map->{$i} = $local; } } return $bFound; } 1;