#!/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 $/; }; 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