From: Junio C Hamano Date: Wed, 27 Oct 2010 05:02:52 +0000 (-0700) Subject: Merge branch 'ab/send-email-perl' X-Git-Tag: v1.7.4-rc0~160 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/7ebee44167fc25b975f5543472c851ab1840af1b?ds=inline;hp=-c Merge branch 'ab/send-email-perl' * ab/send-email-perl: send-email: extract_valid_address use qr// regexes send-email: is_rfc2047_quoted use qr// regexes send-email: use Perl idioms in while loop send-email: make_message_id use "require" instead of "use" send-email: send_message die on $!, not $? send-email: use (?:) instead of () if no match variables are needed send-email: sanitize_address use qq["foo"], not "\"foo\"" send-email: sanitize_address use $foo, not "$foo" send-email: use \E***\Q instead of \*\*\* send-email: cleanup_compose_files doesn't need a prototype send-email: unique_email_list doesn't need a prototype send-email: file_declares_8bit_cte doesn't need a prototype send-email: get_patch_subject doesn't need a prototype send-email: use lexical filehandles during sending send-email: use lexical filehandles for $compose send-email: use lexical filehandle for opendir Conflicts: git-send-email.perl --- 7ebee44167fc25b975f5543472c851ab1840af1b diff --combined git-send-email.perl index f304ef913e,db17aae020..f68ed5a5d3 --- a/git-send-email.perl +++ b/git-send-email.perl @@@ -1,4 -1,4 +1,4 @@@ -#!/usr/bin/perl -w +#!/usr/bin/perl # # Copyright 2002,2005 Greg Kroah-Hartman # Copyright 2005 Ryan Anderson @@@ -16,7 -16,6 +16,7 @@@ # and second line is the subject of the message. # +use 5.008; use strict; use warnings; use Term::ReadLine; @@@ -62,7 -61,6 +62,7 @@@ git send-email [options] * Email envelope sender. --smtp-server * Outgoing SMTP server to use. The port is optional. Default 'localhost'. + --smtp-server-option * Outgoing SMTP server option to use. --smtp-server-port * Outgoing SMTP server port. --smtp-user * Username for SMTP-AUTH. --smtp-pass * Password for SMTP-AUTH; not necessary. @@@ -73,7 -71,6 +73,7 @@@ Automating: --identity * Use the sendemail. options. + --to-cmd * Email To: via ` \$patch_path` --cc-cmd * Email Cc: via ` \$patch_path` --suppress-cc * author, self, sob, cc, cccmd, body, bodycc, all. --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. @@@ -139,11 -136,8 +139,8 @@@ my $have_mail_address = eval { require my $smtp; my $auth; - sub unique_email_list(@); - sub cleanup_compose_files(); - # Variables we fill in automatically, or via prompting: -my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh, +my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh, $initial_reply_to,$initial_subject,@files, $author,$sender,$smtp_authpass,$annotate,$compose,$time); @@@ -193,11 -187,9 +190,11 @@@ sub do_edit } # Variables with corresponding config settings -my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd); -my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption); -my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain); +my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc); +my ($to_cmd, $cc_cmd); +my ($smtp_server, $smtp_server_port, @smtp_server_options); +my ($smtp_authuser, $smtp_encryption); +my ($identity, $aliasfiletype, @alias_files, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); my ($auto_8bit_encoding); @@@ -218,12 -210,10 +215,12 @@@ my %config_bool_settings = my %config_settings = ( "smtpserver" => \$smtp_server, "smtpserverport" => \$smtp_server_port, + "smtpserveroption" => \@smtp_server_options, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, - "smtpdomain" => \$smtp_domain, - "to" => \@to, + "smtpdomain" => \$smtp_domain, + "to" => \@initial_to, + "tocmd" => \$to_cmd, "cc" => \@initial_cc, "cccmd" => \$cc_cmd, "aliasfiletype" => \$aliasfiletype, @@@ -281,8 -271,7 +278,8 @@@ $SIG{INT} = \&signal_handler my $rc = GetOptions("sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, - "to=s" => \@to, + "to=s" => \@initial_to, + "to-cmd=s" => \$to_cmd, "no-to" => \$no_to, "cc=s" => \@initial_cc, "no-cc" => \$no_cc, @@@ -290,7 -279,6 +287,7 @@@ "no-bcc" => \$no_bcc, "chain-reply-to!" => \$chain_reply_to, "smtp-server=s" => \$smtp_server, + "smtp-server-option=s" => \@smtp_server_options, "smtp-server-port=s" => \$smtp_server_port, "smtp-user=s" => \$smtp_authuser, "smtp-pass:s" => \$smtp_authpass, @@@ -377,7 -365,7 +374,7 @@@ my(%suppress_cc) if (@suppress_cc) { foreach my $entry (@suppress_cc) { die "Unknown --suppress-cc field: '$entry'\n" - unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/; + unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/; $suppress_cc{$entry} = 1; } } @@@ -422,7 -410,7 +419,7 @@@ my ($repoauthor, $repocommitter) # Verify the user input -foreach my $entry (@to) { +foreach my $entry (@initial_to) { die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/; } @@@ -521,12 -509,12 +518,12 @@@ while (defined(my $f = shift @ARGV)) push @rev_list_opts, "--", @ARGV; @ARGV = (); } elsif (-d $f and !check_file_rev_conflict($f)) { - opendir(DH,$f) + opendir my $dh, $f or die "Failed to opendir $f: $!"; push @files, grep { -f $_ } map { catfile($f, $_) } - sort readdir(DH); - closedir(DH); + sort readdir $dh; + closedir $dh; } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) { push @files, $f; } else { @@@ -558,7 -546,7 +555,7 @@@ if (@files) usage(); } - sub get_patch_subject($) { + sub get_patch_subject { my $fn = shift; open (my $fh, '<', $fn); while (my $line = <$fh>) { @@@ -576,7 -564,7 +573,7 @@@ if ($compose) $compose_filename = ($repo ? tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) : tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; - open(C,">",$compose_filename) + open my $c, ">", $compose_filename or die "Failed to open for writing $compose_filename: $!"; @@@ -584,7 -572,7 +581,7 @@@ my $tpl_subject = $initial_subject || ''; my $tpl_reply_to = $initial_reply_to || ''; - print C <",$compose_filename . ".final") + open my $c2, ">", $compose_filename . ".final" or die "Failed to open $compose_filename.final : " . $!; - open(C,"<",$compose_filename) + open $c, "<", $compose_filename or die "Failed to open $compose_filename : " . $!; my $need_8bit_cte = file_has_nonascii($compose_filename); my $in_body = 0; my $summary_empty = 1; - while() { + while(<$c>) { next if m/^GIT:/; if ($in_body) { $summary_empty = 0 unless (/^\n$/); } elsif (/^\n$/) { $in_body = 1; if ($need_8bit_cte) { - print C2 "MIME-Version: 1.0\n", + print $c2 "MIME-Version: 1.0\n", "Content-Type: text/plain; ", "charset=UTF-8\n", "Content-Transfer-Encoding: 8bit\n"; @@@ -648,10 -636,10 +645,10 @@@ print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n"; next; } - print C2 $_; + print $c2 $_; } - close(C); - close(C2); + close $c; + close $c2; if ($summary_empty) { print "Summary email is empty, skipping it\n"; @@@ -688,7 -676,7 +685,7 @@@ sub ask my %broken_encoding; - sub file_declares_8bit_cte($) { + sub file_declares_8bit_cte { my $fn = shift; open (my $fh, '<', $fn); while (my $line = <$fh>) { @@@ -717,7 -705,7 +714,7 @@@ if (!defined $auto_8bit_encoding && sca if (!$force) { for my $f (@files) { - if (get_patch_subject($f) =~ /\*\*\* SUBJECT HERE \*\*\*/) { + if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) { die "Refusing to send because the patch\n\t$f\n" . "has the template subject '*** SUBJECT HERE ***'. " . "Pass --force if you really want to send.\n"; @@@ -734,9 -722,9 +731,9 @@@ if (!defined $sender) $prompting++; } -if (!@to) { +if (!@initial_to && !defined $to_cmd) { my $to = ask("Who should the emails be sent to? "); - push @to, parse_address_line($to) if defined $to; # sanitized/validated later + push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later $prompting++; } @@@ -754,8 -742,8 +751,8 @@@ sub expand_one_alias return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias; } -@to = expand_aliases(@to); -@to = (map { sanitize_address($_) } @to); +@initial_to = expand_aliases(@initial_to); +@initial_to = (map { sanitize_address($_) } @initial_to); @initial_cc = expand_aliases(@initial_cc); @bcclist = expand_aliases(@bcclist); @@@ -789,8 -777,8 +786,8 @@@ our ($message_id, %mail, $subject, $rep sub extract_valid_address { my $address = shift; - my $local_part_regexp = '[^<>"\s@]+'; - my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+'; + my $local_part_regexp = qr/[^<>"\s@]+/; + my $domain_regexp = qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/; # check for a local address: return $address if ($address =~ /^($local_part_regexp)$/); @@@ -831,7 -819,7 +828,7 @@@ sub make_message_id last if (defined $du_part and $du_part ne ''); } if (not defined $du_part or $du_part eq '') { - use Sys::Hostname qw(); + require Sys::Hostname; $du_part = 'user@' . Sys::Hostname::hostname(); } my $message_id_template = "<%s-git-send-email-%s>"; @@@ -864,8 -852,8 +861,8 @@@ sub quote_rfc2047 sub is_rfc2047_quoted { my $s = shift; - my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+'; - my $encoded_text = '[!->@-~]+'; + my $token = qr/[^][()<>@,;:"\/?.= \000-\037\177-\377]+/; + my $encoded_text = qr/[!->@-~]+/; length($s) <= 75 && $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o; } @@@ -876,7 -864,7 +873,7 @@@ sub sanitize_address my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); if (not $recipient_name) { - return "$recipient"; + return $recipient; } # if recipient_name is already quoted, do nothing @@@ -893,7 -881,7 +890,7 @@@ # double quotes are needed if specials or CTLs are included elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) { $recipient_name =~ s/(["\\\r])/\\$1/g; - $recipient_name = "\"$recipient_name\""; + $recipient_name = qq["$recipient_name"]; } return "$recipient_name $recipient_addr"; @@@ -1038,8 -1026,6 +1035,8 @@@ X-Mailer: git-send-email $gitversio } } + unshift (@sendmail_parameters, @smtp_server_options); + if ($dry_run) { # We don't want to send the email. } elsif ($smtp_server =~ m#^/#) { @@@ -1049,7 -1035,7 +1046,7 @@@ exec($smtp_server, @sendmail_parameters) or die $!; } print $sm "$header\n$message"; - close $sm or die $?; + close $sm or die $!; } else { if (!defined $smtp_server) { @@@ -1155,13 -1141,12 +1152,13 @@@ $subject = $initial_subject $message_num = 0; foreach my $t (@files) { - open(F,"<",$t) or die "can't open file $t"; + open my $fh, "<", $t or die "can't open file $t"; my $author = undef; my $author_encoding; my $has_content_type; my $body_encoding; + @to = (); @cc = (); @xh = (); my $input_format = undef; @@@ -1169,7 -1154,7 +1166,7 @@@ $message = ""; $message_num++; # First unfold multiline header fields - while() { + while(<$fh>) { last if /^\s*$/; if (/^\s+\S/ and @header) { chomp($header[$#header]); @@@ -1202,13 -1187,6 +1199,13 @@@ $1, $_) unless $quiet; push @cc, $1; } + elsif (/^To:\s+(.*)$/) { + foreach my $addr (parse_address_line($1)) { + printf("(mbox) Adding to: %s from line '%s'\n", + $addr, $_) unless $quiet; + push @to, sanitize_address($addr); + } + } elsif (/^Cc:\s+(.*)$/) { foreach my $addr (parse_address_line($1)) { if (unquote_rfc2047($addr) eq $sender) { @@@ -1252,7 -1230,7 +1249,7 @@@ } } # Now parse the message body - while() { + while(<$fh>) { $message .= $_; if (/^(Signed-off-by|Cc): (.*)$/i) { chomp; @@@ -1269,12 -1247,22 +1266,12 @@@ $c, $_) unless $quiet; } } - close F; + close $fh; - if (defined $cc_cmd && !$suppress_cc{'cccmd'}) { - open my $fh, "$cc_cmd \Q$t\E |" - or die "(cc-cmd) Could not execute '$cc_cmd'"; - while(my $c = <$fh>) { - chomp $c; - $c =~ s/^\s*//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; - } - close $fh - or die "(cc-cmd) failed to close pipe to '$cc_cmd'"; - } + push @to, recipients_cmd("to-cmd", "to", $to_cmd, $t) + if defined $to_cmd; + push @cc, recipients_cmd("cc-cmd", "cc", $cc_cmd, $t) + if defined $cc_cmd && !$suppress_cc{'cccmd'}; if ($broken_encoding{$t} && !$has_content_type) { $has_content_type = 1; @@@ -1315,7 -1303,6 +1312,7 @@@ ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1)); $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc); + @to = (@initial_to, @to); @cc = (@initial_cc, @cc); my $message_was_sent = send_message(); @@@ -1333,39 -1320,15 +1330,38 @@@ $message_id = undef; } +# Execute a command (e.g. $to_cmd) to get a list of email addresses +# and return a results array +sub recipients_cmd { + my ($prefix, $what, $cmd, $file) = @_; + + my $sanitized_sender = sanitize_address($sender); + my @addresses = (); - open(F, "$cmd \Q$file\E |") ++ open my $fh, "$cmd \Q$file\E |" + or die "($prefix) Could not execute '$cmd'"; - while() { - my $address = $_; ++ while (my $address = <$fh>) { + $address =~ s/^\s*//g; + $address =~ s/\s*$//g; + $address = sanitize_address($address); + next if ($address eq $sanitized_sender and $suppress_from); + push @addresses, $address; + printf("($prefix) Adding %s: %s from: '%s'\n", + $what, $address, $cmd) unless $quiet; + } - close F ++ close $fh + or die "($prefix) failed to close pipe to '$cmd'"; + return @addresses; +} + cleanup_compose_files(); - sub cleanup_compose_files() { + sub cleanup_compose_files { unlink($compose_filename, $compose_filename . ".final") if $compose; } $smtp->quit if $smtp; - sub unique_email_list(@) { + sub unique_email_list { my %seen; my @emails; diff --combined t/t9001-send-email.sh index 028758c652,99a16d5750..d1ba25205b --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@@ -201,28 -201,10 +201,28 @@@ test_expect_success $PREREQ 'Prompting grep "^To: to@example.com\$" msgtxt1 ' +test_expect_success $PREREQ 'tocmd works' ' + clean_fake_sendmail && + cp $patches tocmd.patch && + echo tocmd--tocmd@example.com >>tocmd.patch && + { + echo "#!$SHELL_PATH" + echo sed -n -e s/^tocmd--//p \"\$1\" + } > tocmd-sed && + chmod +x tocmd-sed && + git send-email \ + --from="Example " \ + --to-cmd=./tocmd-sed \ + --smtp-server="$(pwd)/fake.sendmail" \ + tocmd.patch \ + && + grep "^To: tocmd@example.com" msgtxt1 +' + test_expect_success $PREREQ 'cccmd works' ' clean_fake_sendmail && cp $patches cccmd.patch && - echo cccmd--cccmd@example.com >>cccmd.patch && + echo "cccmd-- cccmd@example.com" >>cccmd.patch && { echo "#!$SHELL_PATH" echo sed -n -e s/^cccmd--//p \"\$1\" @@@ -297,7 -279,7 +297,7 @@@ test_expect_success $PREREQ 'Invalid In --to=nobody@example.com \ --in-reply-to=" " \ --smtp-server="$(pwd)/fake.sendmail" \ - $patches + $patches \ 2>errors ! grep "^In-Reply-To: < *>" msgtxt1 ' @@@ -965,45 -947,6 +965,45 @@@ test_expect_success $PREREQ '--no-bcc o ! grep "RCPT TO:" stdout ' +test_expect_success $PREREQ 'patches To headers are used by default' ' + patch=`git format-patch -1 --to="bodies@example.com"` && + test_when_finished "rm $patch" && + git send-email \ + --dry-run \ + --from="Example " \ + --smtp-server relay.example.com \ + $patch >stdout && + grep "RCPT TO:" stdout +' + +test_expect_success $PREREQ 'patches To headers are appended to' ' + patch=`git format-patch -1 --to="bodies@example.com"` && + test_when_finished "rm $patch" && + git send-email \ + --dry-run \ + --from="Example " \ + --to=nobody@example.com \ + --smtp-server relay.example.com \ + $patch >stdout && + grep "RCPT TO:" stdout && + grep "RCPT TO:" stdout +' + +test_expect_success $PREREQ 'To headers from files reset each patch' ' + patch1=`git format-patch -1 --to="bodies@example.com"` && + patch2=`git format-patch -1 --to="other@example.com" HEAD~` && + test_when_finished "rm $patch1 && rm $patch2" && + git send-email \ + --dry-run \ + --from="Example " \ + --to="nobody@example.com" \ + --smtp-server relay.example.com \ + $patch1 $patch2 >stdout && + test $(grep -c "RCPT TO:" stdout) = 1 && + test $(grep -c "RCPT TO:" stdout) = 2 && + test $(grep -c "RCPT TO:" stdout) = 1 +' + test_expect_success $PREREQ 'setup expect' ' cat >email-using-8bit <