compat: Add simplified merge sort implementation from glibc
[gitweb.git] / git-send-email.perl
index b03297c9d7ed40f39b9f805c08bf9c2f17bb7379..a1a9d14b00d01844509a87c598314e2ffbbfa47c 100755 (executable)
@@ -73,11 +73,22 @@ 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.
+                  Defaults to localhost.  Port number can be specified here with
+                  hostname:port format or by using --smtp-server-port option.
+
+   --smtp-server-port Specify a port on the outgoing SMTP server to connect to.
+
+   --smtp-user    The username for SMTP-AUTH.
+
+   --smtp-pass    The password for SMTP-AUTH.
 
-   --suppress-from Suppress sending emails to yourself if your address
-                  appears in a From: line. Defaults to off.
+   --smtp-ssl     If set, connects to the SMTP server using SSL.
+
+   --suppress-from Suppress sending emails to yourself. Defaults to off.
 
    --thread       Specify that the "In-Reply-To:" header should be set on all
                   emails. Defaults to on.
@@ -89,6 +100,8 @@ sub usage {
 
    --envelope-sender   Specify the envelope sender used to send the emails.
 
+   --no-validate       Don't perform any sanity checks on patches.
+
 EOT
        exit(1);
 }
@@ -134,6 +147,7 @@ sub format_2822_time {
 
 my $have_email_valid = eval { require Email::Valid; 1 };
 my $smtp;
+my $auth;
 
 sub unique_email_list(@);
 sub cleanup_compose_files();
@@ -145,7 +159,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:
@@ -164,24 +177,29 @@ sub format_2822_time {
 
 # Variables with corresponding config settings
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
+my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_authpass, $smtp_ssl);
+my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
+my ($no_validate);
 
-my %config_settings = (
+my %config_bool_settings = (
     "thread" => [\$thread, 1],
     "chainreplyto" => [\$chain_reply_to, 1],
     "suppressfrom" => [\$suppress_from, 0],
     "signedoffcc" => [\$signed_off_cc, 1],
-    "cccmd" => [\$cc_cmd, ""],
+    "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,
+    "smtpserverport" => \$smtp_server_port,
+    "smtpuser" => \$smtp_authuser,
+    "smtppass" => \$smtp_authpass,
+    "to" => \@to,
+    "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:
@@ -194,6 +212,11 @@ sub format_2822_time {
                    "bcc=s" => \@bcclist,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
+                   "smtp-server-port=s" => \$smtp_server_port,
+                   "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,
@@ -202,12 +225,50 @@ sub format_2822_time {
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
                    "thread!" => \$thread,
+                   "no-validate" => \$no_validate,
         );
 
 unless ($rc) {
     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) {
@@ -222,14 +283,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>) {
@@ -264,6 +318,40 @@ sub format_2822_time {
 
 ($sender) = expand_aliases($sender) if defined $sender;
 
+# Now that all the defaults are set, process the rest of the command line
+# arguments and collect up the files that need to be processed.
+for my $f (@ARGV) {
+       if (-d $f) {
+               opendir(DH,$f)
+                       or die "Failed to opendir $f: $!";
+
+               push @files, grep { -f $_ } map { +$f . "/" . $_ }
+                               sort readdir(DH);
+
+       } elsif (-f $f) {
+               push @files, $f;
+
+       } else {
+               print STDERR "Skipping $f - not found.\n";
+       }
+}
+
+if (!$no_validate) {
+       foreach my $f (@files) {
+               my $error = validate_patch($f);
+               $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+       }
+}
+
+if (@files) {
+       unless ($quiet) {
+               print $_,"\n" for (@files);
+       }
+} else {
+       print STDERR "\nNo patch files specified!\n\n";
+       usage();
+}
+
 my $prompting = 0;
 if (!defined $sender) {
        $sender = $repoauthor || $repocommitter;
@@ -317,14 +405,13 @@ sub expand_aliases {
        } while (!defined $_);
 
        $initial_reply_to = $_;
-       $initial_reply_to =~ s/^\s+<?/</;
-       $initial_reply_to =~ s/>?\s+$/>/;
 }
-
-if (!$smtp_server) {
-       $smtp_server = $repo->config('sendemail.smtpserver');
+if (defined $initial_reply_to && $_ ne "") {
+       $initial_reply_to =~ s/^\s*<?/</;
+       $initial_reply_to =~ s/>?\s*$/>/;
 }
-if (!$smtp_server) {
+
+if (!defined $smtp_server) {
        foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
                if (-x $_) {
                        $smtp_server = $_;
@@ -351,7 +438,7 @@ sub expand_aliases {
        close(C);
 
        my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
-       system($editor, $compose_filename);
+       system('sh', '-c', '$0 $@', $editor, $compose_filename);
 
        open(C2,">",$compose_filename . ".final")
                or die "Failed to open $compose_filename.final : " . $!;
@@ -375,35 +462,7 @@ sub expand_aliases {
                exit(0);
        }
 
-       @files = ($compose_filename . ".final");
-}
-
-
-# Now that all the defaults are set, process the rest of the command line
-# arguments and collect up the files that need to be processed.
-for my $f (@ARGV) {
-       if (-d $f) {
-               opendir(DH,$f)
-                       or die "Failed to opendir $f: $!";
-
-               push @files, grep { -f $_ } map { +$f . "/" . $_ }
-                               sort readdir(DH);
-
-       } elsif (-f $f) {
-               push @files, $f;
-
-       } else {
-               print STDERR "Skipping $f - not found.\n";
-       }
-}
-
-if (@files) {
-       unless ($quiet) {
-               print $_,"\n" for (@files);
-       }
-} else {
-       print STDERR "\nNo patch files specified!\n\n";
-       usage();
+       @files = ($compose_filename . ".final", @files);
 }
 
 # Variables we set as part of the loop over files
@@ -511,7 +570,11 @@ sub sanitize_address
 sub send_message
 {
        my @recipients = unique_email_list(@to);
-       @cc = (map { sanitize_address($_) } @cc);
+       @cc = (grep { my $cc = extract_valid_address($_);
+                     not grep { $cc eq $_ } @recipients
+                   }
+              map { sanitize_address($_) }
+              @cc);
        my $to = join (",\n\t", @recipients);
        @recipients = unique_email_list(@recipients,@cc,@bcclist);
        @recipients = (map { extract_valid_address($_) } @recipients);
@@ -527,7 +590,7 @@ sub send_message
                $ccline = "\nCc: $cc";
        }
        my $sanitized_sender = sanitize_address($sender);
-       make_message_id();
+       make_message_id() unless defined($message_id);
 
        my $header = "From: $sanitized_sender
 To: $to${ccline}
@@ -563,8 +626,30 @@ sub send_message
                print $sm "$header\n$message";
                close $sm or die $?;
        } else {
-               require Net::SMTP;
-               $smtp ||= Net::SMTP->new( $smtp_server );
+
+               if (!defined $smtp_server) {
+                       die "The required SMTP server is not properly defined."
+               }
+
+               if ($smtp_ssl) {
+                       $smtp_server_port ||= 465; # ssmtp
+                       require Net::SMTP::SSL;
+                       $smtp ||= Net::SMTP::SSL->new($smtp_server, Port => $smtp_server_port);
+               }
+               else {
+                       require Net::SMTP;
+                       $smtp ||= Net::SMTP->new((defined $smtp_server_port)
+                                                ? "$smtp_server:$smtp_server_port"
+                                                : $smtp_server);
+               }
+
+               if (!$smtp) {
+                       die "Unable to initialize SMTP properly.  Is there something wrong with your config?";
+               }
+
+               if ((defined $smtp_authuser) && (defined $smtp_authpass)) {
+                       $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
+               }
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
@@ -575,7 +660,7 @@ sub send_message
        if ($quiet) {
                printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
        } else {
-               print (($dry_run ? "Dry-" : "")."OK. Log says:\nDate: $date\n");
+               print (($dry_run ? "Dry-" : "")."OK. Log says:\n");
                if ($smtp_server !~ m#^/#) {
                        print "Server: $smtp_server\n";
                        print "MAIL FROM:<$raw_from>\n";
@@ -583,7 +668,7 @@ sub send_message
                } else {
                        print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
                }
-               print "From: $sanitized_sender\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+               print $header, "\n";
                if ($smtp) {
                        print "Result: ", $smtp->code, ' ',
                                ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
@@ -643,6 +728,9 @@ sub send_message
                                        }
                                        push @xh, $_;
                                }
+                               elsif (/^Message-Id: (.*)/i) {
+                                       $message_id = $1;
+                               }
                                elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
                                        push @xh, $_;
                                }
@@ -674,6 +762,7 @@ sub send_message
                        if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
                                my $c = $2;
                                chomp $c;
+                               next if ($c eq $sender and $suppress_from);
                                push @cc, $c;
                                printf("(sob) Adding cc: %s from line '%s'\n",
                                        $c, $_) unless $quiet;
@@ -682,13 +771,14 @@ sub send_message
        }
        close F;
 
-       if ($cc_cmd ne "") {
+       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;
+                       next if ($c eq $sender and $suppress_from);
                        push @cc, $c;
                        printf("(cc-cmd) Adding cc: %s from: '%s'\n",
                                $c, $cc_cmd) unless $quiet;
@@ -728,6 +818,7 @@ sub send_message
                        $references = "$message_id";
                }
        }
+       $message_id = undef;
 }
 
 if ($compose) {
@@ -757,3 +848,15 @@ (@)
        }
        return @emails;
 }
+
+sub validate_patch {
+       my $fn = shift;
+       open(my $fh, '<', $fn)
+               or die "unable to open $fn: $!\n";
+       while (my $line = <$fh>) {
+               if (length($line) > 998) {
+                       return "$.: patch contains a line longer than 998 characters";
+               }
+       }
+       return undef;
+}