[Fusionforge-commits] r8533 - trunk/gforge/plugins/svncommitemail/bin

Alain Peyrat aljeux at libremir.placard.fr.eu.org
Wed Dec 16 20:55:52 CET 2009


Author: aljeux
Date: 2009-12-16 20:55:52 +0100 (Wed, 16 Dec 2009)
New Revision: 8533

Modified:
   trunk/gforge/plugins/svncommitemail/bin/commit-email.pl
Log:
upgrade default commit-email.pl script in svncommitemail plugin

Modified: trunk/gforge/plugins/svncommitemail/bin/commit-email.pl
===================================================================
--- trunk/gforge/plugins/svncommitemail/bin/commit-email.pl	2009-12-15 16:10:23 UTC (rev 8532)
+++ trunk/gforge/plugins/svncommitemail/bin/commit-email.pl	2009-12-16 19:55:52 UTC (rev 8533)
@@ -27,21 +27,26 @@
 # ====================================================================
 
 # Turn on warnings the best way depending on the Perl version.
-BEGIN {                                                                         
-  if ( $] >= 5.006_000)                                                         
-    { require warnings; import warnings; }                
-  else                                                                          
-    { $^W = 1; }                                                  
-}                                                                               
-						
+BEGIN {
+  if ( $] >= 5.006_000)
+    { require warnings; import warnings; }
+  else
+    { $^W = 1; }
+}
+
 use strict;
 use Carp;
+my ($sendmail, $smtp_server);
 
 ######################################################################
 # Configuration section.
 
-# Sendmail path.
-my $sendmail = "/usr/sbin/sendmail";
+# Sendmail path, or SMTP server address.
+# You should define exactly one of these two configuration variables,
+# leaving the other commented out, to select which method of sending
+# email should be used.
+$sendmail = "/usr/sbin/sendmail";
+#$smtp_server = "127.0.0.1";
 
 # Svnlook path.
 my $svnlook = "/usr/bin/svnlook";
@@ -51,14 +56,23 @@
 # in the log and email messages by not printing the file, then set
 # $no_diff_deleted to 1.
 my $no_diff_deleted = 0;
+# By default, when a file is added to the repository, svnlook diff
+# prints the entire contents of the file.  If you want to save space
+# in the log and email messages by not printing the file, then set
+# $no_diff_added to 1.
+my $no_diff_added = 0;
 
-# Since the path to svnlook depends upon the local installation
-# preferences, check that the required programs exist to insure that
-# the administrator has set up the script properly.
+# End of Configuration section.
+######################################################################
+
+# Check that the required programs exist, and the email sending method
+# configuration is sane, to ensure that the administrator has set up
+# the script properly.
 {
   my $ok = 1;
   foreach my $program ($sendmail, $svnlook)
     {
+      next if not defined $program;
       if (-e $program)
         {
           unless (-x $program)
@@ -74,9 +88,16 @@
           $ok = 0;
         }
     }
+  if (not (defined $sendmail xor defined $smtp_server))
+    {
+      warn "$0: exactly one of \$sendmail or \$smtp_server must be ",
+           "set, edit $0.\n";
+      $ok = 0;
+    }
   exit 1 unless $ok;
 }
 
+require Net::SMTP if defined $smtp_server;
 
 ######################################################################
 # Initial setup/command-line handling.
@@ -86,12 +107,22 @@
 # implicit rule that matches all paths.
 my @project_settings_list = (&new_project);
 
-# Process the command line arguments till there are none left.  The
-# first two arguments that are not used by a command line option are
-# the repository path and the revision number.
+# Process the command line arguments till there are none left.
+# In commit mode: The first two arguments that are not used by a command line
+# option are the repository path and the revision number.
+# In revprop-change mode: The first four arguments that are not used by a
+# command line option are the repository path, the revision number, the
+# author, and the property name. This script has no support for the fifth
+# argument (action) added to the post-revprop-change hook in Subversion
+# 1.2.0 yet - patches welcome!
 my $repos;
 my $rev;
+my $author;
+my $propname;
 
+my $mode = 'commit';
+my $diff_file;
+
 # Use the reference to the first project to populate.
 my $current_project = $project_settings_list[0];
 
@@ -99,11 +130,14 @@
 # project.  If a key exists but has a false value (''), then the
 # command line option is allowed but requires special handling.
 my %opt_to_hash_key = ('--from' => 'from_address',
+                       '--revprop-change' => '',
+                       '-d'     => '',
                        '-h'     => 'hostname',
                        '-l'     => 'log_file',
                        '-m'     => '',
                        '-r'     => 'reply_to',
-                       '-s'     => 'subject_prefix');
+                       '-s'     => 'subject_prefix',
+                       '--diff' => '');
 
 while (@ARGV)
   {
@@ -116,11 +150,15 @@
             die "$0: command line option `$arg' is not recognized.\n";
           }
 
