OSSP CVS Repository

ossp - Difference in CVSROOT/shiela versions 1.10 and 1.11
Not logged in
[Honeypot]  [Browse]  [Home]  [Login]  [Reports
[Search]  [Ticket]  [Timeline
  [History

CVSROOT/shiela 1.10 -> 1.11

--- shiela       2001/12/16 13:08:11     1.10
+++ shiela       2002/12/22 19:25:51     1.11
@@ -1,15 +1,16 @@
 #!/v/ossp/sw/bin/perl -w
 ##
-##  Shiela - CVS Access Control and Logging Facility
-##  Copyright (c) 2000 Ralf S. Engelschall <rse@engelschall.com>
+##  OSSP shiela - CVS Access Control and Logging Facility
+##  Copyright (c) 2000-2002 Ralf S. Engelschall <rse@engelschall.com>
+##  Copyright (c) 2000-2002 The OSSP Project <http://www.ossp.org/>
 ##
-##  This file is part of Shiela, an access control and logging
+##  This file is part of OSSP shiela, an access control and logging
 ##  facility for Concurrent Versions System (CVS) repositories
-##  which can be found at http://www.ossp.org/pkg/shiela/.
+##  which can be found at http://www.ossp.org/pkg/tool/shiela/.
 ##
 ##  This program is free software; you can redistribute it and/or
-##  modify it under the terms of the GNU General Public  License 
-##  as published by the Free Software Foundation; either version 
+##  modify it under the terms of the GNU General Public  License
+##  as published by the Free Software Foundation; either version
 ##  2.0 of the License, or (at your option) any later version.
 ##
 ##  This program is distributed in the hope that it will be useful,
@@ -17,23 +18,25 @@
 ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 ##  General Public License for more details.
 ##
-##  You should have received a copy of the GNU General Public License 
+##  You should have received a copy of the GNU General Public License
 ##  along with this file; if not, write to the Free Software
 ##  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 ##  USA, or contact Ralf S. Engelschall <rse@engelschall.com>.
 ##
-##  shiela: Shiela control program (syntax: Perl)
+##  shiela.pl: control program (syntax: Perl)
 ##
 
-my $version = '0.9.2';
+my $version = '1.0.0';
 
 require 5.005;
 
 use strict;       # shipped with Perl since 5.000
 use POSIX;        # shipped with Perl since 5.000
+use IO::File;     # shipped with Perl since 5.003
 use IO::Handle;   # shipped with Perl since 5.003
 use IPC::Open2;   # shipped with Perl since 5.003
 use Data::Dumper; # shipped with Perl since 5.005
+use Cwd;          # shipped with Perl since 5.005
 
 #   DEBUGGING
 $Data::Dumper::Purity = 1;
@@ -102,7 +105,7 @@
 ##  _________________________________________________________________
 ##
 ##  Run-time information determination.
-##  
+##
 ##  This is a two-stage process, because we need parts of the
 ##  information for parsing the configuration, but OTOH we need the
 ##  configuration for determining other information. To simply solve
@@ -115,7 +118,7 @@
     my ($program, $version) = @_;
     my $RT = {};
 
-    #   program version and name 
+    #   program version and name
     $RT->{vers} = $version;
     $RT->{name} = ($program =~ m|^.*?([^/]+?)(?:\.[^/.]+)?$|)[0];
 
@@ -128,7 +131,7 @@
     $RT->{userid}  = $ENV{CVSUSER} || $ENV{LOGNAME} || $ENV{LOGUSER} || $ENV{USER} || die 'unknown CVS user';
 
     #   various directory paths
-    $RT->{tmpdir}   = $ENV{TMPDIR} || $ENV{TEMPDIR} || '/tmp';
+    $RT->{tmpdir}    = $ENV{TMPDIR} || $ENV{TEMPDIR} || '/tmp';
     $RT->{cvstmpdir} = (-w "$RT->{cvsroot}/CVSTMP" ? "$RT->{cvsroot}/CVSTMP" : $RT->{tmpdir});
     $RT->{cvsadmdir} = "$RT->{cvsroot}/CVSROOT";
     $RT->{cvslogdir} = (-w "$RT->{cvsroot}/CVSLOG" ? "$RT->{cvsroot}/CVSLOG" : $RT->{cvsadmdir});
@@ -146,7 +149,7 @@
 
     #   determine user information
     $RT->{username} = $CF->{Project}->{User}->{$RT->{userid}}->{name} ||
-                      die "CVS user `$RT->{userid}' not found in Shiela configuration";
+                      die "CVS user `$RT->{userid}' not found in OSSP shiela configuration";
     $RT->{usermail} = $CF->{Project}->{User}->{$RT->{userid}}->{mail} ||
                       "$RT->{userid}\@localhost";
 
@@ -164,7 +167,7 @@
     sub find_program {
         my ($name) = @_;
         my ($prog) = '';
-        foreach my $dir (split(/:/, "/v/ossp/sw/bin:/v/ossp/sw/sbin:$ENV{PATH}:/usr/local/lib:/usr/lib:/lib")) {
+        foreach my $dir (split(/:/, "$ENV{PATH}:/usr/local/lib:/usr/lib:/lib")) {
             if (-x "$dir/$name") {
                 $prog = "$dir/$name";
                 last;
@@ -172,11 +175,22 @@
         }
         return $prog;
     }
-    $RT->{sendmail} = &find_program("ssmtp") ||
-                      &find_program("sendmail") || 
+    $RT->{sendmail} = $CF->{Environment}->{Program}->{sendmail} ||
+                      &find_program("ssmtp") ||
+                      &find_program("sendmail") ||
                       die "unable to find `sendmail' program";
-    $RT->{cvs}      = &find_program("cvs") || 
+    $RT->{cvs}      = $CF->{Environment}->{Program}->{cvs} ||
+                      &find_program("cvs") ||
                       die "unable to find `cvs' program";
+    $RT->{diff}     = $CF->{Environment}->{Program}->{diff} ||
+                      &find_program("diff") ||
+                      '';
+    $RT->{xdelta}   = $CF->{Environment}->{Program}->{xdelta} ||
+                      &find_program("xdelta") ||
+                      '';
+    $RT->{uuencode} = $CF->{Environment}->{Program}->{uuencode} ||
+                      &find_program("uuencode") ||
+                      '';
 
     #   pre-calculate a reasonable MIME boundary tag
     my $randtag;
@@ -191,8 +205,8 @@
     my $v = `$RT->{cvs} --version 2>/dev/null`;
     $RT->{cvsvers} = '?';
     $RT->{cvsvers} = $1 if ($v =~ m|Concurrent\s+Versions\s+System\s+\(CVS\)\s+([\d.p]+)\s+|s);
-    $RT->{cvsossp} = 0;
-    $RT->{cvsossp} = 1 if ($v =~ m|OSSP|s);
+    $RT->{cvsrse} = 0;
+    $RT->{cvsrse} = 1 if ($v =~ m|\[RSE\]|s);
     die "$RT->{cvs} is not at least CVS 1.10" if ($RT->{cvsvers} !~ m|^1\.1[0-9]|);
     $RT->{useserver} = 0;
     $RT->{useserver} = 1 if ($v =~ m|server|s);
@@ -208,7 +222,7 @@
 ##
 ##  C-style configuration syntax parsing.
 ##
-##  <config>    ::= <directive> 
+##  <config>    ::= <directive>
 ##                | <config> <directive>
 ##  <directive> ::= <name> ';'
 ##                | <name> <args> ';'
@@ -284,7 +298,7 @@
                     $t = $';
                 }
                 elsif ($t =~ m|^\s+|s) {
-                    push(@{$A}, $a) if ($a ne ''); 
+                    push(@{$A}, $a) if ($a ne '');
                     $a = '';
                     $t = $';
                 }
@@ -346,7 +360,7 @@
 
 ##  _________________________________________________________________
 ##
-##  Determine Shiela configuration.
+##  Determine OSSP shiela configuration.
 ##
 ##  We theoretically could directly operate on the syntax tree as
 ##  created by parse_config() above. But for convinience reasons and
@@ -359,10 +373,10 @@
     my ($file) = @_;
 
     #   read configuration file
-    open(CFG, "<$file") || die "unable to open configuration file `$file'";
+    my $io = new IO::File "<$file" || die "unable to open configuration file `$file'";
     my $t = '';
-    $t .= $_ while (<CFG>);
-    close(CFG);
+    $t .= $_ while (<$io>);
+    $io->close;
 
     #   parse configuration syntax into nested internal structure and
     #   in parallel (through a callback function) create the final
@@ -377,6 +391,9 @@
         },
         'Logging' => {
             'Report' => {}
+        },
+        'Environment' => {
+            'Program' => {}
         }
     };
     my $cf = &parse_config($t, \&parse_config_callback, $CF);
@@ -385,7 +402,7 @@
         if ($action eq 'CMD' and $cf->[0] =~ m/(Project|Repository|Logging)/) {
             my $a;
             foreach $a (@{$cf->[1]}) {
-                $CF->{$1}->{$a->[0]} = $a->[1] 
+                $CF->{$1}->{$a->[0]} = $a->[1]
                     if ($a->[0] ne 'Users' and
                         $a->[0] ne 'Groups' and
                         $a->[0] ne 'Modules' and
@@ -432,6 +449,9 @@
                 }
             }
         }
+        elsif ($action eq 'CMD' and $cf->[0] eq 'Program') {
+            $CF->{Environment}->{Program}->{$cf->[1]} = $cf->[2];
+        }
         return $cf;
     }
     return $CF;
@@ -490,7 +510,7 @@
     #   prepare the pattern
     if ($pat =~ m|^m(.)(.+)\1$| and $2 !~ m|$1|) {
         #   pattern is a regular expression,
-        #   so just make sure it is anchored 
+        #   so just make sure it is anchored
         $pat =~ s|^([^\^])|^$1|;
         $pat =~ s|([^\$])$|$1\$|;
     }
@@ -519,7 +539,7 @@
 
 ##  _________________________________________________________________
 ##
-##  CVS server communication. 
+##  CVS server communication.
 ##
 ##  We use this instead of calling the regular CVS client commands
 ##  because we not always have a working directory available (which is
@@ -529,7 +549,7 @@
 ##  process and act as we would be a regular CVS client. For convinience
 ##  reasons, the communication is encapsulated in a "CVS" class object.
 ##  _________________________________________________________________
-## 
+##
 
 package CVS;
 
@@ -541,7 +561,7 @@
     my $cvsroot = shift || $ENV{CVSROOT} || die "unknown CVSROOT";
     my $trace = shift || 0;
 
-    #   spawn a CVS server process and establish a 
+    #   spawn a CVS server process and establish a
     #   bidirectional communication path to it.
     my $cvs = {};
     $cvs->{cvsroot} = $cvsroot;
@@ -602,7 +622,7 @@
     $data .= "\n" if ($data !~ m|\n$|s);
     $cvs->{wfd}->print($data);
     if ($cvs->{trace}) {
-        $data =~ s|^|cvs server: -> |mg; 
+        $data =~ s|^|cvs server: -> |mg;
         print STDERR $data;
     }
 }
