summaryrefslogtreecommitdiff
path: root/rt/bin/rt-commit-handler
diff options
context:
space:
mode:
Diffstat (limited to 'rt/bin/rt-commit-handler')
-rw-r--r--rt/bin/rt-commit-handler846
1 files changed, 846 insertions, 0 deletions
diff --git a/rt/bin/rt-commit-handler b/rt/bin/rt-commit-handler
new file mode 100644
index 0000000..29e443e
--- /dev/null
+++ b/rt/bin/rt-commit-handler
@@ -0,0 +1,846 @@
+#!/usr/bin/perl -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+# {{{ Docs
+# -*-Perl-*-
+#
+#ident "@(#)ccvs/contrib:$Name: $:$Id: rt-commit-handler,v 1.1 2003-07-15 13:16:15 ivan Exp $"
+#
+# Perl filter to handle the log messages from the checkin of files in multiple
+# directories. This script will group the lists of files by log message, and
+# send one piece of mail per unique message, no matter how many files are
+# committed.
+
+=head1 NAME rt-commit-handler
+
+=head1 USAGE
+
+
+
+=head2 Regular use
+
+Stick the following in in CVSROOT/commitinfo
+
+ ALL /opt/rt3/bin/rt-commit-handler --record-last-dir
+
+Stick the following in CVSROOT/loginfo
+
+ ALL /opt/rt3/bin/rt-commit-handler --cvs-root /pathtocvs/root --rt %{Vvts}
+
+=head2 Invocation (advanced use)
+
+rt-commit-handler --cvs-root /path/to/cvs/root [-d] [-D] [-r] [-M module] \
+ [[-m mailto] ...] [[-R replyto] ...] [-f logfile]
+
+
+ -d - turn on debugging
+ -m mailto - send mail to "mailto" (multiple)
+ -R replyto - set the "Reply-To:" to "replyto" (multiple)
+ -M modulename - set module name to "modulename"
+ -f logfile - write commit messages to logfile too
+ -D - generate diff commands
+ --rt - invoke RT commit handler
+ --cvs-root - specify your CVS root
+
+ --record-last-dir - Record the last directory with changes in
+ pre-commit (commitinfo) mode
+
+
+=cut
+
+# }}}
+
+use strict;
+use Carp;
+use Getopt::Long;
+use Text::Wrap;
+use Digest::MD5;
+use MIME::Entity;
+
+use lib ("/opt/rt3/lib", "/opt/rt3/local/lib");
+
+use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
+
+use vars
+ qw(@MAILER $TMPDIR $FILE_PREFIX $LASTDIR_FILE $HASH_FILE $VERSION_FILE $MESSAGE_FILE $MAIL_FILE $DEBUG $MAILTO $REPLYTO $id $MODULE_NAME
+ $LOGIN $COMMITLOG $CVS_ROOT $RT_HANDLER);
+
+#Clean out all the nasties from the environment
+CleanEnv();
+
+#Load etc/config.pm and drop privs
+RT::LoadConfig();
+
+#Drop setgid permissions
+RT::DropSetGIDPermissions();
+
+# {{{ Variable setup
+$TMPDIR = '/tmp';
+$FILE_PREFIX = $TMPDIR . '/#cvs.';
+
+# The root of your CVS install. we should get this from some smarter place.
+# It needs a trailing /
+
+$LASTDIR_FILE = $FILE_PREFIX . "lastdir";
+$HASH_FILE = $FILE_PREFIX . "hash";
+$VERSION_FILE = $FILE_PREFIX . "version";
+$MESSAGE_FILE = $FILE_PREFIX . "message";
+$MAIL_FILE = $FILE_PREFIX . "mail";
+
+$DEBUG = 0;
+$RT_HANDLER = 1;
+
+$MAILTO = '';
+
+my @files = ();
+my (@log_lines);
+my $do_diff = 0;
+my $id = getpgrp(); # note, you *must* use a shell which does setpgrp()
+$LOGIN = getpwuid($<);
+
+# }}}
+
+die "User could not be found" unless ($LOGIN);
+
+# {{{ parse command line arguments (file list is seen as one arg)
+#
+while ( my $arg = shift @ARGV ) {
+
+ if ( $arg eq '-d' ) {
+ $DEBUG = 1;
+ warn "Debug turned on...\n";
+ }
+ elsif ( $arg =~ /^--record-last-dir$/i ) {
+ record_last_dir( $id, $ARGV[0] );
+ exit(0);
+ }
+ elsif ( $arg eq '-m' ) {
+ $MAILTO .= ", " if $MAILTO;
+ $MAILTO .= shift @ARGV;
+ }
+ elsif ( $arg eq '--rt' ) {
+ $RT_HANDLER = 1;
+ }
+ elsif ( $arg eq '-R' ) {
+ $REPLYTO .= ", " if $REPLYTO;
+ $REPLYTO .= shift @ARGV;
+ }
+ elsif ( $arg eq '-M' ) {
+ die ("too many '-M' args\n") if $MODULE_NAME;
+ $MODULE_NAME = shift @ARGV;
+ }
+ elsif ( $arg eq '--cvs-root' ) {
+ $CVS_ROOT = shift @ARGV;
+ $CVS_ROOT .= "/" unless ( $CVS_ROOT =~ /\/$/ );
+ }
+ elsif ( $arg eq '-f' ) {
+ die ("too many '-f' args\n") if $COMMITLOG;
+ $COMMITLOG = shift @ARGV;
+
+ # This is a disgusting hack to untaint $COMMITLOG if we're running from
+ # setgid cvs.
+ $COMMITLOG = untaint($COMMITLOG);
+ }
+ elsif ( $arg eq '-D' ) {
+ $do_diff = 1;
+ }
+ else {
+ @files = split ( ' ', $arg );
+ last;
+ }
+}
+
+# }}}
+
+$REPLYTO = $LOGIN unless ($REPLYTO);
+
+# for now, the first "file" is the repository directory being committed,
+# relative to the $CVSROOT location
+#
+my $dir = shift @files;
+
+# XXX there are some ugly assumptions in here about module names and
+# XXX directories relative to the $CVSROOT location -- really should
+# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
+# XXX we have to parse it backwards.
+#
+# XXX For now we set the `module' name to the top-level directory name.
+#
+unless ($MODULE_NAME) {
+ ($MODULE_NAME) = split ( '/', $dir, 2 );
+}
+
+if ($DEBUG) {
+ warn "module - ", $MODULE_NAME, "\n";
+ warn "dir - ", $dir, "\n";
+ warn "files - ", join ( " ", @files ), "\n";
+ warn "id - ", $id, "\n";
+}
+
+# {{{ Check for a new directory or an import command.
+
+#
+# files[0] - "-"
+# files[1] - "New"
+# files[2] - "directory"
+#
+# files[0] - "-"
+# files[1] - "Imported"
+# files[2] - "sources"
+#
+if ( $files[0] eq "-" ) {
+
+ #we just don't care about New Directory notes
+ unless ( $files[1] eq "New" && $files[2] eq "directory" ) {
+
+ my @text = ();
+
+ push @text, build_header();
+ push @text, "";
+
+ while ( my $line = <STDIN> ) {
+ chop $line; # Drop the newline
+ push @text, $line;
+ }
+
+ append_logfile( $COMMITLOG, @text ) if ($COMMITLOG);
+
+ mail_notification( $id, @text );
+ }
+
+ exit 0;
+}
+
+# }}}
+
+# {{{ Collect just the log message from stdin.
+#
+
+while ( my $line = <STDIN> ) {
+ chop $line; # strip the newline
+ last if ( $line =~ /^Log Message:$/ );
+}
+while ( my $line = <STDIN> ) {
+ chop $line; # strip the newline
+ $line =~ s/\s+$//; # strip trailing white space
+ push @log_lines, $line;
+}
+
+my $md5 = Digest::MD5->new();
+foreach my $line (@log_lines) {
+ $md5->add( $line . "\n" );
+}
+my $hash = $md5->hexdigest();
+
+warn "hash = $hash\n" if ($DEBUG);
+
+if ( !-e "$MESSAGE_FILE.$id.$hash" ) {
+ append_logfile( "$HASH_FILE.$id", $hash );
+ write_file( "$MESSAGE_FILE.$id.$hash", @log_lines );
+}
+
+# }}}
+
+# Spit out the information gathered in this pass.
+
+append_logfile( "$VERSION_FILE.$id.$hash", $dir . '/', @files );
+
+# {{{ Check whether this is the last directory. If not, quit.
+
+warn "Checking current dir against last dir $LASTDIR_FILE.$id\n" if ($DEBUG);
+
+my @last_dir = read_file("$LASTDIR_FILE.$id");
+
+unless ($CVS_ROOT) {
+ die "No cvs root specified with --cvs-root. Can't continue.";
+}
+
+if ( $last_dir[0] ne $CVS_ROOT . $dir ) {
+ warn "Current directory $CVS_ROOT$dir is not last directory $last_dir[0].\n"
+ if ($DEBUG);
+ exit 0;
+}
+
+# }}}
+
+# {{{ End Of Commits!
+#
+
+# This is it. The commits are all finished. Lump everything together
+# into a single message, fire a copy off to the mailing list, and drop
+# it on the end of the Changes file.
+#
+
+#
+# Produce the final compilation of the log messages
+#
+
+my @hashes = read_file("$HASH_FILE.$id");
+my (@text);
+
+push @text, build_header();
+push @text, "";
+
+my ( @added_files, @modified_files, @removed_files );
+
+foreach my $hash (@hashes) {
+
+ # In case we're running setgid, make sure the hash file hasn't been hacked.
+ $hash =~ m/([a-z0-9]*)/ || die "*** Hacking attempt detected\n";
+ $hash = $1;
+
+ my @files = read_file("$VERSION_FILE.$id.$hash");
+ my @log_lines = read_file("$MESSAGE_FILE.$id.$hash");
+
+ my $working_on_dir; # gets set as we iterate through the files.
+ foreach my $file (@files) {
+
+ #If we've entered a new directory, make a note of that and remove the trailing /
+
+ if ( $file =~ s'\/$'' ) {
+ $working_on_dir = $file;
+ next;
+ }
+
+ my @file_entry = ( split ( ',', $file, 4 ), $working_on_dir );
+
+ # file_entry looks like ths:
+
+ # 0 1 2 3 4
+ # Old rev : new rev : tag: file :directory
+ my $entry = {};
+ $entry->{'old'} = $file_entry[0];
+ $entry->{'new'} = $file_entry[1];
+ $entry->{'tag'} = $file_entry[2];
+ $entry->{'file'} = $file_entry[3];
+ $entry->{'dir'} = $file_entry[4];
+
+ if ( $file_entry[0] eq 'NONE' ) {
+ $entry->{'old'} = '0';
+ push @added_files, $entry;
+ }
+ elsif ( $file_entry[1] eq 'NONE' ) {
+ $entry->{'new'} = '0';
+ push @removed_files, $entry;
+ }
+ else {
+ push @modified_files, $entry;
+ }
+ }
+}
+
+# }}}
+
+# {{{ start building up the body
+
+# Strip leading and trailing blank lines from the log message. Also
+# compress multiple blank lines in the body of the message down to a
+# single blank line.
+#
+
+my $blank = 1;
+@log_lines = map {
+ my $wasblank = $blank;
+ $blank = $_ eq '';
+ $blank && $wasblank ? () : $_;
+} @log_lines;
+
+pop @log_lines if $blank;
+
+@modified_files = order_and_summarize_diffs(@modified_files);
+@added_files = order_and_summarize_diffs(@added_files);
+@removed_files = order_and_summarize_diffs(@removed_files);
+
+push @text, "Modified Files:", format_lists(@modified_files)
+ if (@modified_files);
+
+push @text, "Added Files:", format_lists(@added_files) if (@added_files);
+
+push @text, "Removed Files:", format_lists(@removed_files) if (@removed_files);
+
+push @text, "", "Log Message", @log_lines if (@log_lines);
+
+push @text, "";
+
+if ($RT_HANDLER) {
+ rt_handler(
+ @log_lines, "\n",
+ loc("To generate a diff of this commit:\n"), "\n",
+ format_diffs( @modified_files, @added_files, @removed_files )
+ );
+}
+
+if ($COMMITLOG) {
+ append_logfile( $COMMITLOG, @text );
+}
+
+if ($do_diff) {
+ push @text, "";
+ push @text, loc("To generate a diff of this commit:");
+ push @text, format_diffs( @modified_files, @added_files, @removed_files );
+ push @text, "";
+}
+
+# }}}
+
+# {{{ Mail out the notification.
+
+mail_notification( $id, @text );
+
+# }}}
+
+# {{{ clean up
+
+unless ($DEBUG) {
+ $hash = untaint($hash);
+ $id = untaint($id);
+ unlink "$VERSION_FILE.$id.$hash";
+ unlink "$MESSAGE_FILE.$id.$hash";
+ unlink "$MAIL_FILE.$id";
+ unlink "$LASTDIR_FILE.$id";
+ unlink "$HASH_FILE.$id";
+}
+
+# }}}
+
+exit 0;
+
+# {{{ Subroutines
+#
+
+# {{{ append_logfile
+sub append_logfile {
+ my $filename = shift;
+ my (@lines) = @_;
+
+ $filename = untaint($filename);
+
+ open( FILE, ">>$filename" )
+ || die ("Cannot open file $filename for append.\n");
+ foreach my $line (@lines) {
+ print FILE $line . "\n";
+ }
+ close(FILE);
+}
+
+# }}}
+
+# {{{ write_file
+sub write_file {
+ my $filename = shift;
+ my (@lines) = @_;
+
+ $filename = untaint($filename);
+
+ open( FILE, ">$filename" )
+ || die ("Cannot open file $filename for write.\n");
+ foreach my $line (@lines) {
+ print FILE $line . "\n";
+ }
+ close(FILE);
+}
+
+# }}}
+
+# {{{ read_file
+sub read_file {
+ my $filename = shift;
+ my (@lines);
+
+ open( FILE, "<$filename" )
+ || die ("Cannot open file $filename for read.\n");
+ while ( my $line = <FILE> ) {
+ chop $line;
+ push @lines, $line;
+ }
+ close(FILE);
+
+ return (@lines);
+}
+
+# }}}
+
+# {{{ sub format_lists
+
+sub format_lists {
+ my @items = (@_);
+
+ my $files = "";
+ map {
+ $_->{'files'} && ( $files .= ' ' . join ( ' ', @{ $_->{'files'} } ) );
+ } @items;
+
+ my @lines = wrap( "\t", "\t\t", $files );
+ return (@lines);
+
+}
+
+# }}}
+
+# {{{ sub format_diffs
+
+sub format_diffs {
+ my @items = (@_);
+
+ my @lines;
+ foreach my $item (@items) {
+ next unless ( $item->{'files'} );
+ push ( @lines,
+ "cvs diff -r"
+ . $item->{'old'} . " -r"
+ . $item->{'new'} . " "
+ . join ( " ", @{ $item->{'files'} } ) . "\n" );
+
+ }
+
+ @lines = fill( "\t", "\t\t", @lines );
+
+ return (@lines);
+}
+
+# }}}
+
+# {{{ sub order_and_summarize_diffs {
+
+# takes an array of file items
+# returns a sorted array of fileset items, which are like file items, except they can have an array of files, rather than
+# a singleton file.
+
+sub order_and_summarize_diffs {
+
+ my @files = (@_);
+
+ # Sort by tag, dir, file.
+ @files = sort {
+ $a->{'tag'} cmp $b->{'tag'}
+ || $a->{'dir'} cmp $b->{'dir'}
+ || $a->{'file'} cmp $b->{'file'};
+ } @files;
+
+ # Combine adjacent rows that are the same modulo the file name.
+
+ my @items = (undef);
+
+ foreach my $file (@files) {
+ if ( $#items == -1 #if it's empty
+ || ( !defined $items[-1]->{'old'}
+ || $items[-1]->{'old'} ne $file->{'old'} )
+ || ( !defined $items[-1]->{'new'}
+ || $items[-1]->{'new'} ne $file->{'new'} )
+ || ( !defined $items[-1]->{'tag'}
+ || $items[-1]->{'tag'} ne $file->{'tag'} ) )
+ {
+
+ push ( @items, $file );
+ }
+ push ( @{ $items[-1]->{'files'} },
+ $file->{'dir'} . "/" . $file->{'file'} );
+ }
+
+ return (@items);
+}
+
+# }}}
+
+# {{{ build_header
+
+sub build_header {
+ my $now = gmtime;
+ my $header =
+ sprintf( "Module Name:\t%s\nCommitted By:\t%s\nDate:\t\t%s %s %s",
+ $MODULE_NAME, $LOGIN, substr( $now, 0, 19 ), "UTC",
+ substr( $now, 20, 4 ) );
+ return ($header);
+}
+
+# }}}
+
+# {{{ mail_notification
+sub mail_notification {
+ my $id = shift;
+ my (@text) = @_;
+ write_file( "$MAIL_FILE.$id", "From: " . $LOGIN,
+ "Subject: CVS commit: " . $MODULE_NAME, "To: " . $MAILTO,
+ "Reply-To: " . $REPLYTO, "", "", @text );
+
+ my $entity = MIME::Entity->build(
+ From => $LOGIN,
+ To => $MAILTO,
+ Subject => "CVS commit: " . $MODULE_NAME,
+ 'Reply-To' => $REPLYTO,
+ Data => join ( "\n", @text )
+ );
+ if ( $RT::MailCommand eq 'sendmailpipe' ) {
+ open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" )
+ || die "Couldn't send mail: " . $@ . "\n";
+ print MAIL $entity->as_string;
+ close(MAIL);
+ }
+ else {
+ $entity->send( $RT::MailCommand, $RT::MailParams );
+ }
+
+}
+
+# }}}
+
+# {{{ sub record_last_dir
+
+sub record_last_dir {
+ my $id = shift;
+ my $dir = shift;
+
+ # make a note of this directory. later, we'll use this to
+ # figure out if we've gone through the whole commit,
+ # for something that is a bad mockery of attomic commits.
+
+ warn "about to write $dir to $LASTDIR_FILE.$id" if ($DEBUG);
+
+ write_file( "$LASTDIR_FILE.$id", $dir );
+}
+
+# }}}
+
+# {{{ Get the RT stuff set up
+
+# {{{ sub rt_handler
+
+sub rt_handler {
+ my (@LogMessage) = (@_);
+
+ #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+ RT::Init;
+
+ require RT::Ticket;
+
+ #Get the current user all loaded
+ my $CurrentUser = GetCurrentUser();
+
+ if ( !$CurrentUser->Id ) {
+ print
+loc("No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\n");
+ return;
+ }
+
+ my (@commands) = find_commands( \@LogMessage );
+
+ my ( @tickets, @errors );
+
+ # Get the list of tickets we're working with out of commands
+ grep { $_ =~ /^RT-Ticket:\s*(.*?)$/i && push ( @tickets, $1 ) } @commands;
+
+ my $message = new MIME::Entity;
+ $message->build(
+ From => $CurrentUser->EmailAddress,
+ Subject => 'CVS Commit',
+ Data => \@LogMessage
+ );
+
+ # {{{ comment or correspond, as needed
+
+ foreach my $ticket (@tickets) {
+ my $TicketObj = RT::Ticket->new($CurrentUser);
+ $TicketObj->Load($ticket);
+ my ( $id, $msg );
+ unless ( $TicketObj->Id ) {
+ push ( @errors,
+"Couldn't load ticket #$ticket. Not adding commit log to ticket history.\n"
+ );
+ }
+
+ if ( $LogMessage[0] =~ /^(comment|private)$/ ) {
+ ( $id, $msg ) = $TicketObj->Comment( MIMEObj => $message );
+
+ }
+ else {
+ ( $id, $msg ) = $TicketObj->Correspond( MIMEObj => $message );
+ }
+
+ push ( @errors, ">> Log message",
+ "Ticket #" . $TicketObj->Id . ": " . $msg );
+
+ }
+
+ # }}}
+
+ my ($reply) = ActOnPseudoHeaders( $CurrentUser, @commands );
+ print "$reply\n" if ($reply);
+ print join ( "\n", @errors );
+ print "\n";
+
+}
+
+# }}}
+
+# {{{ sub find_commands
+
+sub find_commands {
+ my $lines = shift;
+ my (@pseudoheaders);
+
+ while ( my $line = shift @{$lines} ) {
+ next if $line =~ /^\s*?$/;
+ if ( $line =~ /^RT-/i ) {
+
+ push ( @pseudoheaders, $line );
+ }
+
+ #If we find a line that's not a command, get out.
+ else {
+ unshift ( @{$lines}, $line );
+ last;
+ }
+ }
+
+ return (@pseudoheaders);
+
+}
+
+# }}}
+
+# {{{ sub ActOnPseudoHeaders
+
+=item ActOnPseudoHeaders $PseudoHeaders
+
+Takes a string of pseudo-headers, iterates through them and does what they tell it to.
+
+=cut
+
+sub ActOnPseudoHeaders {
+ my $CurrentUser = shift;
+ my (@actions) = (@_);
+
+ my $ResultsMessage = '';
+ my $Ticket = RT::Ticket->new($CurrentUser);
+
+ foreach my $action (@actions) {
+ my ($val);
+ my $msg = '';
+
+ $ResultsMessage .= ">>> $action\n";
+
+ if ( $action =~ /^RT-(.*?):\s*(.*)$/i ) {
+ my $command = $1;
+ my $args = $2;
+
+ if ( $command =~ /^ticket$/i ) {
+
+ $val = $Ticket->Load($args);
+ unless ($val) {
+ $ResultsMessage .=
+ loc("ERROR: Couldn't load ticket '[_1]': [_2].\n", $1, $msg);
+ . loc("Aborting to avoid unintended ticket modifications.\n")
+ . loc("The following commands were not proccessed:\n\n")
+ . join ( "\n", @actions );
+ return ($ResultsMessage);
+ }
+ $ResultsMessage .= loc("Ticket [_1] loaded\n", $Ticket->Id);
+ }
+ else {
+ unless ( $Ticket->Id ) {
+ $ResultsMessage .= loc("No Ticket specified. Aborting ticket ")
+ . loc("modifications\n\n")
+ . loc("The following commands were not proccessed:\n\n")
+ . join ( "\n", @actions );
+ return ($ResultsMessage);
+ }
+
+ # Deal with the basics
+ if ( $command =~ /^(Subject|Owner|Status|Queue)$/i ) {
+ my $method = 'Set' . ucfirst( lc($1) );
+ ( $val, $msg ) = $Ticket->$method($args);
+ }
+
+ # Deal with the dates
+ elsif ( $command =~ /^(due|starts|started|resolved)$/i ) {
+ my $method = 'Set' . ucfirst( lc($1) );
+ my $date = new RT::Date($CurrentUser);
+ $date->Set( Format => 'unknown', Value => $args );
+ ( $val, $msg ) = $Ticket->$method( $date->ISO );
+ }
+
+ # Deal with the watchers
+ elsif ( $command =~ /^(requestor|requestors|cc|admincc)$/i ) {
+ my $operator = "+";
+ my ($type);
+ if ( $args =~ /^(\+|\-)(.*)$/ ) {
+ $operator = $1;
+ $args = $2;
+ }
+ $type = 'Requestor' if ( $command =~ /^requestor/i );
+ $type = 'Cc' if ( $command =~ /^cc/i );
+ $type = 'AdminCc' if ( $command =~ /^admincc/i );
+
+ my $user = RT::User->new($CurrentUser);
+ $user->Load($args);
+
+ if ($operator eq '+') {
+ ($val, $msg) = $Ticket->AddWatcher( Type => $type,
+ PrincipalId => $user->PrincipalId);
+ } elsif ($operator eq '-') {
+ ($val, $msg) = $Ticket->DeleteWatcher( Type => $type,
+ PrincipalId => $user->PrincipalId);
+ }
+
+ }
+ $ResultsMessage .= $msg . "\n";
+ }
+
+ }
+ return ($ResultsMessage);
+
+}
+
+# }}}
+
+# {{{ sub untaint
+sub untaint {
+ my $val = shift;
+
+ if ( $val =~ /^([-\#\/\w.]+)$/ ) {
+ $val = $1; # $data now untainted
+ }
+ else {
+ die loc("Bad data in [_1]", $val); # log this somewhere
+ }
+ return ($val);
+}
+
+# }}}
+
+=head1 AUTHOR
+
+
+
+ rt-commit-handler is a rewritten version of the NetBSD commit handler,
+ which was placed in the public domain by Charles Hannum. It bore the following
+ authors statement:
+
+ Contributed by David Hampton <hampton@cisco.com>
+ Hacked greatly by Greg A. Woods <woods@planix.com>
+ Rewritten by Charles M. Hannum <mycroft@netbsd.org>
+
+=cut
+