-        unless (@ARGV)
+        my $value;
+        if ($arg ne '--revprop-change')
           {
-            die "$0: command line option `$arg' is missing a value.\n";
+            unless (@ARGV)
+              {
+                die "$0: command line option `$arg' is missing a value.\n";
+              }
+            $value = shift @ARGV;
           }
-        my $value = shift @ARGV;
 
         if ($hash_key)
           {
@@ -128,20 +166,46 @@
           }
         else
           {
-            # Here handle -m.
-            unless ($arg eq '-m')
+            if ($arg eq '-m')
               {
-                die "$0: internal error: should only handle -m here.\n";
+                $current_project                = &new_project;
+                $current_project->{match_regex} = $value;
+                push(@project_settings_list, $current_project);
               }
-            $current_project                = &new_project;
-            $current_project->{match_regex} = $value;
-            push(@project_settings_list, $current_project);
+            elsif ($arg eq '-d')
+              {
+                if ($mode ne 'revprop-change')
+                  {
+                    die "$0: `-d' is valid only when used after"
+                      . " `--revprop-change'.\n";
+                  }
+                if ($diff_file)
+                  {
+                    die "$0: command line option `$arg'"
+                      . " can only be used once.\n";
+                  }
+                $diff_file = $value;
+              }
+            elsif ($arg eq '--revprop-change')
+              {
+                if (defined $repos)
+                  {
+                    die "$0: `--revprop-change' must be specified before"
+                      . " the first non-option argument.\n";
+                  }
+                $mode = 'revprop-change';
+              }
+            elsif ($arg eq '--diff')
+              {
+                $current_project->{show_diff} = parse_boolean($value);
+              }
+            else
+              {
+                die "$0: internal error:"
+                  . " should not be handling `$arg' here.\n";
+              }
           }
       }
-    elsif ($arg =~ /^-/)
-      {
-        die "$0: command line option `$arg' is not recognized.\n";
-      }
     else
       {
         if (! defined $repos)
@@ -152,6 +216,14 @@
           {
             $rev = $arg;
           }
+        elsif (! defined $author && $mode eq 'revprop-change')
+          {
+            $author = $arg;
+          }
+        elsif (! defined $propname && $mode eq 'revprop-change')
+          {
+            $propname = $arg;
+          }
         else
           {
             push(@{$current_project->{email_addresses}}, $arg);
@@ -159,9 +231,14 @@
       }
   }
 
-# If the revision number is undefined, then there were not enough
-# command line arguments.
-&usage("$0: too few arguments.") unless defined $rev;
+if ($mode eq 'commit')
+  {
+    &usage("$0: too few arguments.") unless defined $rev;
+  }
+elsif ($mode eq 'revprop-change')
+  {
+    &usage("$0: too few arguments.") unless defined $propname;
+  }
 
 # Check the validity of the command line arguments.  Check that the
 # revision is an integer greater than 0 and that the repository
@@ -206,24 +283,10 @@
   exit 1 unless $ok;
 }
 
-######################################################################
-# Harvest data using svnlook.
+# Harvest common data needed for both commit or revprop-change.
 
-# Change into /tmp so that svnlook diff can create its .svnlook
-# directory.
-my $tmp_dir = '/tmp';
-chdir($tmp_dir)
-  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 $author = shift @svnlooklines;
-my $date = shift @svnlooklines;
-shift @svnlooklines;
-my @log = map { "$_\n" } @svnlooklines;
-
 # Figure out what directories have changed using svnlook.
-my @dirschanged = &read_from_process($svnlook, 'dirs-changed', $repos, 
+my @dirschanged = &read_from_process($svnlook, 'dirs-changed', $repos,
                                      '-r', $rev);
 
 # Lose the trailing slash in the directory names if one exists, except
@@ -242,7 +305,7 @@
   }
 
 # Figure out what files have changed using svnlook.
- at svnlooklines = &read_from_process($svnlook, 'changed', $repos, '-r', $rev);
+my @svnlooklines = &read_from_process($svnlook, 'changed', $repos, '-r', $rev);
 
 # Parse the changed nodes.
 my @adds;
@@ -275,92 +338,154 @@
       }
   }
 
-# 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);
+# Declare variables which carry information out of the inner scope of
+# the conditional blocks below.
+my $subject_base;
+my @body;
+# $author - declared above for use as a command line parameter in
+#   revprop-change mode.  In commit mode, gets filled in below.
 
