#!/usr/bin/perl # # Script that updates a copy of a subversion repository # and e-mails update results to someone, like, say # conflict alerts. # #-------------------------------------------------- # Stuff you can configure (some of this can also be passed # as command line args, see help) #-------------------------------------------------- # email address to send alerts to. # my $mail_to = 'user@domain'; # email address to use as 'from' # my $mail_from = 'svnadmin@localhost'; # Subject to use for email # my $mail_subject = 'SVN Update summary'; # The paths we're updating # my @update_paths = (); # optional - log to this file # my $logfile = undef; # Debug # my $debug = 0; # Subversion location # my $svn = "/usr/local/bin/svn"; # sendmail # my $sendmail = "/usr/sbin/sendmail"; #-------------------------------------------------- # Nothing more to configure below, just perl magic #-------------------------------------------------- use strict; use Carp; # Parse some arguments and stuff # while(@ARGV) { my $arg = shift @ARGV; if($arg =~ m/^-.*/) { if($arg =~ m/^-d$/) { $debug = 1; } elsif($arg =~ m/^--log-file=(.+)$/) { $logfile = $1; } elsif($arg =~ m/^--mail-to=(.+)$/) { $mail_to = $1; } elsif($arg =~ m/^--mail-from=(.+)$/) { $mail_from = $1; } elsif($arg =~ m/^--mail-subject=(.+)$/) { $mail_subject = $1; } elsif($arg =~ m/^-h$/ or $arg =~ m/^--help$/) { &usage; } else { &usage("Invalid argument \"$arg\""); } } else { push @update_paths, $arg; } } # Sanity checks on args # die &usage("$0: Unable to execute svn '$svn'") unless -x $svn; die &usage("$0: Unable to execute sendmail '$sendmail'") unless -x $sendmail; # Check update paths # { my @errors = (); foreach my $path (@update_paths) { push @errors, "'$path' does not exist\n" unless -e $path; } die &usage(@errors) if @errors; } my $now = scalar(localtime); if($logfile) { open (LOG, ">>$logfile"); print LOG "$now: Starting $0\n"; if($debug) { my @paths = map { "$_, " } @update_paths; print LOG "Settings:\n"; print LOG "paths @paths\n"; print LOG "mail to $mail_to\n"; print LOG "mail from $mail_from\n"; print LOG "subject $mail_subject\n"; } } my @body = (); foreach my $path (@update_paths) { my $start_time = time; my $now = scalar(localtime); my @update_result = &read_from_process($svn, 'update', $path, '--non-interactive'); my $num_updated = 0; my $num_added = 0; my $num_deleted = 0; my $num_merged = 0; my $num_conflicted = 0; my @conflicted_files = (); my $failed = 0; foreach (@update_result) { if($logfile) { print LOG "$_\n"; } if($failed) { push(@body, "$_\n"); } else { if(m/^$0.*failed.*/i) { $failed = 1; push(@body, "Update failed:\n"); } elsif(m/^A\s+/) { $num_added++; } elsif(m/^U\s+/) { $num_updated++; } elsif(m/^D\s+/) { $num_deleted++; } elsif(m/^G\s+/) { $num_merged++; } elsif(m/^C\s+/) { $num_conflicted++; if (m/^C\s+(.+)$/) { push(@conflicted_files, $1); } } } } my $finish_time = time; my $seconds = $finish_time - $start_time; my $minutes = int($seconds / 60); my $hours = int($minutes / 60); $seconds = ($seconds - ($minutes * 60)) if $minutes; $minutes = ($minutes - ($hours * 60)) if $hours; my $finished_str = "Finished update in "; $finished_str .= "$hours hours " if $hours; $finished_str .= "$minutes minutes " if $minutes; $finished_str .= "$seconds seconds" if $seconds; if(!$failed) { push(@body, "$now Updated $path\n"); if($num_conflicted) { my $s = ''; $s = "s" if($num_conflicted > 1); push(@body, "$num_conflicted Conflict$s Detected!\n"); push(@body, "\n"); push(@body, "Conflicted files:\n"); push(@body, @conflicted_files, "\n"); } push(@body, "\n"); push(@body, "$num_added files added\n"); push(@body, "$num_updated files updated\n"); push(@body, "$num_deleted files deleted\n"); push(@body, "$num_merged files merged\n"); push(@body, "\n$finished_str\n"); } push(@body, "--------------------------------------------------\n\n"); } my @head; push(@head, "To: $mail_to\n"); push(@head, "From: $mail_from\n"); push(@head, "Subject: $mail_subject\n"); push(@head, "Content-Type: text/plain; charset=UTF-8\n"); push(@head, "Content-Transfer-Encoding: 8bit\n"); push(@head, "\n"); my $command = "$sendmail $mail_to"; if (open(SENDMAIL, "| $command")) { print SENDMAIL @head, @body; close SENDMAIL or warn "$0: error in closing `$command' for writing: $!\n"; } if($logfile) { if($debug) { print LOG "@head"; print LOG "@body"; } my $now = scalar(localtime); print LOG "$now: Finished $0\n"; close(LOG); } # done! # exit 0; # Use safe_read_from_pipe to start a child process safely and return # the output if it succeeded or an error message followed by the output # if it failed. # sub read_from_process { unless (@_) { croak "$0: read_from_process passed no arguments.\n"; } my ($status, @output) = &safe_read_from_pipe(@_); if ($status) { return ("$0: `@_' failed with this output:", @output); } else { return wantarray? @output : $output[0]; } } # Start a child process safely without using /bin/sh. # sub safe_read_from_pipe { unless (@_) { croak "$0: safe_read_from_pipe passed no arguments.\n"; } my $pid = open(SAFE_READ, '-|'); unless (defined $pid) { die "$0: cannot fork: $!\n"; } unless ($pid) { open(STDERR, ">&STDOUT") or die "$0: cannot dup STDOUT: $!\n"; exec(@_) or die "$0: cannot exec `@_': $!\n"; } my @output; while () { s/[\r\n]+$//; push(@output, $_); } close(SAFE_READ); my $result = $?; my $exit = $result >> 8; my $signal = $result & 127; my $cd = $result & 128 ? "with core dump" : ""; if ($signal or $cd) { warn "$0: pipe from `@_' failed $cd: exit=$exit signal=$signal\n"; } if (wantarray) { return ($result, @output); } else { return $result; } } sub usage { warn "@_\n" if @_; die "usage: $0 [options] PATH1 PATH2...\n", "options are\n", " -d Turn on debug for more verbose logging\n", " -h Print the illuminating help\n", " --help Same as -h\n", " --log-file=logfile Log actions to this log file\n", " --mail-to=user\@domain e-mail address to send the summary to\n", " --mail-from=user\@domain e-mail address to use in the from field\n", " --mail-subject=subject Subject to use for the email\n", "\n", "This script updates a copy of a Subversion Repository and sends summary\n", "information to the specified e-mail address including conflict alerts\n", "\n", "If more than one path is passed, each path will be updated separately\n", "and summary included in th same email\n"; } __END__ =head1 NAME $0 Update a Subversion work area and e-mail a summary =head1 SYNOPSIS $0 [I] =head1 DESCRIPTION This scripts updates a checked-out copy of a Subversion repository and e-mails a summary of the update and a list of any conflicted files. =head1 OPTIONS =over 4 =item B<-d> Turn on debug mode -- just prints slightly more information in the log file (if any). Useless without also providing a log file location =item B<-h> Print help =item B<--help> Same as -h =item B<--path=path> Specifies the path to be udpated. =item B<--log-file=F> Location of a log file, if not specified the script will not provide any feedback. =item B<--mailt-to=user\@domain> The destination address for the email. =item B<--mail-from=user\@domain> The e-mail address to use as From =item B<--mail-subject=subject> The subject to use for the e-mail =back =head1 BUGS/MISFEATURES This script will work with absolutely no arguments, but unless you set the default values to something meaningful, you probably don't want that. =head1 AUTHOR Kasia Trapszo C =cut