#!/usr/bin/perl # # cvs2mysql # # logs CVS log messages to mysql database # # $Id$ # use strict; use Getopt::Long qw(:config no_ignore_case bundling); use Date::Parse; use DBI; our $version = '$Revision$'; our $dbh; our $rlogbin = 'rlog -zLT'; my $needhelp; my $user; my $repo; my $path; my $files; my @file_list; my $import; my $cvsroot; my $progress_counter_linecount = 1000; my $log; my $time; my $db_host = 'localhost'; my $db_name = 'cvs2mysql'; my $db_user = 'root'; my $db_pass = ''; my $options = GetOptions( 'h|?|help' => \$needhelp, 'import=s' => \$import, 'u|user=s' => \$user, 'f|files=s' => \$files, 'h|hostname=s' => \$db_host, 'd|database=s' => \$db_name, 'U|dbuser=s' => \$db_user, 'p|password=s' => \$db_pass,); Usage() if (!$options || $needhelp); ## verify options # check parameters # if no import and user or files is missing # or if all three are missing if ((!$import && (!$user || !$files)) || (!$import && !$user && !$files)) { Usage(); } # verify correct parameters # verify import is set properly if ($import && !(-r $import)) { Usage("Could not read file '$import'"); } # end if unreadable import file DBConnect($db_host, $db_name, $db_user, $db_pass); # import the history file if ($import) { # find cvs root if (!$cvsroot) { $cvsroot = $1 if ($import =~ m|^(.*)/CVSROOT|); } # end if no cvsroot open(HISTORY, "<$import"); # find total number of lines my $total_lines = 0; while (my $line = ) { $total_lines++; } # end while lines seek(HISTORY, 0, 0); # loop through history and combine transactions my %transaction; my $progress_counter = 0; while (my $line = ) { $progress_counter++; if ($progress_counter % $progress_counter_linecount == 0) { print "$progress_counter / $total_lines\n"; } # end if time to output progress counter # O4683df5d|silfreed|~/tmp/*0|CVSROOT||CVSROOT # M4683df85|silfreed|~/tmp/*0|CVSROOT|1.2|loginfo chomp($line); my ($transaction_id, $user, $src_path, $repo_path, $revision, $file) = split(/\|/, $line); # pull transaction type and date from transaction id # see: cvs-1.11.22/src/history.c my ($transaction_type, $transaction_date) = unpack('AA8', $transaction_id); $transaction_date = hex $transaction_date; # we only care about Modified or Added files next if ($transaction_type !~ /^[MA]$/); # we only care about transactions that might have an RCS file next if (!$file || !$revision); # if we've already logged about this file, skip it next if fileExistsInDatabase($repo_path, $file, $revision); if (!$transaction{'id'} || $transaction_id ne $transaction{'id'}) { # log the previous transaction logHistoryTransaction(\%transaction) if $transaction{'id'}; # replace the transaction with new information %transaction = {}; $transaction{'id'} = $transaction_id; $transaction{'date'} = $transaction_date; $transaction{'user'} = $user; $transaction{'repo'} = $1 if ($repo_path =~ /^(\w+)/); $transaction{'path'} = $repo_path; $transaction{'file_list'} = (); push(@{$transaction{'file_list'}}, $file . ',' . $revision); $transaction{'log'} = getRCSLog( $cvsroot . '/' . $repo_path . '/' .$file, $revision); } # end if different transaction else { # add files to current transaction push(@{$transaction{'file_list'}}, $file . ',' . $revision) if ($file && $revision); } # end if same transaction } # end while history # log the final transaction logHistoryTransaction(\%transaction) if $transaction{'id'}; } # end if import history # insert the cvs log message into the database else { $time = time(); $log = captureLog(); ($path, @file_list) = split(/\s+/, $files); $files = join(' ', @file_list); $repo = $1 if ($path =~ /^(\w+)/); my $cvslog_id = logToDatabase($repo, $user, $time, $log); logFilesToDatabase($cvslog_id, $path, @file_list); } # end if not import ### # SUBROUTINES # sub Usage() { my $message = shift(@_); if ($message) { chomp($message); print "$message\n\n"; } # end if message print < (-d|--database) (-U|--dbuser) (-p|--password) $0 --import= $0 (-u|--username) (-f|--files) END exit(0); } # end sub Usage sub captureLog() { # read in log from stdin while (my $line = ) { $log .= $line; } my $old_paragraph_separator = $/; $/ = ""; chomp($log); $/ = $old_paragraph_separator; $log =~ s/.*Log Message:\s*(.*)$/$1/sm; return $log; } # end sub captureLog sub DBConnect($$$$) { my ($hostname, $database, $username, $password) = @_; $dbh = DBI->connect("DBI:mysql:database=$database;host=$hostname", $username, $password); if (!$dbh) { print "ERROR: couldn't connect to database\n"; exit(); } # end if we didn't connect } # end sub DBConnect sub fileExistsInDatabase($$$) { my ($path, $filename, $revision) = @_; if ($path && $filename && $revision) { my @row = $dbh->selectrow_array('SELECT `cvslog_id` FROM `cvsfiles` ' . 'WHERE `path` = ' . $dbh->quote($path) . ' ' . 'AND `filename` = ' . $dbh->quote($filename) . ' ' . 'AND `revision` = ' . $dbh->quote($revision) . ' '); return $row[0] if ($row[0]); } # end if filename and revision return 0; } # end sub fileExistsInDatabase sub getRCSLog($$) { my $log; my ($filename, $revision) = @_; if ($filename && $revision) { my $fulllog = `$rlogbin -r$revision '$filename' 2> /dev/null`; if ($fulllog =~ /date:.*?\n(.*?)={60,}/sm) { $log = $1; my $old_paragraph_separator = $/; $/ = ""; chomp($log); $/ = $old_paragraph_separator; } # end if log matches } # end if filename and revision return $log; } # end sub getRCSLog sub logHistoryTransaction(%) { my %transaction = %{$_[0]}; # log transaction my $cvslog_id = logToDatabase($transaction{'repo'}, $transaction{'user'}, $transaction{'date'}, $transaction{'log'}); logFilesToDatabase($cvslog_id, $transaction{'path'}, @{$transaction{'file_list'}}); } # end sub logHistoryTransaction sub logToDatabase($$$$$) { my ($repo, $user, $time, $log) = @_; my $return = $dbh->do('INSERT INTO `cvslog` SET ' . '`repo` = ' . $dbh->quote($repo) . ', ' . '`user` = ' . $dbh->quote($user) . ', ' . '`committed` = FROM_UNIXTIME(' . $time . '), ' . '`log` = ' . $dbh->quote($log) . ''); my @row = $dbh->selectrow_array('SELECT LAST_INSERT_ID()'); return $row[0]; } # end sub logToDatabase() sub logFilesToDatabase($$@) { my ($id, $path, @files) = @_; foreach my $file (@files) { my ($filename, $revision) = split(/,/, $file); if ($filename && $revision) { next if fileExistsInDatabase($path, $filename, $revision); my $return = $dbh->do('INSERT INTO `cvsfiles` SET ' . '`cvslog_id` = ' . $id . ', ' . '`path` = ' . $dbh->quote($path) . ', ' . '`filename` = ' . $dbh->quote($filename) . ', ' . '`revision` = ' . $dbh->quote($revision) . ' '); } # end if filename and revision } # end foreach file } # end sub logFilesToDatabase()