-######################################################################
-# Modified directory name collapsing.
+if ($mode eq 'commit')
+  {
+    ######################################################################
+    # Harvest data using svnlook.
 
-# Collapse the list of changed directories only if the root directory
-# was not modified, because otherwise everything is under root and
-# there's no point in collapsing the directories, and only if more
-# than one directory was modified.
-my $commondir = '';
-if (!$rootchanged and @dirschanged > 1)
-  {
-    my $firstline    = shift @dirschanged;
-    my @commonpieces = split('/', $firstline);
-    foreach my $line (@dirschanged)
+    # Get the author, date, and log from svnlook.
+    my @infolines = &read_from_process($svnlook, 'info', $repos, '-r', $rev);
+    $author = shift @infolines;
+    my $date = shift @infolines;
+    shift @infolines;
+    my @log = map { "$_\n" } @infolines;
+
+    ######################################################################
+    # Modified directory name collapsing.
+
+    # Collapse the list of changed directories only if the root directory
+    # was not modified, because otherwise everything is under root and
+    # there's no point in collapsing the directories, and only if more
+    # than one directory was modified.
+    my $commondir = '';
+    my @edited_dirschanged = @dirschanged;
+    if (!$rootchanged and @edited_dirschanged > 1)
       {
-        my @pieces = split('/', $line);
-        my $i = 0;
-        while ($i < @pieces and $i < @commonpieces)
+        my $firstline    = shift @edited_dirschanged;
+        my @commonpieces = split('/', $firstline);
+        foreach my $line (@edited_dirschanged)
           {
-            if ($pieces[$i] ne $commonpieces[$i])
+            my @pieces = split('/', $line);
+            my $i = 0;
+            while ($i < @pieces and $i < @commonpieces)
               {
-                splice(@commonpieces, $i, @commonpieces - $i);
-                last;
+                if ($pieces[$i] ne $commonpieces[$i])
+                  {
+                    splice(@commonpieces, $i, @commonpieces - $i);
+                    last;
+                  }
+                $i++;
               }
-            $i++;
           }
-      }
-    unshift(@dirschanged, $firstline);
+        unshift(@edited_dirschanged, $firstline);
 
-    if (@commonpieces)
-      {
-        $commondir = join('/', @commonpieces);
-        my @new_dirschanged;
-        foreach my $dir (@dirschanged)
+        if (@commonpieces)
           {
-            if ($dir eq $commondir)
+            $commondir = join('/', @commonpieces);
+            my @new_dirschanged;
+            foreach my $dir (@edited_dirschanged)
               {
-                $dir = '.';
+                if ($dir eq $commondir)
+                  {
+                    $dir = '.';
+                  }
+                else
+                  {
+                    $dir =~ s#^\Q$commondir/\E##;
+                  }
+                push(@new_dirschanged, $dir);
               }
-            else
-              {
-                $dir =~ s#^$commondir/##;
-              }
-            push(@new_dirschanged, $dir);
+            @edited_dirschanged = @new_dirschanged;
           }
-        @dirschanged = @new_dirschanged;
       }
-  }
-my $dirlist = join(' ', @dirschanged);
+    my $dirlist = join(' ', @edited_dirschanged);
 
-######################################################################
-# Assembly of log message.
+    ######################################################################
+    # 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 ($commondir ne '')
+      {
+        $subject_base = "r$rev - in $commondir: $dirlist";
+      }
+    else
+      {
+        $subject_base = "r$rev - $dirlist";
+      }
+
+    # Put together the body of the log message.
+    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");
   }
-if (@dels)
+elsif ($mode eq 'revprop-change')
   {
-    @dels = sort @dels;
-    push(@body, "Removed:\n");
-    push(@body, map { "   $_\n" } @dels);
+    ######################################################################
+    # Harvest data.
+
+    my @svnlines;
+    # Get the diff file if it was provided, otherwise the property value.
+    if ($diff_file)
+      {
+        open(DIFF_FILE, $diff_file) or die "$0: cannot read `$diff_file': $!\n";
+        @svnlines = <DIFF_FILE>;
+        close DIFF_FILE;
+      }
+    else
+      {
+        @svnlines = &read_from_process($svnlook, 'propget', '--revprop', '-r',
+                                       $rev, $repos, $propname);
+      }
+
+    ######################################################################
+    # Assembly of log message.
+
+    $subject_base = "propchange - r$rev $propname";
+
+    # Put together the body of the log message.
+    push(@body, "Author: $author\n");
+    push(@body, "Revision: $rev\n");
+    push(@body, "Property Name: $propname\n");
+    push(@body, "\n");
+    unless ($diff_file)
+      {
+        push(@body, "New Property Value:\n");
+      }
+    push(@body, map { /[\r\n]+$/ ? $_ : "$_\n" } @svnlines);
+    push(@body, "\n");
   }