@@ -711,14 +731,13 @@
     $sm->{fd} = new IO::Handle;
     open($sm->{fd}, "|$RT->{sendmail} -oi -oem $toaddr");
     print "sendmail: spawned \"$RT->{sendmail} -oi -oem $toaddr\"\n" if ($sm->{trace});
-    $sm->{header} = 
+    $sm->{header} =
         "From: \"".$RT->{username}."\" <".$RT->{usermail}.">\n" .
         "To: $toaddr\n" .
-        "User-Agent: ".uc(substr($RT->{name}, 0, 1)).substr($RT->{name}, 1)."/$RT->{vers} " .
-                     ($RT->{cvsossp} ? "OSSP-CVS" : "CVS")."/$RT->{cvsvers}\n" .
+        "User-Agent: OSSP shiela ".$RT->{vers}." [CVS ".$RT->{cvsvers}.($RT->{cvsrse} ? "+RSE" : "")."]\n" .
         "Precedence: bulk\n" .
         "Mime-Version: 1.0\n" .
-        "Content-Type: text/plain; charset=iso-8859-1\n" . 
+        "Content-Type: text/plain; charset=iso-8859-1\n" .
         "Content-Transfer-Encoding: 8bit\n";
     $sm->{body} = '';
     return $sm;
@@ -792,24 +811,24 @@
 
     #   append to or override a file with lines from an array
     if ($op eq 'append' or $op eq 'write') {
-        open(FP, ($op eq 'append' ? ">" : "").">$file") or
-            die "unable to open `$file' for $op";
+        my $io = new IO::File ($op eq 'append' ? ">>$file" : ">$file")
+            || die "unable to open `$file' for operation `$op'";
         foreach my $line (@lines) {
             $line =~ s|\n+$||s;
-            print FP $prefix . $line . "\n";
+            $io->print($prefix . $line . "\n");
         }
-        close(FP);
+        $io->close;
     }
     #   read a file line by line into an array
     elsif ($op eq 'read') {
         my @text = ();
-        open(FP, "<$file") or
-            die "unable to open `$file' for $op";
-        while (<FP>) {
+        my $io = new IO::File "<$file"
+            || die "unable to open `$file' for $op";
+        while (<$io>) {
             s|\n$||s;
             push(@text, $prefix . $_);
         }
-        close(FP);
+        $io->close;
         return @text;
     }
 }
