# # CVS -- cvs support for WebMake CGI. package HTML::WebMake::CGI::CVS; ########################################################################### use strict; use HTML::WebMake::Main; use HTML::WebMake::Util; use File::Find; use File::Basename; use vars qw{ @ISA $CVS }; $CVS = 'cvs'; ########################################################################### sub new ($$$$$) { my $class = shift; $class = ref($class) || $class; my $self = { 'cgibase' => shift }; bless ($self, $class); $self; } # ------------------------------------------------------------------------- sub file_in_cvs { my ($self, $file) = @_; my $ents = dirname ($file) . "/CVS/Entries"; my $fname = basename ($file); if (open (ENTS, "<$ents")) { while () { /^\/([^\/]+?)\// or next; if ($1 eq $fname) { close ENTS; return 1; } } close ENTS; } return 0; } # ------------------------------------------------------------------------- sub cvs_update { my ($self) = @_; my $cmd = "$CVS -z3 update -dP"; my $text = $self->run_cvs_command ("update", $cmd); if ($self->{conflicts}) { my $warn = qq{

Some conflicts occurred while updating! Please check and correct errors in the files listed below in red.


}; $text = $warn . $text; } return $text; } # --------------------------------------------------------------------------- sub cvs_commit { my ($self, $msg) = @_; $msg =~ s/\'\"\0//gs; $self->run_cvs_command ("commit", "$CVS -z3 commit -m '".$msg."'"); } # --------------------------------------------------------------------------- sub do_cvs_adds { my ($self) = @_; my $text = ''; my @addtxts = split (/\|/, $self->{cgibase}->{cvsadd}); my @addbins = split (/\|/, $self->{cgibase}->{cvsaddbin}); my %rms = (); foreach my $file (split (/\|/, $self->{cgibase}->{cvsrm})) { $rms{$file} = 1; } chdir ($self->{cgibase}->{file_base}); # create any directories required first my @dirstocreate = (); my %skipfiles = (); foreach my $file (@addtxts, @addbins) { if ($self->file_in_cvs ($file) || defined($rms{$file})) { # $self->warn ("Not adding \"$file\", it is already in CVS."); $skipfiles{$file} = 1; next; } my $working = $file; while (1) { my $base = basename ($working); my $dir = dirname ($working); my $ents = $dir.'/CVS/Entries'; last if ($dir eq '.'); if (!-f $ents) { push (@dirstocreate, $dir); $working = $dir; } else { last; } } } # for a file "foo/bar/baz/newfile", @dirstocreate will look like: # qw(foo/bar/baz foo/bar foo) now. my $cmd; if (scalar @dirstocreate > 0) { $cmd = "cvs add '". join ("' '", reverse @dirstocreate)."'"; $text .= $self->run_cvs_command ("add dirs", $cmd); } # now add the files. @addtxts = grep { !defined $skipfiles{$_} } @addtxts; @addbins = grep { !defined $skipfiles{$_} } @addbins; if (scalar @addtxts > 0) { $cmd = "cvs add '". join ("' '", @addtxts)."'"; $text .= $self->run_cvs_command ("add files", $cmd); } if (scalar @addbins > 0) { $cmd = "cvs add -kb '". join ("' '", @addbins)."'"; $text .= $self->run_cvs_command ("add files", $cmd); } $text; } # --------------------------------------------------------------------------- sub do_cvs_deletes { my ($self) = @_; my $text = ''; my @delfiles = split (/\|/, $self->{cgibase}->{cvsrm}); my @deldirs = split (/\|/, $self->{cgibase}->{cvsrmdir}); # ensure the arrays are sorted so that subdirectories and subfiles # are listed deepest first. @deldirs = sort { count_slashes($b) <=> count_slashes($a) } @deldirs; my @newdeldirs = (); my %delfiles_hash = (); foreach my $dir (@deldirs) { my $ents = $dir.'/CVS/Entries'; if (!-f $ents) { $self->warn ("\"$dir\" is not in CVS (no CVS/Entries file)"); next; } open (ENTS, "<$ents"); while () { /^\/([^\/]+?)\// or next; $delfiles_hash{$1} = 1; } close ENTS; push (@newdeldirs, $dir); } @deldirs = @newdeldirs; foreach my $file (@delfiles) { if (!$self->file_in_cvs ($file)) { # $self->warn ("Not deleting \"$file\", it is not in CVS."); next; } $delfiles_hash{$file} = 1; } @delfiles = sort { count_slashes($b) <=> count_slashes($a) } keys %delfiles_hash; # now we have a full list of: # - files we were asked to delete # - directories we were asked to delete, that are in CVS # - and all CVS files in those dirs # now process them. if (scalar @delfiles > 0) { my $cmd = "cvs remove '". join ("' '", @delfiles)."'"; $text .= $self->run_cvs_command ("delete files", $cmd); } # er, that's it; we don't need to explicitly delete the directories. # once they're empty of files, we're done. $text; } # --------------------------------------------------------------------------- sub run_cvs_command { my ($self, $opdesc, $cmd) = @_; if (!chdir ($self->{cgibase}->{file_base})) { $self->warn ("Cannot chdir to {WMROOT}, not performing site $opdesc"); return ''; } my $text = ''; $text .= qq{

$cmd:

}; if (!open (CVS, "$cmd 2>&1 |")) { $self->warn ("$opdesc failed: '$cmd': $!"); goto failed; } my $conflicts = 0; while () { $_ = $self->txt2html($_); if ($opdesc eq 'update') { if (/^C (.*)$/) { $conflicts++; } s/^[UP] (.*)$/Updated: $1<\/font>/g; s/^A (.*)$/Added: $1<\/font>/g; s/^R (.*)$/Removed: $1<\/font>/g; s/^M (.*)$/Commit required: $1<\/font>/g; s/^C (.*)$/CONFLICT, please edit: $1<\/font>/g; s/^\? (.*)$/Not in CVS: $1<\/a>/g; s/^(cvs update: Updating .*)$/$1<\/font>/g; } if ($opdesc eq 'commit') { s/^\? (.*)$/Not in CVS: $1<\/a>/g; s/^(cvs commit: Examining .*)$/$1<\/font>/g; s/^(new revision: .*)$/$1<\/font>/g; s/^(done)$/$1<\/font>/g; s/^(.* \<-- .*)$/$1<\/font>/g; } $text .= $_; } $self->{conflicts} = $conflicts; close CVS; my $status = ($? >> 8); $text .= "(exit status $status)<\/font>"; if ($status != 0) { $self->warn ("$opdesc failed: command '$cmd' exited badly"); goto failed; } failed: $text .= qq{
}; $text; } # --------------------------------------------------------------------------- sub count_slashes { my $dir = shift; my @slashes = ($dir =~ m/\//g); return scalar @slashes; } # --------------------------------------------------------------------------- sub warn { my ($self) = shift; $self->{cgibase}->warn (@_); } sub txt2html { my ($self) = shift; $self->{cgibase}->txt2html (@_); } # --------------------------------------------------------------------------- 1;