-if (@mods)
-  {
-    @mods = sort @mods;
-    push(@body, "Modified:\n");
-    push(@body, map { "   $_\n" } @mods);
-  }
-push(@body, "Log:\n");
-push(@body, @log);
-push(@body, "\n");
-push(@body, map { /[\r\n]+$/ ? $_ : "$_\n" } @difflines);
 
+# Cached information - calculated when first needed.
+my @difflines;
+
 # Go through each project and see if there are any matches for this
 # project.  If so, send the log out.
 foreach my $project (@project_settings_list)
@@ -386,16 +511,9 @@
     my $log_file        = $project->{log_file};
     my $reply_to        = $project->{reply_to};
     my $subject_prefix  = $project->{subject_prefix};
-    my $subject;
+    my $subject         = $subject_base;
+    my $diff_wanted     = ($project->{show_diff} and $mode eq 'commit');
 
-    if ($commondir ne '')
-      {
-        $subject = "r$rev - in $commondir: $dirlist";
-      }
-    else
-      {
-        $subject = "r$rev - $dirlist";
-      }
     if ($subject_prefix =~ /\w/)
       {
         $subject = "$subject_prefix $subject";
@@ -410,6 +528,11 @@
       {
         $mail_from = "$mail_from\@$hostname";
       }
+    elsif (defined $smtp_server)
+      {
+        die "$0: use of either `-h' or `--from' is mandatory when ",
+            "sending email using direct SMTP.\n";
+      }
 
     my @head;
     push(@head, "To: $to\n");
@@ -424,22 +547,22 @@
     # Subject: Re: svn commit: rev 2599 - trunk/tools/cgi
     # To: dev at subversion.tigris.org
     # Date: Fri, 19 Jul 2002 23:42:32 -0700
-    # 
+    #
     # Well... that isn't strictly true. The contents of the files
     # might not be UTF-8, so the "diff" portion will be hosed.
-    # 
+    #
     # If you want a truly "proper" commit message, then you'd use
     # multipart MIME messages, with each file going into its own part,
     # and labeled with an appropriate MIME type and charset. Of
     # course, we haven't defined a charset property yet, but no biggy.
-    # 
+    #
     # Going with multipart will surely throw out the notion of "cut
     # out the patch from the email and apply." But then again: the
     # commit emailer could see that all portions are in the same
-    # charset and skip the multipart thang. 
-    # 
+    # charset and skip the multipart thang.
+    #
     # etc etc
-    # 
+    #
     # Basically: adding/tweaking the content-type is nice, but don't
     # think that is the proper solution.
     push(@head, "Content-Type: text/plain; charset=UTF-8\n");
@@ -447,13 +570,25 @@
 
     push(@head, "\n");
 
-    if ($sendmail =~ /\w/ and @email_addresses)
+    if ($diff_wanted and not @difflines)
       {
+        # Get the diff from svnlook.
+        my @no_diff_deleted = $no_diff_deleted ? ('--no-diff-deleted') : ();
+        my @no_diff_added = $no_diff_added ? ('--no-diff-added') : ();
+        @difflines = &read_from_process($svnlook, 'diff', $repos,
+                                        '-r', $rev, @no_diff_deleted,
+                                        @no_diff_added);
+        @difflines = map { /[\r\n]+$/ ? $_ : "$_\n" } @difflines;
+      }
+
+    if (defined $sendmail and @email_addresses)
+      {
         # Open a pipe to sendmail.
-        my $command = "$sendmail $userlist";
+        my $command = "$sendmail -f'$mail_from' $userlist";
         if (open(SENDMAIL, "| $command"))
           {
             print SENDMAIL @head, @body;
+            print SENDMAIL @difflines if $diff_wanted;
             close SENDMAIL
               or warn "$0: error in closing `$command' for writing: $!\n";
           }
@@ -462,6 +597,20 @@
             warn "$0: cannot open `| $command' for writing: $!\n";
           }
       }