@@ -827,8 +846,7 @@
 sub history_save {
     my ($PA, $RT, $CF, $IN) = @_;
     my $O = '';
-    my $file;
-    foreach $file (keys(%{$IN->{file}})) {
+    foreach my $file (keys(%{$IN->{file}})) {
         my $e = $IN->{file}->{$file};
         $O .= $IN->{handle};
         $O .= ",$file";
@@ -841,10 +859,10 @@
         $O .= ",".$e->{delta};
         $O .= "\n";
     }
-    open(HDB, ">>".$RT->{historydb}) 
-         || die "cannot store information to history db `$file'";
-    print HDB $O;
-    close(HDB);
+    my $io = new IO::File ">>".$RT->{historydb}
+         || die "cannot store information to history db `$RT->{historydb}'";
+    $io->print($O);
+    $io->close;
     return;
 }
 
@@ -873,7 +891,7 @@
     my @groups = split(/,/, $RT->{usergroups});
     my $file;
     foreach $file (@files) {
-        $file =~ m|^([^/]+)/(.*):([^:]+)$| 
+        $file =~ m|^([^/]+)/(.*):([^:]+)$|
             || die "invalid file specification `$file' for access control";
         my ($d, $f, $t) = ($1, $2, $3);
         my $allow = 0;
@@ -905,7 +923,7 @@
                             }
                             if (   (    not $not
                                     and ($u eq '*' or $u eq $user)
-                                    and ($g eq '*' or grep(m/^$g$/, @groups))) 
+                                    and ($g eq '*' or grep(m/^$g$/, @groups)))
                                 or (    $not
                                     and ($u ne '*' and $u ne $user)
                                     and ($g ne '*' and not grep(m/^$g$/, @groups)))) {
@@ -961,6 +979,41 @@
 
 ##  _________________________________________________________________
 ##
+##  Wrap a single-line log message.
+##
+##  This line-wraps a single-line log message into a multi-line log
+##  message.
+##  _________________________________________________________________
+##
+
+sub wrap_message {
+    my ($columns, $text) = @_;
+
+    my $r = "";
+    my $nl = "";
+    my $left = "";
+    pos($text) = 0;
+
+    while ($text !~ m/\G\s*\Z/gc) {
+        if ($text =~ /\G([^\n]{0,$columns})(\s|\z)/xmgc) {
+            $r .= $nl . $1;
+            $left = $2;
+        } elsif ($text =~ /\G([^\n]*?)(\s|\z)/xmgc) {
+            $r .= $nl . $1;
+            $left = $2;
+        }
+        $nl = "\n";
+    }
+
+    $r .= $left;
+    $r .= substr($text, pos($text), length($text)-pos($text))
+        if (pos($text) ne length($text));
+
+    return $r;
+}
+
+##  _________________________________________________________________
+##
 ##  TAGINFO HOOK
 ##
 ##  We hook into CVS via `taginfo' to check whether user is allowed to
@@ -980,12 +1033,13 @@
 
     #   take the arguments
     my ($tagname, $tagop, $cvsdir, %cvsfiles) = @{$PA->{ARG}};
+
+    #   strip absolute prefix
     $cvsdir =~ s|^$RT->{cvsroot}/?||;
-    my $cvsdirphysical = `cd $RT->{cvsroot} && pwd`;
-    $cvsdirphysical =~ s|\n$||s; 
+    my $cvsdirphysical = Cwd::abs_path($RT->{cvsroot});
     $cvsdir =~ s|^$cvsdirphysical/?||;
 
-    #   provide access control 
+    #   provide access control
     my @paths = ();
     foreach my $cvsfile (keys(%cvsfiles)) {
         push(@paths, "$cvsdir/$cvsfile:*");
@@ -993,7 +1047,7 @@
     my @denyfiles = &do_access_control($PA, $RT, $CF, @paths);
     if ($#denyfiles > -1) {
         #   inform user
-        print "cvs tag: Access Denied - Insufficient Karma!\n"; 
+        print "cvs tag: Access Denied - Insufficient Karma!\n";
         print "cvs tag: Tagging access for the following file(s) was denied:\n";
         foreach my $file (@denyfiles) {
             print "cvs tag: `$file'\n";
@@ -1042,7 +1096,7 @@
     my ($cvsdir, @cvsfiles) = @{$PA->{ARG}};
     $cvsdir =~ s|^$RT->{cvsroot}/?||;
 
-    #   provide access control 
+    #   provide access control
     my @paths = ();
     foreach my $cvsfile (@cvsfiles) {
         push(@paths, "$cvsdir/$cvsfile:*");
@@ -1050,7 +1104,7 @@
     my @denyfiles = &do_access_control($PA, $RT, $CF, @paths);
     if ($#denyfiles > -1) {
         #   inform user
-        print "cvs admin: Access Denied - Insufficient Karma!\n"; 
+        print "cvs admin: Access Denied - Insufficient Karma!\n";
         print "cvs admin: Admin access for the following file(s) was denied:\n";
         foreach my $file (@denyfiles) {
             print "cvs admin: `$file'\n";
@@ -1098,7 +1152,7 @@
     my ($cvsbranch, $cvsdir, @cvsfiles) = @{$PA->{ARG}};
     $cvsdir =~ s|^$RT->{cvsroot}/?||;
 
-    #   provide access control 
+    #   provide access control
     my @paths = ();
     foreach my $cvsfile (@cvsfiles) {
         push(@paths, "$cvsdir/$cvsfile:$cvsbranch");
@@ -1106,7 +1160,7 @@
     my @denyfiles = &do_access_control($PA, $RT, $CF, @paths);
     if ($#denyfiles > -1) {
         #   inform user
-        print "cvs import: Access Denied - Insufficient Karma!\n"; 
+        print "cvs import: Access Denied - Insufficient Karma!\n";
         print "cvs import: Import access for the following files was denied:\n";
         foreach my $file (@denyfiles) {
             print "cvs import: `$file'\n";
@@ -1166,9 +1220,10 @@
     #   annotate the files with the branch they stay on
     my $cvsstat = '';
     if (not $RT->{useserver}) {
-        open(CVSS, "$RT->{cvs} -f -l -Q -n status ".join(' ', @cvsfiles)."|");
-        $cvsstat .= $_ while (<CVSS>);
-        close(CVSS);
+        my $io = new IO::File "$RT->{cvs} -f -l -Q -n status ".join(' ', @cvsfiles)."|"
+            || die "unable to open CVS command pipe for reading";
+        $cvsstat .= $_ while (<$io>);
+        $io->close;
     }
     else {
         my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
@@ -1195,7 +1250,7 @@
     }
     @cvsfiles = @newfiles;
 
-    #   provide access control 
+    #   provide access control
     my @paths = ();
     foreach my $cvsfile (@cvsfiles) {
         push(@paths, "$cvsdir/$cvsfile");
@@ -1203,7 +1258,7 @@
     my @denyfiles = &do_access_control($PA, $RT, $CF, @paths);
     if ($#denyfiles > -1) {
         #   inform user
-        print "cvs commit: Access Denied - Insufficient Karma!\n"; 
+        print "cvs commit: Access Denied - Insufficient Karma!\n";
         print "cvs commit: Commit access for the following file(s) was denied:\n";
         foreach my $file (@denyfiles) {
             print "cvs commit: `$file'\n";
@@ -1241,7 +1296,7 @@
 ##  We hook into CVS via `commitinfo' to post-process log messages.
 ##  The intention is to sanitise the results of what the user may have
 ##  `done' while editing the commit log message. If CVS is a standard
-##  version, this check is advisory only. If CVS contains the OSSP
+##  version, this check is advisory only. If CVS contains the RSE
 ##  patches, the log message is changed and CVS reads back the contents
 ##  so that this script can actually make changes.
 ##
@@ -1254,27 +1309,29 @@
     my ($PA, $RT, $CF) = @_;
     my $rv = 0;
 
-    #   we require the OSSP patches for operation
-    return $rv if (not $RT->{cvsossp});
+    #   we require the RSE patches for operation
+    return $rv if (not $RT->{cvsrse});
 
     #   suck in the log message
     my $logfile = $PA->{ARG}->[0];
-    open(FP,  "<$logfile") || die "cannot open message file `$logfile' for reading";
+    my $io = new IO::File "<$logfile"
+        || die "cannot open message file `$logfile' for reading";
     my $data = '';
-    $data .= $_ while (<FP>);
-    close(FP);
+    $data .= $_ while (<$io>);
+    $io->close;
 
     #  filter the log message
     $data = &compress_message($data);
 
     #  update the log message
-    #  (OSSP CVS reads in this again, stock CVS ignores it)
-    open(FP, ">$logfile") || die "cannot open message file `$logfile' for writing";
-    print FP $data;
-    close(FP);
+    #  (CVS with RSE patches reads in this again, stock CVS ignores it)
+    $io = new IO::File ">$logfile"
+        || die "cannot open message file `$logfile' for writing";
+    $io->print($data);
+    $io->close;
 
     #   nuke possibly existing editor backup files
-    unlink("${logfile}~");    
+    unlink("${logfile}~");
     unlink("${logfile}.bak");
 
     return $rv;
@@ -1307,7 +1364,7 @@
     #   determine whether we are the last call
     my $islastcall = ($RT->{cvsop} eq 'import' ? 1 : 0);
     if (-f "$RT->{tmpfile}.lastdir") {
-        my ($lastdir) = &do_file('read', "$RT->{tmpfile}.lastdir", ''); 
+        my ($lastdir) = &do_file('read', "$RT->{tmpfile}.lastdir", '');
         $islastcall = 1 if ($lastdir eq $cvsdir);
     }
 
@@ -1341,7 +1398,7 @@
 #   collect the information
 sub hook_loginfo_collect {
     my ($PA, $RT, $CF) = @_;
-    
+
     #   take the arguments
     my ($cvsdir, @cvsinfo) = split(/\s+/, $PA->{ARG}->[0]);
 
@@ -1354,14 +1411,14 @@
 
     #   handle special invocation under `cvs add <dir>'
     if (join(' ', @cvsinfo) eq '- New directory') { # see CVS' src/add.c
-        #   Hmmm... we always just deal with files in Shiela, so there
+        #   Hmmm... we always just deal with files in OSSP shiela, so there
         #   is no obvious and consistent way to deal now with only a
         #   plain directory. And there is also no log message provided
         #   by CVS. Additionally, creating empty directories in the CVS
         #   repository doesn't harm anyone. A regular cronjob is usually
         #   used to get rid of them anyway. So we decided to not log
         #   `cvs add <dir>' commands at all. We are early in processing
-        #   it is acceptable to just exit Shiela immediately.
+        #   it is acceptable to just exit OSSP shiela immediately.
         exit(0);
     }
 
@@ -1375,7 +1432,7 @@
         #   N = new file
         #   U = updated w/o conflict
         #   C = updated w/  conflict
-        #   T = touched/tagged only (OSSP extension)
+        #   T = touched/tagged only (RSE extension)
         $RT->{cvsop} = 'import';
         @cvsinfo = ();
         $cvsmsg =~ s|Status:\n+Vendor Tag:\s+(\S+).*?\nRelease Tags:\s+(.+?)\s*\n(.+)$||s;
@@ -1400,15 +1457,16 @@
                     #  provided a temporary working area on the server
                     #  side for us. Now we can only hope the CVS version
                     #  is at least capable of server communications...
-                    print STDERR "cvs import: Warning: Shiela cannot process local imports\n";
+                    print STDERR "cvs import: Warning: OSSP shiela cannot process local imports\n";
                     print STDERR "cvs import: if the CVS version isn't at least capable of\n";
                     print STDERR "cvs import: server communications (which we're forced to use).\n";
                     print STDERR "cvs import: Ignoring this operation - don't expect log messages!\n";
                     exit(0);
                 }
-                open(CVSS, "$RT->{cvs} -f -l -Q -n log -r$It '$Is'|");
-                $rcslog = $_ while (<CVSS>);
-                close(CVSS);
+                my $io = new IO::File "$RT->{cvs} -f -l -Q -n log -r$It '$Is'|"
+                    || die "unable to open CVS command pipe for reading";
+                $rcslog = $_ while (<$io>);
+                $io->close;
             }
             else {
                 my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
@@ -1453,8 +1511,8 @@
     &do_file('write', "$RT->{tmpfile}.log", '', $cvslog);
 
     #   if we are using a stock CVS version, we have to determine
-    #   extra information (which an OSSP CVS version would provide).
-    if (not $RT->{cvsossp} and not $RT->{cvsop} eq 'import') {
+    #   extra information (which an RSE CVS version would provide).
+    if (not $RT->{cvsrse} and not $RT->{cvsop} eq 'import') {
 
         #   parse CVS commit information
         my $tag = 'HEAD';
@@ -1479,7 +1537,7 @@
             if ($line =~ m/^Modified Files/) { $state = 'M'; next; }
             if ($line =~ m/^Removed Files/)  { $state = 'R'; next; }
             if ($line =~ m/^Log Message/)    { $state = '-'; next; }
-            
+
             if ($state =~ m/^[AMR]$/) {
                 my $file;
                 foreach $file (split(/\s+/, $line)) {
@@ -1492,7 +1550,7 @@
         my @newinfo = ();
         foreach my $info (@cvsinfo) {
             $info =~ m|^([^,]+),([^,]+),([^,]+)|
-                || die "invalid loginfo argument `$info'";
+                || die "invalid loginfo argument `$info' while extending stock CVS information";
             my ($Is, $IV, $Iv) = ($1, $2, $3);
 
             my $It = '';
@@ -1512,19 +1570,45 @@
     my @newinfo = ();
     foreach my $info (@cvsinfo) {
         $info =~ m|^([^,]+),([^,]+),([^,]+),([^,]*),([^,]*)$|
-            || die "invalid loginfo argument `$info'";
+            || die "invalid loginfo argument `$info' while extending summary information";
         my ($Is, $IV, $Iv, $It, $Io) = ($1, $2, $3, $4, $5);
 
         #   fix branch/tag and accumulate information
         $It = 'HEAD' if ($It eq '');
 
-        #   read file log entry   
+        #   manually determine next revision number for removed files
+        #   by fetching the whole revision log and extracting the next
+        #   number.
+        if ($Io eq 'R' and $Iv eq 'NONE') {
+            my $rcslog ='';
+            if (not $RT->{useserver}) {
+                my $io = new IO::File "$RT->{cvs} -f -l -Q -n log '$Is'|"
+                    || die "unable to open CVS command pipe for reading";
+                $rcslog .= $_ while (<$io>);
+                $io->close;
+            }
+            else {
+                my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
+                $cvs->directory($cvsdir);
+                $cvs->entry($Is);
+                $cvs->arguments($Is);
+                $cvs->send("log");
+                $rcslog = scalar $cvs->result;
+                $cvs->close;
+            }
+            if ($rcslog =~ m|^head:\s+([\d.]+)|m) {
+                $Iv = $1;
+            }
+        }
+
+        #   read file log entry
         my $rcslog = '';
-        if ($Io eq 'A' or $Io eq 'M') {
+        if ($Io eq 'A' or $Io eq 'M' or $Io eq 'R') {
             if (not $RT->{useserver}) {
-                open(CVSS, "$RT->{cvs} -f -l -Q -n log -r$Iv '$Is'|");
-                $rcslog .= $_ while (<CVSS>);
-                close(CVSS);
+                my $io = new IO::File "$RT->{cvs} -f -l -Q -n log -r$Iv '$Is'|"
+                    || die "unable to open CVS command pipe for reading";
+                $rcslog .= $_ while (<$io>);
+                $io->close;
             }
             else {
                 my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
@@ -1552,15 +1636,15 @@
 
         #   determine change delta
         my $Id = '+0/-0';
-        if ($Ik eq 'b' or -B $Is) { 
+        if ($Ik eq 'b' or -B $Is) {
             $Id = 'BLOB';
         }
         else {
             if ($Io eq 'A') {
-                open(FP, "<$Is"); 
+                my $io = new IO::File "<$Is" || die "unable open $Is for reading";
                 my $l = 0;
-                $l++ while (<FP>);
-                close(FP);
+                $l++ while (<$io>);
+                $io->close;
                 $Id = sprintf("+%d/-%d", $l, 0);
             }
             elsif ($Io eq 'M') {
@@ -1583,100 +1667,168 @@
             }
         }
 
-        #   determine change diff
+        #   determine change difference summary
         if ($Io eq 'A') {
-            #   file was added, so we show the whole contents
-            if ($Ik eq 'b' or -B $Is) { 
-                #   file seems to be a binary file
-                $cvsdiff .= 
-                    "<Diff $cvsdir/$Is>\n" .
-                    "Index: $cvsdir/$Is\n" .
-                    "============================================================\n" .
-                    "\$ cvs update -p -r$Iv $Is | uuencode $Is\n";
-                if (not $RT->{useserver}) {
-                    open(CVSS, "$RT->{cvs} -f -l -Q -n update -p -r$Iv '$Is' | uuencode '$Is' |");
-                    $cvsdiff .= $_ while (<CVSS>);
-                    close(CVSS);
-                }
-                else {
-                    my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
-                    $cvs->directory($cvsdir);
-                    $cvs->entry($Is);
-                    $cvs->arguments("-p", "-r$Iv", $Is);
-                    $cvs->send("update");
-                    $cvsdiff .= scalar $cvs->result;
-                    $cvs->close;
-                }
-                $cvsdiff .= "</Diff>\n";
+            ##
+            ##   ADDED FILE
+            ##
+
+            #   retrieve whole file contents
+            unlink("$RT->{tmpfile}.all");
+            my $io = new IO::File ">$RT->{tmpfile}.all"
+                || die "unable to open temporary file $RT->{tmpfile}.all for writing";
+            if (not $RT->{useserver}) {
+                my $cvs = new IO::File "$RT->{cvs} -f -l -Q -n update -p -r$Iv '$Is'|"
+                    || die "unable to open CVS command pipe for reading";
+                $io->print($_) while (<$cvs>);
+                $cvs->close;
             }
             else {
-                #   file seems to be a regular text file
-                $cvsdiff .= 
-                    "<Diff $cvsdir/$Is>\n" .
-                    "Index: $cvsdir/$Is\n" .
-                    "============================================================\n" .
-                    "\$ cvs update -p -r$Iv $Is\n";
-                if (not $RT->{useserver}) {
-                    open(CVSS, "$RT->{cvs} -f -l -Q -n update -p -r$Iv '$Is'|");
-                    $cvsdiff .= $_ while (<CVSS>);
-                    close(CVSS);
+                my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
+                $cvs->directory($cvsdir);
+                $cvs->entry($Is);
+                $cvs->arguments("-p", "-r$Iv", $Is);
+                $cvs->send("update");
+                $io->print(scalar $cvs->result);
+                $cvs->close;
+            }
+            $io->close;
+
+            if ($Ik eq 'b' or -B $Is) {
+                #   generate binary change patch script
+                if ($RT->{xdelta} and $RT->{uuencode}) {
+                    $cvsdiff .=
+                        "<shiela:patch $cvsdir/$Is>\n" .
+                        "(cd $cvsdir && uudecode <<'@@ .' && \\\n" .
+                        " xdelta patch $Is.xdelta /dev/null $Is && \\\n" .
+                        " rm -f $Is.xdelta)\n" .
+                        "Index: $cvsdir/$Is\n" .
+                        ("=" x 76) . "\n";
+                    unlink("$RT->{tmpfile}.null");
+                    unlink("$RT->{tmpfile}.xdelta");
+                    my $io = new IO::File ">$RT->{tmpfile}.null"
+                        || die "unable to open temporary file $RT->{tmpfile}.null for writing";
+                    $io->close;
+                    system("$RT->{xdelta} delta $RT->{tmpfile}.null " .
+                           "$RT->{tmpfile}.all $RT->{tmpfile}.xdelta >/dev/null 2>&1");
+                    $io = new IO::File "uuencode $RT->{tmpfile}.xdelta $Is.xdelta |"
+                        || die "unable to open uuencode command pipe for reading";
+                    $cvsdiff .= $_ while (<$io>);
+                    $io->close;
+                    $cvsdiff .= "@@ .\n";
+                    $cvsdiff .= "</shiela:patch>\n";
+                    unlink("$RT->{tmpfile}.null");
+                    unlink("$RT->{tmpfile}.xdelta");
                 }
-                else {
-                    my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
-                    $cvs->directory($cvsdir);
-                    $cvs->entry($Is);
-                    $cvs->arguments("-p", "-r$Iv", $Is);
-                    $cvs->send("update");
-                    $cvsdiff .= scalar $cvs->result;
-                    $cvs->close;
+            }
+            else {
+                #   generate textual change patch script
+                if ($RT->{diff}) {
+                    $cvsdiff .=
+                        "<shiela:patch $cvsdir/$Is>\n" .
+                        "patch -p0 <<'@@ .'\n" .
+                        "Index: $cvsdir/$Is\n" .
+                        ("=" x 76) . "\n" .
+                        "\$ cvs diff -u -r0 -r$Iv $Is\n";
+                    my $diff = '';
+                    my $io = new IO::File "$RT->{diff} -u /dev/null $RT->{tmpfile}.all|"
+                        || die "unable to open CVS command pipe for reading";
+                    $diff .= $_ while (<$io>);
+                    $io->close;
+                    my $Is_quoted = quotemeta("$RT->{tmpfile}.all");
+                    $diff =~ s|^(\+\+\+\s+)$Is_quoted|$1$Is|m;
+                    $cvsdiff .= $diff;
+                    $cvsdiff .= "@@ .\n";
+                    $cvsdiff .= "</shiela:patch>\n";
                 }
-                $cvsdiff .= "</Diff>\n";
             }
+
+            #   cleanup
+            unlink("$RT->{tmpfile}.all");
         }
         elsif ($Io eq 'M') {
-            if ($Ik eq 'b' or -B $Is) { 
-                #   file seems to be a binary file
-                $cvsdiff .= 
-                    "<Diff $cvsdir/$Is>\n" .
-                    "Index: $cvsdir/$Is\n" .
-                    "============================================================\n" .
-                    "\$ cvs update -p -r$IV $Is >$Is.old\n" .
-                    "\$ cvs update -p -r$Iv $Is >$Is.new\n" .
-                    "\$ diff -u $Is.old $Is.new\n";
-                if (not $RT->{useserver}) {
-                    system("$RT->{cvs} -f -l -Q -n update -p -r$IV '$Is' | uuencode '$Is' >$Is.old");
-                    system("$RT->{cvs} -f -l -Q -n update -p -r$Iv '$Is' | uuencode '$Is' >$Is.new");
-                }
-                else {
-                    my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
-                    $cvs->directory($cvsdir);
-                    $cvs->entry($Is);
-                    $cvs->arguments("-p", "-r$IV", $Is);
-                    $cvs->send("update");
-                    my $data = scalar $cvs->result;
-                    open(FP, ">$Is.old") || die "cannot write to $Is.old";
-                    print FP $data;
-                    close(FP);
-                    $cvs->arguments("-p", "-r$Iv", $Is);
-                    $cvs->send("update");
-                    $data = scalar $cvs->result;
-                    open(FP, ">$Is.new") || die "cannot write to $Is.old";
-                    print FP $data;
-                    close(FP);
-                    $cvs->close;
+            ##
+            ##   MODIFIED FILE
+            ##
+
+            if ($Ik eq 'b' or -B $Is) {
+                #   generate binary change patch script
+
+                if ($RT->{xdelta} and $RT->{uuencode}) {
+                    #   retrieve whole file contents (old revision)
+                    unlink("$RT->{tmpfile}.old");
+                    my $io = new IO::File ">$RT->{tmpfile}.old"
+                        || die "unable to open temporary file $RT->{tmpfile}.old for writing";
+                    if (not $RT->{useserver}) {
+                        my $cvs = new IO::File "$RT->{cvs} -f -l -Q -n update -p -r$IV '$Is'|"
+                            || die "unable to open CVS command pipe for reading";
+                        $io->print($_) while (<$cvs>);
+                        $cvs->close;
+                    }
+                    else {
+                        my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
+                        $cvs->directory($cvsdir);
+                        $cvs->entry($Is);
+                        $cvs->arguments("-p", "-r$IV", $Is);
+                        $cvs->send("update");
+                        $io->print(scalar $cvs->result);
+                        $cvs->close;
+                    }
+                    $io->close;
+
+                    #   retrieve whole file contents (new revision)
+                    unlink("$RT->{tmpfile}.new");
+                    $io = new IO::File ">$RT->{tmpfile}.new"
+                        || die "unable to open temporary file $RT->{tmpfile}.new for writing";
+                    if (not $RT->{useserver}) {
+                        my $cvs = new IO::File "$RT->{cvs} -f -l -Q -n update -p -r$Iv '$Is'|"
+                            || die "unable to open CVS command pipe for reading";
+                        $io->print($_) while (<$cvs>);
+                        $cvs->close;
+                    }
+                    else {
+                        my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
+                        $cvs->directory($cvsdir);
+                        $cvs->entry($Is);
+                        $cvs->arguments("-p", "-r$Iv", $Is);
+                        $cvs->send("update");
+                        $io->print(scalar $cvs->result);
+                        $cvs->close;
+                    }
+                    $io->close;
+
+                    #   generate change patch script
+                    $cvsdiff .=
+                        "<shiela:patch $cvsdir/$Is>\n" .
+                        "(cd $cvsdir && uudecode <<'@@ .' && \\\n" .
+                        " mv $Is $Is.orig && xdelta patch $Is.xdelta $Is.orig $Is && \\\n" .
+                        " rm -f $Is.orig $Is.xdelta)\n" .
+                        "Index: $cvsdir/$Is\n" .
+                        ("=" x 76) . "\n";
+                    unlink("$RT->{tmpfile}.xdelta");
+                    system("$RT->{xdelta} delta $RT->{tmpfile}.old " .
+                           "$RT->{tmpfile}.new $RT->{tmpfile}.xdelta >/dev/null 2>&1");
+                    $io = new IO::File "uuencode $RT->{tmpfile}.xdelta $Is.xdelta |"
+                        || die "unable to open uuencode command pipe for reading";
+                    $cvsdiff .= $_ while (<$io>);
+                    $io->close;
+                    $cvsdiff .= "@@ .\n";
+                    $cvsdiff .= "</shiela:patch>\n";
+                    unlink("$RT->{tmpfile}.xdelta");
+
+                    #   cleanup
+                    unlink("$RT->{tmpfile}.old");
+                    unlink("$RT->{tmpfile}.new");
                 }
-                open(FP, "diff -u $Is.old $Is.new|");
-                $cvsdiff .= $_ while (<FP>);
-                close(FP);
-                $cvsdiff .= "</Diff>\n";
             }
             else {
-                #   file was modified, so we show the changed contents only
+                #   generate textual change patch script
                 my $d = '';
                 if (not $RT->{useserver}) {
-                    open(FP, "$RT->{cvs} -f -l -Q -n diff -u -r$IV -r$Iv '$Is'|");
-                    $d .= $_ while (<FP>);
-                    close(FP);
+                    my $io = new IO::File "$RT->{cvs} -f -l -Q -n diff -u -r$IV -r$Iv '$Is'|"
+                        || die "unable to open CVS command pipe for reading";
+                    $d .= $_ while (<$io>);
+                    $io->close;
                 }
                 else {
                     my $cvs = new CVS ($RT->{cvs}, $RT->{cvsroot});
@@ -1692,13 +1844,30 @@
                 $d =~ s|^(---\s+)${Is_quoted}(\s+)|$1$cvsdir/$Is$2|m;
                 $d =~ s|^(\+\+\+\s+)${Is_quoted}(\s+)|$1$cvsdir/$Is$2|m;
                 $cvsdiff .=
-                    "<Diff $cvsdir/$Is>\n" .
-                     "Index: $cvsdir/$Is\n" .
-                     "============================================================\n" .
-                     "\$ cvs diff -u -r$IV -r$Iv $Is\n" .
-                     $d .
-                     "</Diff>\n";
-            }
+                    "<shiela:patch $cvsdir/$Is>\n" .
+                    "patch -p0 <<'@@ .'\n" .
+                    "Index: $cvsdir/$Is\n" .
+                    ("=" x 76) . "\n" .
+                    "\$ cvs diff -u -r$IV -r$Iv $Is\n" .
+                    $d .
+                    "@@ .\n" .
+                    "</shiela:patch>\n";
+            }
+        }
+        elsif ($Io eq 'R') {
+            ##
+            ##   REMOVED FILE
+            ##
+
+            #   generate binary and textaual change patch script
+            $cvsdiff .=
+                "<shiela:patch $cvsdir/$Is>\n" .
+                "rm -f $cvsdir/$Is <<'@@ .'\n" .
+                "Index: $cvsdir/$Is\n" .
+                ("=" x 76) . "\n" .
+                "[NO CHANGE SUMMARY BECAUSE FILE AS A WHOLE IS JUST REMOVED]\n" .
+                "@@ .\n" .
+                "</shiela:patch>\n";
         }
 
         $info = "$cvsdir/$Is,$IV,$Iv,$It,$Io,$Ik,$ID,$Id";
@@ -1752,7 +1921,7 @@
     my $handle_max = undef;
     foreach my $cvsinfo (@cvsinfo) {
         $cvsinfo =~ m|^([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+)$|
-             || die "invalid loginfo argument `$cvsinfo'";
+             || die "invalid loginfo argument `$cvsinfo' while accumulating information";
         my ($Is, $IV, $Iv, $It, $Io, $Ik, $ID, $Id) = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
         my $e = {};
         $e->{oldrev} = $IV;
@@ -1764,16 +1933,16 @@
         $e->{delta}  = $Id;
         $e->{diff} = '';
         my $Is_quoted = quotemeta($Is);
-        $cvsdiff =~ s|\n<Diff\s+${Is_quoted}>\n(.+?\n)</Diff>|$e->{diff} = $1, ''|se;
+        $cvsdiff =~ s|\n<shiela:patch\s+${Is_quoted}>\n(.+?\n)</shiela:patch>|$e->{diff} = $1, ''|se;
         $IN->{file}->{$Is} = $e;
-        $handle_min = $ID if ($ID ne '' and (not defined($handle_min) or $handle_min > $ID));
-        $handle_max = $ID if ($ID ne '' and (not defined($handle_max) or $handle_max < $ID));
+        $handle_min = $ID if ($ID ne '' and $ID ne '0' and (not defined($handle_min) or $handle_min > $ID));
+        $handle_max = $ID if ($ID ne '' and $ID ne '0' and (not defined($handle_max) or $handle_max < $ID));
     }
     $IN->{handle} = '-NONE-';
     if (defined($handle_min) and defined($handle_max)) {
         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($handle_min);
         $IN->{handle} = sprintf("%04d%02d%02d%02d%02d%02d%02d",
-                                1900+$year, $mon+1, $mday, $hour, $min, $sec, 
+                                1900+$year, $mon+1, $mday, $hour, $min, $sec,
                                 $handle_max - $handle_min);
     }
     return $IN;
@@ -1823,11 +1992,19 @@
                 my $subject = "[CVS]";
                 $subject .= " ".$CF->{Project}->{Tag}.":";
                 my $dirlast = '';
+                my $branchlast = '';
                 foreach my $path (sort(keys(%{$IN->{file}}))) {
                     my ($dir, $file) = ($path =~ m|^(.+)/([^/]+)$|);
+                    my $branch = $IN->{file}->{$path}->{branch} || 'HEAD';
+                    if ($branchlast ne $branch) {
+                        #   prefix with branch
+                        $branchlast = $branch;
+                        $subject .= " $branch:" if ($branch ne 'HEAD');
+                    }
                     if ($dirlast ne $dir) {
+                        #   prefix with directory
                         $dirlast = $dir;
-                        $subject .= " $dir";
+                        $subject .= " $dir/";
                     }
                     $subject .= " $file";
                 }
@@ -1835,9 +2012,11 @@
                 print "cvs commit: Mailing commit message to <$logurl>\n";
                 my $sm = new Sendmail ($RT, $logurl);
                 $sm->header('Subject', $subject);
-                if ($CF->{Logging}->{Report}->{$logtype}->{Details} eq 'diff:mime') {
-                    $sm->header('Content-Type', 
-                                "multipart/mixed; boundary=\"".$RT->{mimeboundary}."\"");
+                if (defined($CF->{Logging}->{Report}->{$logtype}->{Details})) {
+                    if ($CF->{Logging}->{Report}->{$logtype}->{Details} eq 'patch:mime') {
+                        $sm->header('Content-Type',
+                                    "multipart/mixed; boundary=\"".$RT->{mimeboundary}."\"");
+                    }
                 }
                 $sm->body($logmsg);
                 $sm->close;
@@ -1847,9 +2026,9 @@
                 my $logmsg = &produce_log_message($PA, $RT, $CF, $IN, $logtype, @files);
                 $logurl = $RT->{cvsroot}."/".$logurl if ($logurl !~ m|^/|);
                 print "cvs commit: Writing commit message to $logurl\n";
-                open(LOG, ">>$logurl") || die "cannot append log message to `$logurl'";
-                print LOG $logmsg;
-                close(LOG);
+                my $io = new IO::File ">>$logurl" || die "cannot append log message to `$logurl'";
+                $io->print($logmsg);
+                $io->close;
             }
         }
     }
@@ -1859,10 +2038,10 @@
 sub produce_log_message {
     my ($PA, $RT, $CF, $IN, $type, @files) = @_;
 
-    #   
+    #
     #   Parse out more details.
     #
-    my $cvslist = {}; 
+    my $cvslist = {};
     my %cvsmodules = ();
     my %cvsbranches = ();
     my $file;
@@ -1898,7 +2077,7 @@
 
     my $RP = $CF->{Logging}->{Report}->{$type} || die "No report of type `$type' defined";
     my $prefix = $RP->{Prefix} || '';
-    my $style = $RP->{Details} || 'diff:plain';
+    my $style = $RP->{Details} || 'patch:plain';
     my $O = '';
     foreach my $content (@{$RP->{Content}}) {
 
@@ -1927,7 +2106,7 @@
                 $prefix . sprintf("%-40s %s\n", "Module: ".$IN->{cvsmodule},          "Date:   ".$date) .
                 $prefix . sprintf("%-40s %s\n", "Branch: ".$IN->{cvsbranch},          "Handle: ".$IN->{handle});
         }
-        
+
         #   the file list
         elsif ($content eq 'files') {
             $O .= "\n";
@@ -1999,7 +2178,10 @@
             $O .= "\n";
             $O .= $prefix."Log:\n";
             my $log = $IN->{log};
-            $log =~ s|^|$prefix  |mg;
+            if ($log !~ m|\n.+|s and length($log) > 70) {
+                $log = &wrap_message(70, $log);
+            }
+            $log =~ s|^|${prefix}  |mg;
             $O .= $log;
         }
 
@@ -2010,13 +2192,13 @@
             $O .= $prefix."  Revision    Changes     Path\n";
             foreach $file (sort(keys(%{$IN->{file}}))) {
                 my ($op, $rev, $delta) = ($IN->{file}->{$file}->{op},
-                                          $IN->{file}->{$file}->{newrev}, 
+                                          $IN->{file}->{$file}->{newrev},
                                           $IN->{file}->{$file}->{delta});
                 next if ($op eq 'T');
                 if ($delta =~ m|^(.+)/(.+)$|) {
                     $delta = sprintf("%-3s %-3s", $1, $2);
                 }
-                $O .= $prefix.sprintf("  %-12s%-12s%s\n", $rev, $delta, $file);
+                $O .= $prefix . sprintf("  %-12s%-12s%s\n", $rev, $delta, $file);
             }
         }
 
@@ -2036,7 +2218,7 @@
                     $O .= "$prefix$url\n";
                 }
             }
-            elsif ($style eq 'diff:plain') {
+            elsif ($style eq 'patch:plain') {
                 foreach $file (sort(keys(%{$IN->{file}}))) {
                     next if ($IN->{file}->{$file}->{op} eq 'T');
                     my $diff = $IN->{file}->{$file}->{diff};
@@ -2044,7 +2226,7 @@
                     $O .= $diff;
                 }
             }
-            elsif ($style eq 'diff:mime') {
+            elsif ($style eq 'patch:mime') {
                 foreach $file (sort(keys(%{$IN->{file}}))) {
                     next if ($IN->{file}->{$file}->{op} eq 'T');
                     my $diff = $IN->{file}->{$file}->{diff};
@@ -2068,12 +2250,12 @@
     $O =~ s|\n+$|\n|s;
 
     #   MIME post-processing
-    if ($style eq 'diff:mime') {
+    if ($style eq 'patch:mime') {
         $O = "This is a multi-part message in MIME format.\n" .
              "--".$RT->{mimeboundary}."\n" .
              "Content-Type: text/plain; charset=iso-8859-1\n" .
              "Content-Transfer-Encoding: 8bit\n" .
-             "Content-Description: commit summary\n" .
+             "Content-Description: change summary\n" .
              "Content-Disposition: inline\n" .
              "\n" .
              $O .
@@ -2084,4 +2266,3 @@
     return $O;
 }
 
-##EOF##

CVSTrac 2.0.1