--- /usr/lib/subversion/hook-scripts/commit-email.pl	2004-12-12 23:45:14.000000000 -0500
+++ /usr/local/lib/subversion/hook-scripts/commit-email.pl	2005-01-24 00:16:00.000000000 -0500
@@ -26,6 +26,9 @@
 # history and logs, available at http://subversion.tigris.org/.
 # ====================================================================
 
+# Modified by Branden Robinson, Peter Samuelson, and Mark Hymers
+# September 2004 to add $size_limit support.
+
 # Turn on warnings the best way depending on the Perl version.
 BEGIN {                                                                         
   if ( $] >= 5.006_000)                                                         
@@ -40,6 +43,9 @@
 ######################################################################
 # Configuration section.
 
+# Body of the message to be sent
+my @body;
+
 # Sendmail path.
 my $sendmail = "/usr/sbin/sendmail";
 
@@ -94,6 +100,7 @@
 
 # Use the reference to the first project to populate.
 my $current_project = $project_settings_list[0];
+my $size_limit = 0;
 
 # This hash matches the command line option to the hash key in the
 # project.  If a key exists but has a false value (''), then the
@@ -103,7 +110,8 @@
                        '-l'     => 'log_file',
                        '-m'     => '',
                        '-r'     => 'reply_to',
-                       '-s'     => 'subject_prefix');
+                       '-s'     => 'subject_prefix',
+                 '--size-limit' => '');
 
 while (@ARGV)
   {
@@ -128,14 +136,28 @@
           }
         else
           {
-            # Here handle -m.
-            unless ($arg eq '-m')
+            # Handle -m and --size-limit here.
+            if ($arg eq '-m')
+              {
+                $current_project                = &new_project;
+                $current_project->{match_regex} = $value;
+                push(@project_settings_list, $current_project);
+              }
+            elsif ($arg eq '--size-limit')
               {
-                die "$0: internal error: should only handle -m here.\n";
+                $size_limit = $value;
+                # Validate the specified diff size limit.
+                if ($size_limit ne '' and $size_limit ne -1 and $size_limit !~ /^\d+$/)
+                  {
+                    die "$0: --size-limit takes only a positive integer or -1"
+                        . " argument; \"$size_limit\" is neither\n";
+                  }
+              }
+            else
+              {
+                die "$0: internal error: should only handle -m and --size-limit"
+                    . "here.\n";
               }
-            $current_project                = &new_project;
-            $current_project->{match_regex} = $value;
-            push(@project_settings_list, $current_project);
           }
       }
     elsif ($arg =~ /^-/)
@@ -216,15 +238,22 @@
   or die "$0: cannot chdir `$tmp_dir': $!\n";
 
 # Get the author, date, and log from svnlook.
-my @svnlooklines = &read_from_process($svnlook, 'info', $repos, '-r', $rev);
+my ($lines_size, @svnlooklines) = &read_from_process(0, $svnlook, 'info',
+                                                     $repos, '-r', $rev);
 my $author = shift @svnlooklines;
 my $date = shift @svnlooklines;
 shift @svnlooklines;
 my @log = map { "$_\n" } @svnlooklines;
 
+# Add header to body
+push(@body, "Author: $author\n");
+push(@body, "Date: $date\n");
+push(@body, "New Revision: $rev\n");
+push(@body, "\n");
+
 # Figure out what directories have changed using svnlook.
-my @dirschanged = &read_from_process($svnlook, 'dirs-changed', $repos, 
-                                     '-r', $rev);
+my ($dirs_size, @dirschanged) = &read_from_process(0, $svnlook, 'dirs-changed',
+                                                   $repos, '-r', $rev);
 
 # Lose the trailing slash in the directory names if one exists, except
 # in the case of '/'.
@@ -242,7 +271,9 @@
   }
 
 # Figure out what files have changed using svnlook.
-@svnlooklines = &read_from_process($svnlook, 'changed', $repos, '-r', $rev);
+my $look_size;
+($look_size, @svnlooklines) = &read_from_process(0, $svnlook, 'changed', $repos,
+                                                 '-r', $rev);
 
 # Parse the changed nodes.
 my @adds;
@@ -275,10 +306,65 @@
       }
   }
 
