diff -u -p -r1.55 cvsreport
--- cvsreport	15 Feb 2007 13:53:19 -0000	1.55
+++ cvsreport	15 Feb 2007 13:58:24 -0000
@@ -581,6 +581,153 @@ sub get_file_info {
     }
 }
 
+sub file_exists_local {
+    my $directory = shift;
+    my $file = shift;
+    my $rev = shift;
+
+    my $file_path = $cvsroot . "/" . $directory . "/" . $file . ",v";
+    my $attic_path = $cvsroot . "/" . $directory . "/Attic/" . $file . ",v";
+
+    if (-f $file_path || -f $attic_path) {
+	return 1;
+    } else {
+	return 0;
+    }
+}
+
+sub file_exists_remote {
+    my $directory = shift;
+    my $file = shift;
+    my $rev = shift;
+
+    my $command = "cvs -d \"$cvsroot\" rlog -r$rev \"$directory/$file\" &> /dev/null";
+
+    if (system($command) == 0) {
+	return 1;
+    } else {
+	return 0;
+    }
+}
+
+sub file_exists {
+    if (is_local $cvsroot) {
+	return file_exists_local @_;
+    } else {
+	return file_exists_remote @_;
+    }
+}
+
+sub find_columns {
+    my $command = shift; 
+    open(HISTORY, "$command 2> /dev/null |") or error "running '$command': $!", 3;
+    my $lines = 0;
+    my %space_count;
+
+    # Find the position of all spaces in the history.
+    while (<HISTORY>) {
+	chomp;
+	my $line = $_;
+	my $pos = -1;
+	
+	$lines++;
+
+	while (($pos = index($line, ' ', $pos)) > -1) {
+	    $pos++;
+	    if (defined $space_count{$pos}) {
+		$space_count{$pos}++;
+	    } else {
+		$space_count{$pos} = 1;
+	    }
+	}
+    }
+
+    close(HISTORY);
+
+    # Now we need to locate the positions where we should separate the strings.
+    my @columns;
+    my $space;
+
+    foreach $space (sort {$a <=> $b} keys %space_count) {
+	if ($space_count{$space} == $lines) {
+	    push (@columns, scalar $space);
+	}
+    }
+
+    my $col_count = scalar @columns;
+
+    if ($col_count > 9) {
+	# There are more columns than there should be. 
+	# This means that our split did not work as it should.
+	#
+	# We know that the first 5 columns are correct, and that
+        # the last two are also correct.
+
+	open(HISTORY, "$command 2> /dev/null |") or error "running '$command': $!", 3;
+	
+	while (<HISTORY>) {
+	    chomp;
+	    my $line = $_;
+
+	    my $rev = trim(substr($line, $columns[4], $columns[5] - $columns[4]));
+
+	    my $cur_split = 6;
+	    while ($cur_split < $col_count - 2) {
+
+		my $file = trim(substr($line, $columns[5], $columns[$cur_split] - $columns[5]));
+		my $dir = trim(substr($line, $columns[$cur_split], $columns[$col_count - 2] - $columns[$cur_split]));
+		if (!file_exists($dir, $file, $rev)) {
+		    splice(@columns, $cur_split, 1);
+
+		    $col_count = scalar @columns;
+
+		    # if we only have 9 columns left, we escape the loop.
+		    if ($col_count == 9) {
+			last;
+		    }
+
+		    # Since we have just removed a column, we skip incrementing the cur_split.
+		    next;
+		}
+		$cur_split++;
+	    }
+	    if ($col_count == 9) { 
+		last;
+	    }
+	}
+
+	close(HISTORY);
+    }
+
+    return @columns;
+}
+
+sub trim($)
+{
+        my $string = shift;
+        $string =~ s/^\s+//;
+        $string =~ s/\s+$//;
+        return $string;
+}
+
+sub extract_line {
+    my $line = shift;
+    my @columns = @_;
+    my @res = ();
+
+    my $curpos = 0;
+    my $cut;
+
+    foreach $cut (@columns) {
+	push (@res, trim(substr($line, $curpos, $cut - $curpos - 1)));
+	$curpos = $cut;
+    }
+
+    push (@res, trim(substr($line, $curpos)));
+
+    return @res;
+}
+
 # Parse history items and gathers them into csets.
 #
 # This is the core of cvsreport, where the magic actually happens. It calls 'cvs history',
@@ -600,6 +747,8 @@ sub parse_history {
     debug "parse_history():";
     debug "  command: $command";
 
+    my @columns = find_columns($command);
+
     # We silence stderr because we will break the pipe on purpose if we have enough parsing.
     open(HISTORY, "$command 2>/dev/null |") or error "running '$command': $!", 3;
 
@@ -627,8 +776,9 @@ sub parse_history {
 
         # This _will_ fail if module or file names have spaces, but there's nothing we can do.
         # Actually we could parse directly $CVSROOT/CVSROOT/history but that would break remote usage.
-        my @tokens = split / +/;
-        my ($action, $date_ymd, $date_hm, $date_zone, $xuser, $revision, $file, $path, undef, $method) = @tokens;
+        my ($action, $date_ymd, $date_hm, $date_zone, $xuser, $revision, $file, $path, undef, $method) 
+	    = extract_line($_, @columns);
+
         my $xdate = parse_utc_date "$date_ymd $date_hm $date_zone";
         my $fullpath = "$path/$file";
 