+    elsif (defined $smtp_server and @email_addresses)
+      {
+        my $smtp = Net::SMTP->new($smtp_server);
+        handle_smtp_error($smtp, $smtp->mail($mail_from));
+        handle_smtp_error($smtp, $smtp->recipient(@email_addresses));
+        handle_smtp_error($smtp, $smtp->data());
+        handle_smtp_error($smtp, $smtp->datasend(@head, @body));
+        if ($diff_wanted)
+          {
+            handle_smtp_error($smtp, $smtp->datasend(@difflines));
+          }
+        handle_smtp_error($smtp, $smtp->dataend());
+        handle_smtp_error($smtp, $smtp->quit());
+      }
 
     # Dump the output to logfile (if its name is not empty).
     if ($log_file =~ /\w/)
@@ -469,6 +618,7 @@
         if (open(LOGFILE, ">> $log_file"))
           {
             print LOGFILE @head, @body;
+            print LOGFILE @difflines if $diff_wanted;
             close LOGFILE
               or warn "$0: error in closing `$log_file' for appending: $!\n";
           }
@@ -481,36 +631,58 @@
 
 exit 0;
 
+sub handle_smtp_error
+{
+  my ($smtp, $retval) = @_;
+  if (not $retval)
+    {
+      die "$0: SMTP Error: " . $smtp->message() . "\n";
+    }
+}
+
 sub usage
 {
   warn "@_\n" if @_;
-  die "usage: $0 REPOS REVNUM [[-m regex] [options] [email_addr ...]] ...\n",
-      "options are\n",
+  die "usage (commit mode):\n",
+      "  $0 REPOS REVNUM [[-m regex] [options] [email_addr ...]] ...\n",
+      "usage: (revprop-change mode):\n",
+      "  $0 --revprop-change REPOS REVNUM USER PROPNAME [-d diff_file] \\\n",
+      "    [[-m regex] [options] [email_addr ...]] ...\n",
+      "options are:\n",
       "  --from email_address  Email address for 'From:' (overrides -h)\n",
       "  -h hostname           Hostname to append to author for 'From:'\n",
       "  -l logfile            Append mail contents to this log file\n",
       "  -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",
+      "  --diff y|n            Include diff in message (default: y)\n",
+      "                        (applies to commit mode only)\n",
       "\n",
       "This script supports a single repository with multiple projects,\n",
-      "where each project receives email only for commits that modify that\n",
-      "project.  A project is identified by using the -m command line\n",
-      "with a regular expression argument.  If a commit has a path that\n",
-      "matches the regular expression, then the entire commit matches.\n",
-      "Any of the following -h, -l, -r and -s command line options and\n",
-      "following email addresses are associated with this project.  The\n",
-      "next -m resets the -h, -l, -r and -s command line options and the\n",
-      "list of email addresses.\n",
+      "where each project receives email only for actions that affect that\n",
+      "project.  A project is identified by using the -m command line\n".
+      "option with a regular expression argument.  If the given revision\n",
+      "contains modifications to a path that matches the regular\n",
+      "expression, then the action applies to the project.\n",
       "\n",
+      "Any of the following -h, -l, -r, -s and --diff command line options\n",
+      "and following email addresses are associated with this project.  The\n",
+      "next -m resets the -h, -l, -r, -s and --diff command line options\n",
+      "and the list of email addresses.\n",
+      "\n",
       "To support a single project conveniently, the script initializes\n",
       "itself with an implicit -m . rule that matches any modifications\n",
-      "to the repository.  Therefore, to use the script for a single\n",
-      "project repository, just use the other comand line options and\n",
+      "to the repository.  Therefore, to use the script for a single-\n",
+      "project repository, just use the other command line options and\n",
       "a list of email addresses on the command line.  If you do not want\n",
-      "a project that matches the entire repository, then use a -m with a\n",
+      "a rule that matches the entire repository, then use -m with a\n",
       "regular expression before any other command line options or email\n",
-      "addresses.\n";
+      "addresses.\n",
+      "\n",
+      "'revprop-change' mode:\n",
+      "The message will contain a copy of the diff_file if it is provided,\n",
+      "otherwise a copy of the (assumed to be new) property value.\n",
+      "\n";
 }
 
 # Return a new hash data structure for a new empty project that
@@ -523,9 +695,18 @@
           log_file        => '',
           match_regex     => '.',
           reply_to        => '',
-          subject_prefix  => ''};
+          subject_prefix  => '',
+          show_diff       => 1};
 }
 
+sub parse_boolean
+{
+  if ($_[0] eq 'y') { return 1; };
+  if ($_[0] eq 'n') { return 0; };
+
+  die "$0: valid boolean options are 'y' or 'n', not '$_[0]'\n";
+}
+
 # Start a child process safely without using /bin/sh.
 sub safe_read_from_pipe
 {




More information about the Fusionforge-commits mailing list