#!/usr/bin/perl

# Copyright (c) Scott Wheeler, 2003
#
# This program is free software; you can redistribute it and/or modify  *
# it under the terms of the GNU General Public License as published by  *
# the Free Software Foundation; either version 2 of the License, or     *
# (at your option) any later version.  

use strict;
use DBI;

our $debug = 0;
our @lines = <>;

################################################################################
# Process the headers for a file and return a list of the branch, modules and
# directory in that module.
################################################################################

sub processHeaders {
  my ($line, $branch, $module, $directory, $flags);

  my $invalid = 1;

  do {
    $line = shift(@lines);

    # mark this message as a valid CVS message
    if($line =~ /^X-Commit-Directories:/) {
      $invalid = 0;
    }

    if($line =~ /^Subject:/) {
      my $subject = $line;
      chomp($subject);

      $subject =~ s/^.+?\s+(.*)/$1/; # get everything after the first space
      if($subject =~ /:\s+/) {
	($branch, $subject) = split(/:\s+/, $subject, 2);
      }
      else {
	$branch = "HEAD";
      }
      if($subject =~ /\s+[\(\[]*.+[\)\]]/) {
	$flags = $subject;
	$flags =~ s/.*\s+[\(\[]*(.+)[\)\]].*/$1/;
      }
      $subject =~ s/\s.*//;
      ($module, $directory) = split('/', $subject, 2);
    }
  } while($line && $line ne "\n");

  return ($invalid, $branch, $module, $directory, $flags);
}

sub splitFileName {
  my ($file) = @_;
  my $directory;

  if($file =~ /\//) {
    $directory = $file;
    $directory =~ s/\/[^\/]*$//;
    $file =~ s/^.*\///;
  }
  return ($directory, $file);
}

################################################################################
# Process the body of the mail and insert the changes into the database.
################################################################################

sub processBody {
  my $user = shift(@lines);
  chomp($user);
  $user =~ s/CVS commit by (\w+)\:.*/$1/;

  my $line = shift(@lines);

  while($line =~ /^\s*$/ && $line) {
    $line = shift(@lines);
  }

  my $log;

  while($line !~ /^  [MRA]/ && $line !~ /^[NI] \w+\// && $line) {
    $log .= $line;
    $line = shift(@lines);
  }

  # strip the whitespace from the end of the log text
  $log =~ s/\s*$//;

  my @changes;

  do {
    # if this line matches the pattern for a CVS change -- a modification,
    # removal or addition...

    if($line =~ /^  [MRA]/ || $line =~ /^[NI] \w+\//) {
      my %change;

      # strip leading and trailing spaces
      $line =~ s/^\s+(.+)\s+$/$1/;

      my @fields = split(/\s+/, $line);

      # modified file
      if($fields[0] eq "M") {
	$change{action} = "modify";
	$change{linesAdded} =  $fields[1];
	$change{linesRemoved} = $fields[2];
	
	# remove non-numeric characters
	$change{linesAdded} =~ s/[^0-9]//g;
	$change{linesRemoved} =~ s/[^0-9]//g;

	($change{directory}, $change{file}) = splitFileName($fields[3]);
	$change{revision} = $fields[4];
      }
      elsif($fields[0] eq "R") {
	$change{action} = "remove";
	($change{directory}, $change{file}) = splitFileName($fields[1]);
	$change{revision} = $fields[2];
      }
      elsif($fields[0] eq "A") {
	$change{action} = "add";
	($change{directory}, $change{file}) = splitFileName($fields[1]);
	$change{revision} = $fields[2];
      }
      elsif($fields[0] eq "I") {
	$change{action} = "import";
	($change{directory}, $change{file}) = splitFileName($fields[1]);
      }
      elsif($fields[0] eq "N") {
	$change{action} = "new";
	($change{directory}, $change{file}) = splitFileName($fields[1]);
      }
      # put a reference to this change in the change list
      push @changes, \%change;
    }
  } while($line = shift(@lines));

  return ($user, $log, \@changes);
}

################################################################################
# Insert a commit into the database and return the commit id.
################################################################################


sub insertCommit {
  my ($dbh, $branch, $user, $module, $directory, $log, $flags) = @_;

  foreach my $i ($branch, $user, $module, $directory, $log, $flags) {
    if(length($i) > 0) {
      $i =~ s/\"/\\\"/g;
      $i = "\"$i\"";
    }
    else {
      $i = "NULL";
    }
  }

  my $query;
  $query .= "INSERT INTO commits (branch, user, module, directory, log, flags) ";
  $query .= "VALUES( $branch, $user, $module, $directory, $log, $flags )";

  my $rv = $dbh->do($query);
  # print "$query\n";

  my $sth = $dbh->prepare("SELECT MAX(id) FROM commits");
  my $ra = $sth->execute;
  my ($id) = $sth->fetchrow_array;
  $ra = $sth->finish;

  return $id;
}

################################################################################
# Insert a change into the database (a change is the part of a commit specific
# to a single file).
################################################################################

sub insertChange {
  my ($dbh, $id, $changeRef) = @_;

  foreach my $i ("action", "directory", "file", "revision") {
    if(length($changeRef->{$i}) > 0) {
      $changeRef->{$i} =~ s/\"/\\\"/g;
      $changeRef->{$i} = "\"$changeRef->{$i}\"";
    }
    else {
      $changeRef->{$i} = "NULL";
    }
  }

  foreach my $key ("linesAdded", "linesRemoved") {
    if(!$changeRef->{$key}) {
      $changeRef->{$key} = "NULL";
    }
  }

  my $query;
  $query .= "INSERT INTO changes VALUES( $id, $changeRef->{action}, $changeRef->{linesAdded}, ";
  $query .= "$changeRef->{linesRemoved}, $changeRef->{directory}, $changeRef->{file}, ";
  $query .= "$changeRef->{revision} )";

  # print "$query\n";
  my $rv = $dbh->do($query);
}

################################################################################
# "main"
################################################################################

my @output = @lines;

my ($invalid, $branch, $module, $directory, $flags) = processHeaders();

# bail out if this doesn't seem to be a valid CVS mail

if($invalid == 1) {
  print @output;
  exit;
}

my ($user, $log, $changesRef) = processBody();

if($debug == 1) {
  print "BRANCH:\t$branch\n";
  print "MODULE:\t$module\n";
  print "DIR:\t$directory\n";
  print "USER:\t$user\n";
  print "FLAGS:\t$flags\n";
  print "LOG:\t$log\n";

  print "\n";

  foreach my $change (@$changesRef) {
    foreach my $key (keys(%$changesRef)) {
      print "\t$key - $change->{$key}\n";
    }
    print "\n";
  }
}
else {
  print @output;
}

my $dbh = DBI->connect("DBI:mysql:kdecvs", "root");
# my $rv = $dbh->do("LOCK TABLES commits WRITE");
my $id = insertCommit($dbh, $branch, $user, $module, $directory, $log, $flags);
foreach my $change (@$changesRef) {
  insertChange($dbh, $id, $change);
}
# my $rv = $dbh->do("UNLOCK TABLES");
$dbh->disconnect;

