#! /usr/local/bin/perl -w

# Offline diff creator

require 5.004;
use Time::Local;
use strict;

Main();

sub Main
{
	if ($#ARGV >= 0)
	{
		usage ();
	}

	open(CVSADM, "cvsu --ignore --types AMRO |") ||
		error ("Cannot read output of cvsu: $!");

	while (<CVSADM>) {
		chomp;
		if ($_ !~ m{^([AMRO]) (.*)$}) {
			error ("Unrecognized output from cvsu");
		}
		my $type = $1;
		my $file = $2;
		if ($type eq "A") {
			handle_added ($file);
		}
		elsif ($type eq "R") {
			handle_removed ($file);
		}
		else {
			handle_modified ($file);
		}
	}
}

# Handle added files
sub handle_added
{
	my $file = shift(@_);
	open(DIFFOUT, "diff -u -L /dev/null -L $file /dev/null $file |") ||
		error ("Cannot read output of diff: $!");
	diff_print ("Index: $file");
	while (<DIFFOUT>) {
		diff_print ($_);
	}
}

# Handle removed files
sub handle_removed
{
	my $file = shift(@_);
	# FIXME: scan for backup copies, as in handle_modified()
	# Any ideas about how to make `patch' erase that file?
	diff_print ("File $file should be removed!\n");
}

# Handle modified files
sub handle_modified
{
	my $file = shift(@_);
	# split into directory and file name
	$file =~ m{^((.*/)?)([^/]+)};
	my $short_file = $3;
	my $dir = $1;
	my %months = (
		"Jan" => 0,
		"Feb" => 1,
		"Mar" => 2,
		"Apr" => 3,
		"May" => 4,
		"Jun" => 5,
		"Jul" => 6,
		"Aug" => 7,
		"Sep" => 8,
		"Oct" => 9,
		"Nov" => 10,
		"Dec" => 11
	);

	# Lookup the original timestamp in CVS/Entries
	open (ENTRIES, "< ${dir}CVS/Entries")
		|| error ("couldn't open ${dir}CVS/Entries: $!");
	my $date_str;
	while (<ENTRIES>) {
		if ( m{^/$short_file/[^/]*/([^/]+)/} ) {
			$date_str = $1;
			last;
		}
	}
	unless (defined $date_str) {
		error ("$file is not listed in ${dir}CVS/Entries");
	}
	close (ENTRIES);

	unless ($date_str =~ m{^(...) (...) (..) (..):(..):(..) (....)$}) {
		error ("Invalid timestamp for $file: $date_str");
	}

	my $basetime = timegm($6, $5, $4, $3, $months{$2}, $7 - 1900);

	# Scan the directory for similar files
	my $backup_file;
	opendir (DIR, $dir eq "" ? "." : $dir) ||
		error ("Cannot open directory $dir: $!");
	foreach (readdir (DIR)) {
		m{$short_file} || next;;
		my $candidate = $dir . $_;
		stat ($candidate) || next;
		if ($basetime == (stat _) [9]) {
			$backup_file = $candidate;
			last;
		}
	}
	closedir (DIR);

	unless (defined $backup_file) {
		warning ("Backup file for $file not found");
		return;
	}

	my $diff_opts = "-u";
	if ($short_file eq "ChangeLog") {
		$diff_opts = "-u1";
	}

	open(DIFFOUT,
	     "diff $diff_opts -L $file -L $file $backup_file $file |") ||
		error ("Cannot read output of diff: $!");
	diff_print ("Index: $file");
	while (<DIFFOUT>) {
		diff_print ($_);
	}
}

# print message and make sure that it ends with a UNIX-style newline
sub diff_print
{
	my $msg = shift(@_);
	chomp $msg;
	print $msg . "\012";
}

# print a warning message
# newline is added at the end
sub warning
{
	print STDERR "cvsdiff: WARNING: " . shift(@_) . "\n";
}

# print message and exit (like "die", but without raising an exception)
# newline is added at the end
sub error
{
	print STDERR shift(@_) . "\n";
	exit 1;
}

# print usage information and exit
sub usage
{
	print "Usage: cvsdiff\n";
	exit 1;
}

