#!/usr/bin/perl -w
# $Date: 2008/03/17 07:54:16 $

# make a failed commit go on a branch, to be merged in later

use strict;
use File::Temp;

## Ensure, all the needed directories are present.

my $home=$ENV{'HOME'};
defined $home && -d $home
	or die "Invalid home directory.\n";

foreach my $dir (qw! .cvsq .cvsq/todo .cvsq/data .cvsq/tmp .cvsq/config .cvsq/dirnames
				     .cvsq/todo-pre .cvsq/todo-post .cvsq-branch .cvsq-branch/backup !) {
     -d "$home/$dir" and next;
     mkdir "$home/$dir", 0700
         or die "Unable to create directory `$home/$dir' ($!)\n";
 }

## check that the todo-pre queue is empty

opendir my $prequeue, "$home/.cvsq/todo-pre"
    or die "Unable to open todo directory `$home/.cvsq/todo-pre' ($!)\n";

while (defined( my $spooled = readdir $prequeue )) {
    $spooled =~ /^\d+$/ and die "Still slots present in the todo-pre queue!\n";
}

closedir $prequeue;

## Now examine the todo queue.
## - the least slot number present is the commit that failed
## - one greater than the maximal slot used is our slot for later.
##   reserve it by creating a temporary file there, which will return non-zero exit value

## Find name in the .cvsq queue

opendir my $queue, "$home/.cvsq/todo"
    or die "Unable to open todo directory `$home/.cvsq/todo' ($!)\n";

my $slot = 1;
my $failedslot = 0;
while (defined( my $spooled = readdir $queue )) {
    $spooled =~ /^\d+$/ or next;
	$failedslot or $failedslot = $spooled;
    $spooled >= $slot and $slot = $spooled + 1;
	$spooled > 0 and $spooled < $failedslot  and $failedslot = $spooled;
}

closedir $queue;

$failedslot or die "Couldn't find the failed slot.\n";

# First create a temporary file, with contents being "exit -1"

my ($tmp,$tmpname) = File::Temp::tempfile('fail-XXXXXX', DIR => "$home/.cvsq/tmp");

print $tmp "exit -1\n";
close $tmp or die "Couldn't close tmp file ($!).\n";

# we assume that link is atomic and in deed will fail, if the file already exists

symlink $tmpname, "$home/.cvsq/todo/$slot"
	or die "Couldn't allocate a new slot due to a seeming race condition ($!).\n";

print "The failed slot is $failedslot and my slot is $slot.\n";

### Generate a safe-copy of the working directory

open (DIRNAMEFILE, "< $home/.cvsq/dirnames/$failedslot")
	or die "Couldn't open directory name of failed commit ($!).\n";
my $dirname = do {local $/; <DIRNAMEFILE>};
close (DIRNAMEFILE) or die "Closing $home/.cvsq/dirnames/$failedslot failed ($!).\n";

print "The failed commit concerns working directory $dirname\n";

system "cp", "-r", "$dirname", "$home/.cvsq/data/$slot-backup"
	and die "cp -r $dirname $home/.cvsq/data/$slot-backup failed ($!)\n";

## Generate a unique name for this branching tag. Date + hostname + pid should be sufficiently good.

my $date = `date -u +%Y-%m-%d-%H%M-%S-%N`; chomp($date);
my $hostname = `hostname`; chomp($hostname);
my $tag = "cvsq-branch-$date-$hostname-$$";

print "Using tag >$tag<\n";

## Tag all the files with a start tag, to remember where the branch started; then
## tag all files with the corresponding branching tag, and update to the branch.


print "(cd \Q$dirname\E; cvs tag $tag-start)\n";
system "(cd \Q$dirname\E; cvs tag $tag-start)"
	and die "command failed ($?)\n";

print "(cd \Q$dirname\E; cvs tag -b $tag)\n";
system "(cd \Q$dirname\E; cvs tag -b $tag)"
	and die "command failed ($?)\n";

print "(cd \Q$dirname\E; cvs update -r$tag)\n";
system "(cd \Q$dirname\E; cvs update -r$tag)"
	and die "command failed ($?)\n";

## generate and schedule a script that later will restore the old working directory and 
## update it, so that cvs will help merge in the latest version
## This will also generate and add a file to remember that a conflict has to be merged in 
## later.

open(ATUPLOAD, "> $home/.cvsq/tmp/$slot-script") or die "Could not generate script file ($!)\n";

print ATUPLOAD <<"EOI";
mv \Q$dirname\E \Q$home\E/.cvsq-branch/backup/$tag && \
cp -r \Q$home\E/.cvsq/data/$slot-backup \Q$dirname\E && \
(cd \Q$dirname\E; cvs update) && \
(cd \Q$dirname\E; touch .$tag) && \
(cd \Q$dirname\E; cvs add .$tag) && \
(cd \Q$dirname\E; cvs ci -m \"branch to finish uploading a cvsq queue\" .$tag)
exit \$?
EOI

close(ATUPLOAD) or die "Could not close script file ($!)\n";


## generate and schedule a script that cleans up the data directory

symlink $tmpname, "$home/.cvsq/todo-post/$slot"
	or die "Couldn't allocate a slot for post commit ($!).\n";

open(ATUPLOAD, "> $home/.cvsq/tmp/$slot-post-script") 
	or die "Could not generate script file ($!)\n";

print ATUPLOAD <<"EOI";
rm -rf \Q$home\E/.cvsq/data/$slot-backup
exit \$?
EOI


rename "$home/.cvsq/tmp/$slot-post-script", "$home/.cvsq/todo-post/$slot"
	or die "Rename failed ($!).\n";

## activate the main script and clean up

rename "$home/.cvsq/tmp/$slot-script", "$home/.cvsq/todo/$slot"
	or die "Rename failed ($!).\n";

unlink $tmpname
	or print "Warning: couldn't clean up the lock skript $tmpname ($!).\n";


## remove the lock

unlink "$home/.cvsq/todo/0"
	or die "Couldn't remove the global lock ($!).";

## done!

print <<"EOI";

Prepared working directory $dirname such that the scheduled
commits should go to branch $tag.

Also scheduled (in slot $slot) to restore the working directory and
call "cvs update". Before doing so, a copy of the working directory
will be saved in $home/.cvsq-branch/backup/$tag.

To continue the upload call "cvsq up" now.
EOI