-# Get the diff from svnlook.
-my @no_diff_deleted = $no_diff_deleted ? ('--no-diff-deleted') : ();
-my @difflines = &read_from_process($svnlook, 'diff', $repos,
-                                   '-r', $rev, @no_diff_deleted);
+# Add the adds, dels and mods to the body of the message.
+if (@adds)
+  {
+    @adds = sort @adds;
+    push(@body, "Added:\n");
+    push(@body, map { "   $_\n" } @adds);
+  }
+if (@dels)
+  {
+    @dels = sort @dels;
+    push(@body, "Removed:\n");
+    push(@body, map { "   $_\n" } @dels);
+  }
+if (@mods)
+  {
+    @mods = sort @mods;
+    push(@body, "Modified:\n");
+    push(@body, map { "   $_\n" } @mods);
+  }
+
+my @difflines;
+my $diff_howto = "Use \"svn diff" . " -r " . ($rev - 1) . ":$rev\" to view"
+                 . " diff.\n";
+
+# Work out how many bytes we have available for the diff.
+my $size_avail = 0;
+if ($size_limit > 0)
+  {
+    my $bodylen = 0;
+    for (@body) { $bodylen += length($_); }
+    $size_avail = $size_limit - $bodylen;
+
+    if ($size_avail <= 0)
+      {
+        @difflines = ( "Diff skipped; message reached limit of $size_limit"
+                       . " bytes with list of changed paths.\n$diff_howto" );
+      }
+}
+
+# A $size_limit of -1 means we do not include a diff.
+if ($size_limit ne -1)
+  {
+    # Get the diff from svnlook.
+    my @no_diff_deleted = $no_diff_deleted ? ('--no-diff-deleted') : ();
+    my $numbytes;
+    ($numbytes, @difflines) = &read_from_process($size_avail, $svnlook, 'diff',
+                                                 $repos, '-r', $rev,
+                                                 @no_diff_deleted);
+    # If the diff is larger than the remaining size limit, we must discard
+    # it.
+    if ($numbytes == -1) {
+      @difflines = ( "Including diff would make mail exceed size limit of"
+                     . " $size_limit bytes.\n$diff_howto" );
+      }
+  }
+else
+  {
+    @difflines = ( $diff_howto );
+  }
 
 ######################################################################
 # Modified directory name collapsing.
@@ -329,33 +415,6 @@
   }
 my $dirlist = join(' ', @dirschanged);
 
-######################################################################
-# Assembly of log message.
-
-# Put together the body of the log message.
-my @body;
-push(@body, "Author: $author\n");
-push(@body, "Date: $date\n");
-push(@body, "New Revision: $rev\n");
-push(@body, "\n");
-if (@adds)
-  {
-    @adds = sort @adds;
-    push(@body, "Added:\n");
-    push(@body, map { "   $_\n" } @adds);
-  }
-if (@dels)
-  {
-    @dels = sort @dels;
-    push(@body, "Removed:\n");
-    push(@body, map { "   $_\n" } @dels);
-  }
-if (@mods)
-  {
-    @mods = sort @mods;
-    push(@body, "Modified:\n");
-    push(@body, map { "   $_\n" } @mods);
-  }
 push(@body, "Log:\n");
 push(@body, @log);
 push(@body, "\n");
@@ -380,7 +439,6 @@
 
     my @email_addresses = @{$project->{email_addresses}};
     my $userlist        = join(' ', @email_addresses);
-    my $to              = join(', ', @email_addresses);
     my $from_address    = $project->{from_address};
     my $hostname        = $project->{hostname};
     my $log_file        = $project->{log_file};
@@ -412,7 +470,7 @@
       }
 
     my @head;
-    push(@head, "To: $to\n");
+    push(@head, "To: $userlist\n");
     push(@head, "From: $mail_from\n");
     push(@head, "Subject: $subject\n");
     push(@head, "Reply-to: $reply_to\n") if $reply_to;
@@ -492,6 +550,9 @@
       "  -m regex              Regular expression to match committed path\n",
       "  -r email_address      Email address for 'Reply-To:'\n",
       "  -s subject_prefix     Subject line prefix\n",
+      "  --size-limit limit    Message size limit in bytes (positive\n",
+      "                        integer); if message exceeds limit, diff is\n",
+      "                        omitted; if set to -1, diff is never sent\n",
       "\n",
       "This script supports a single repository with multiple projects,\n",
       "where each project receives email only for commits that modify that\n",
@@ -527,6 +588,10 @@
 }
 
 # Start a child process safely without using /bin/sh.
+#
+# We take a parameter, $limit, which if greater than zero will limit the
+# amount we read -- this is a hack to avoid OOM errors.   If we return
+# $read_size == -1, we exceeded the limit.
 sub safe_read_from_pipe
 {
   unless (@_)
@@ -534,6 +599,7 @@
       croak "$0: safe_read_from_pipe passed no arguments.\n";
     }
 
+  my $limit = shift @_;
   my $pid = open(SAFE_READ, '-|');
   unless (defined $pid)
     {
@@ -547,9 +613,17 @@
         or die "$0: cannot exec `@_': $!\n";
     }
   my @output;
+  my $read_size = 0;
   while (<SAFE_READ>)
     {
       s/[\r\n]+$//;
+      $read_size += length;
+      if (($limit > 0) and ($read_size > $limit))
+      {
+        $read_size = -1;
+        @output = ("output size exceeds specified limit of " . $limit);
+        last;
+      }
       push(@output, $_);
     }
   close(SAFE_READ);
@@ -563,7 +637,7 @@
     }
   if (wantarray)
     {
-      return ($result, @output);
+      return ($result, $read_size, @output);
     }
   else
     {
@@ -573,20 +647,20 @@
 
 # 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.
+# if it failed.  Returns number of bytes read and the output.
 sub read_from_process
 {
   unless (@_)
     {
       croak "$0: read_from_process passed no arguments.\n";
     }
-  my ($status, @output) = &safe_read_from_pipe(@_);
-  if ($status)
+  my ($status, $read_size, @output) = &safe_read_from_pipe(@_);
+  if ($read_size >= 0 and $status)
     {
       return ("$0: `@_' failed with this output:", @output);
     }
   else
     {
-      return @output;
+      return ($read_size, @output);
     }
 }

