Add post-merge hook, related documentation, and tests.
[gitweb.git] / git-send-email.perl
index 69559b289a28acf7fd095260797535f62db8172e..4031e86b8688e62cc3a547ffd52dfc1b8234f0f4 100755 (executable)
 use Term::ReadLine;
 use Getopt::Long;
 use Data::Dumper;
+use Term::ANSIColor;
 use Git;
 
+$SIG{INT} = sub { print color("reset"), "\n"; exit };
+
 package FakeTerm;
 sub new {
        my ($class, $reason) = @_;
@@ -46,6 +49,9 @@ sub usage {
    --cc           Specify an initial "Cc:" list for the entire series
                   of emails.
 
+   --cc-cmd       Specify a command to execute per file which adds
+                  per file specific cc address entries
+
    --bcc          Specify a list of email addresses that should be Bcc:
                  on all the emails.
 
@@ -67,9 +73,18 @@ sub usage {
    --signed-off-cc Automatically add email addresses that appear in
                  Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
 
+   --identity     The configuration identity, a subsection to prioritise over
+                  the default section.
+
    --smtp-server  If set, specifies the outgoing SMTP server to use.
                   Defaults to localhost.
 
+   --smtp-user    The username for SMTP-AUTH.
+
+   --smtp-pass    The password for SMTP-AUTH.
+
+   --smtp-ssl     If set, connects to the SMTP server using SSL.
+
    --suppress-from Suppress sending emails to yourself if your address
                   appears in a From: line. Defaults to off.
 
@@ -139,7 +154,6 @@ sub format_2822_time {
 my (@to,@cc,@initial_cc,@bcclist,@xh,
        $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time);
 
-my $smtp_server;
 my $envelope_sender;
 
 # Example reply to:
@@ -157,24 +171,27 @@ sub format_2822_time {
 my ($quiet, $dry_run) = (0, 0);
 
 # Variables with corresponding config settings
-my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc);
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
+my ($smtp_server, $smtp_authuser, $smtp_authpass, $smtp_ssl);
+my ($identity, $aliasfiletype, @alias_files);
 
-my %config_settings = (
+my %config_bool_settings = (
     "thread" => [\$thread, 1],
     "chainreplyto" => [\$chain_reply_to, 1],
     "suppressfrom" => [\$suppress_from, 0],
     "signedoffcc" => [\$signed_off_cc, 1],
+    "smtpssl" => [\$smtp_ssl, 0],
 );
 
-foreach my $setting (keys %config_settings) {
-    my $config = $repo->config_bool("sendemail.$setting");
-    ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1];
-}
-
-@bcclist = $repo->config('sendemail.bcc');
-if (!@bcclist or !$bcclist[0]) {
-    @bcclist = ();
-}
+my %config_settings = (
+    "smtpserver" => \$smtp_server,
+    "smtpuser" => \$smtp_authuser,
+    "smtppass" => \$smtp_authpass,
+    "cccmd" => \$cc_cmd,
+    "aliasfiletype" => \$aliasfiletype,
+    "bcc" => \@bcclist,
+    "aliasesfile" => \@alias_files,
+);
 
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
@@ -187,8 +204,13 @@ sub format_2822_time {
                    "bcc=s" => \@bcclist,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
+                   "smtp-user=s" => \$smtp_authuser,
+                   "smtp-pass=s" => \$smtp_authpass,
+                   "smtp-ssl!" => \$smtp_ssl,
+                   "identity=s" => \$identity,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
+                   "cc-cmd=s" => \$cc_cmd,
                    "suppress-from!" => \$suppress_from,
                    "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
                    "dry-run" => \$dry_run,
@@ -200,6 +222,43 @@ sub format_2822_time {
     usage();
 }
 
+# Now, let's fill any that aren't set in with defaults:
+
+sub read_config {
+       my ($prefix) = @_;
+
+       foreach my $setting (keys %config_bool_settings) {
+               my $target = $config_bool_settings{$setting}->[0];
+               $$target = $repo->config_bool("$prefix.$setting") unless (defined $$target);
+       }
+
+       foreach my $setting (keys %config_settings) {
+               my $target = $config_settings{$setting};
+               if (ref($target) eq "ARRAY") {
+                       unless (@$target) {
+                               my @values = $repo->config("$prefix.$setting");
+                               @$target = @values if (@values && defined $values[0]);
+                       }
+               }
+               else {
+                       $$target = $repo->config("$prefix.$setting") unless (defined $$target);
+               }
+       }
+}
+
+# read configuration from [sendemail "$identity"], fall back on [sendemail]
+$identity = $repo->config("sendemail.identity") unless (defined $identity);
+read_config("sendemail.$identity") if (defined $identity);
+read_config("sendemail");
+
+# fall back on builtin bool defaults
+foreach my $setting (values %config_bool_settings) {
+       ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
+}
+
+my ($repoauthor) = $repo->ident_person('author');
+my ($repocommitter) = $repo->ident_person('committer');
+
 # Verify the user input
 
 foreach my $entry (@to) {
@@ -214,14 +273,7 @@ sub format_2822_time {
        die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
 }
 
-# Now, let's fill any that aren't set in with defaults:
-
-my ($repoauthor) = $repo->ident_person('author');
-my ($repocommitter) = $repo->ident_person('committer');
-
 my %aliases;
-my @alias_files = $repo->config('sendemail.aliasesfile');
-my $aliasfiletype = $repo->config('sendemail.aliasfiletype');
 my %parse_alias = (
        # multiline formats can be supported in the future
        mutt => sub { my $fh = shift; while (<$fh>) {
@@ -309,13 +361,11 @@ sub expand_aliases {
        } while (!defined $_);
 
        $initial_reply_to = $_;
-       $initial_reply_to =~ s/(^\s+|\s+$)//g;
+       $initial_reply_to =~ s/^\s+<?/</;
+       $initial_reply_to =~ s/>?\s+$/>/;
 }
 
-if (!$smtp_server) {
-       $smtp_server = $repo->config('sendemail.smtpserver');
-}
-if (!$smtp_server) {
+if (!defined $smtp_server) {
        foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
                if (-x $_) {
                        $smtp_server = $_;
@@ -428,10 +478,17 @@ sub extract_valid_address {
 
 # We'll setup a template for the message id, using the "from" address:
 
+my ($message_id_stamp, $message_id_serial);
 sub make_message_id
 {
-       my $date = time;
-       my $pseudo_rand = int (rand(4200));
+       my $uniq;
+       if (!defined $message_id_stamp) {
+               $message_id_stamp = sprintf("%s-%s", time, $$);
+               $message_id_serial = 0;
+       }
+       $message_id_serial++;
+       $uniq = "$message_id_stamp-$message_id_serial";
+
        my $du_part;
        for ($sender, $repocommitter, $repoauthor) {
                $du_part = extract_valid_address(sanitize_address($_));
@@ -441,8 +498,8 @@ sub make_message_id
                use Sys::Hostname qw();
                $du_part = 'user@' . Sys::Hostname::hostname();
        }
-       my $message_id_template = "<%s-git-send-email-$du_part>";
-       $message_id = sprintf $message_id_template, "$date$pseudo_rand";
+       my $message_id_template = "<%s-git-send-email-%s>";
+       $message_id = sprintf($message_id_template, $uniq, $du_part);
        #print "new message id = $message_id\n"; # Was useful for debugging
 }
 
@@ -545,8 +602,16 @@ sub send_message
                print $sm "$header\n$message";
                close $sm or die $?;
        } else {
-               require Net::SMTP;
-               $smtp ||= Net::SMTP->new( $smtp_server );
+               if ($smtp_ssl) {
+                       require Net::SMTP::SSL;
+                       $smtp ||= Net::SMTP::SSL->new( $smtp_server, Port => 465 );
+               }
+               else {
+                       require Net::SMTP;
+                       $smtp ||= Net::SMTP->new( $smtp_server );
+               }
+               $smtp->auth( $smtp_authuser, $smtp_authpass )
+                       or die $smtp->message if (defined $smtp_authuser);
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
@@ -652,11 +717,26 @@ sub send_message
                }
        }
        close F;
+
+       if (defined $cc_cmd) {
+               open(F, "$cc_cmd $t |")
+                       or die "(cc-cmd) Could not execute '$cc_cmd'";
+               while(<F>) {
+                       my $c = $_;
+                       $c =~ s/^\s*//g;
+                       $c =~ s/\n$//g;
+                       push @cc, $c;
+                       printf("(cc-cmd) Adding cc: %s from: '%s'\n",
+                               $c, $cc_cmd) unless $quiet;
+               }
+               close F
+                       or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
+       }
+
        if (defined $author) {
                $message = "From: $author\n\n$message";
        }
 
-
        send_message();
 
        # set up for the next message