#! /usr/bin/perl -w

# This script simulates some cvs commands even for readonly or diconnected
# CVS repositories

require 5.004;
use Getopt::Long;
use strict;

use vars qw($force_mode $entries_tmp);

Main();

sub Main
{
	$force_mode = 0;	# Forced operation
	my $want_help = 0;	# Print help and exit
	my $want_ver = 0;	# Print version and exit

	my %options = (
		"force"	   => \$force_mode,
		"help"     => \$want_help,
		"version"  => \$want_ver
	);

	GetOptions(%options);

	usage() if $want_help;
	version() if $want_ver;

	my $command = shift (@ARGV);

	if ( $want_ver || !$command || ($command !~ /(add|remove)/) ) {
		usage();
	}

	if ( $#ARGV < 0 ) {
		error ("No files specified\n");
	}

	foreach (@ARGV) {
		process_file ($_, $command);
	}

	cleanup();
}

sub process_file
{
	my $short_file;
	my $entries;
	my $file_exists = 0;
	my $file_listed = 0;

	my $file = shift (@_);
	my $command = shift (@_);

	my $cmd_add = ($command eq 'add');
	my $cmd_remove = ($command eq 'remove');

	if (-e $file) {
		unless (-f $file) {
			error ("File $file is not a plain file\n");
		}
		$file_exists = 1;
	}

	if ( $cmd_add && ! $file_exists && ! $force_mode ) {
		error ("File $file doesn't exist\n");
	} elsif ( $cmd_remove && $file_exists && ! $force_mode ) {
		error ("Won't remove existing file $file\n");
	}

	$entries = $file;
	$entries =~ s{^(([^ ]+/)?)([^/ ]+)$}{${1}CVS/Entries};
	$short_file = $3;

	unless ($entries) {
		error("Wrong filename $file\n");
	}

	$entries_tmp = $entries . ".tmp";

	open(NEW_ENTRIES, "> $entries_tmp") ||
		error("Cannot open $entries_tmp for writing\n");

	open(ENTRIES, "< $entries") ||
		error("Cannot open $entries for reading\n");

	while(<ENTRIES>) {
		if ( m{^(/([^/]+)/)([^/])(.*$)} && $2 eq $short_file ) {
			$file_listed = 1;
			last if $cmd_add;
			if ( $3 eq '-' ) {
				error("File $file is already removed\n");
			} else {
				print NEW_ENTRIES "$1-$3$4\n";
			}
		} else {
			print NEW_ENTRIES $_;
		}
	}

	if ( $cmd_add && $file_listed ) {
		error("File $file is already listed in $entries\n");
	}

	if ( $cmd_remove && ! $file_listed ) {
		error("File $file is not listed in $entries\n");
	}

	if ( $cmd_add ) {
		print NEW_ENTRIES "/$short_file/0/dummy timestamp//\n";
	}

	close (ENTRIES);
	close (NEW_ENTRIES);

	rename $entries_tmp, $entries ||
		error ("Cannot rename $entries_tmp to $entries\n");

	$cmd_remove && $file_exists &&
		( unlink $file || error ("Cannot delete file $file\n") );
}

# print message and exit (like "die", but without raising an exception)
sub error
{
	print STDERR shift(@_);
	cleanup();
	exit 1;
}

# print usage information and exit
sub usage
{
	print "Usage: cvsdo COMMAND FLAGS FILES\n" .
	"Similate cvs commands without accessing the CVS server\n" .
	"Commands supported:\n" .
	"	add		Add a new file\n" .
	"	  -f | --force	  Don't check whether the file exists\n" .
	"	remove		Remove a file\n" .
	"	  -f | --force	  Delete existing files\n";
	exit 1;
}

# print version information and exit
sub version
{
	print "CVS Disconnected Operation\n" .
	"Written by Pavel Roskin <pavel_roskin\@geocities.com>\n";
	exit 0;
}

# remove temporary files
sub cleanup
{
	(defined $entries_tmp) && (-e $entries_tmp) &&
		( unlink $entries_tmp ||
			error ("Cannot delete file $entries_tmp\n") );
}
