From: Jay Soffian <jaysoffian@xxxxxxxxx> When git format-patch is given multiple --cc arguments, it generates a Cc header that looks like: Cc: first@xxxxxxxxxxx, second@xxxxxxxxxxx, third@xxxxxxxxxxx Before this commit, send-email was unable to handle such a message. First, it didn't know how to handle a header split across multiple lines. Second, it couldn't handle multiple comma-separated addresses, even if they were provided all on a single Cc: line. This patch teaches it to unfold header lines by pre-processing the header before extracting any of its fields. It also teaches it to split the Cc line using Mail::Address if available, otherwise it just splits on a simple \s*,\s* regular expression. Also, conditionally use Mail::Address to handle the response to the "Who should the emails be sent to?" prompt. Signed-off-by: Jay Soffian <jaysoffian@xxxxxxxxx> --- This message which you are reading was generated with: $ git format-patch --cc="Ryan Anderson <ryan@xxxxxxxxxxxxxx>" \ --cc=gitster@xxxxxxxxx -s -1 And sent with: $ git send-email --from 'Jay Soffian <jaysoffian@xxxxxxxxx>' \ --to git@xxxxxxxxxxxxxxx --cc 'Jay Soffian <jaysoffian@xxxxxxxxx>' Before this patch, send-email would not have sent it properly. :-) j. git-send-email.perl | 150 +++++++++++++++++++++++++++--------------------- t/t9001-send-email.sh | 22 +++++-- 2 files changed, 99 insertions(+), 73 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 77ca8fe..cde294c 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -126,6 +126,7 @@ sub format_2822_time { } my $have_email_valid = eval { require Email::Valid; 1 }; +my $have_mail_address = eval { require Mail::Address; 1 }; my $smtp; my $auth; @@ -360,6 +361,14 @@ foreach my $entry (@bcclist) { die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/; } +sub parse_address_line { + if ($have_mail_address) { + return map { $_->format } Mail::Address->parse($_[0]); + } else { + return split_addrs($_[0]); + } +} + sub split_addrs { return quotewords('\s*,\s*', 1, @_); } @@ -593,7 +602,7 @@ if (!@to) { } my $to = $_; - push @to, split_addrs($to); + push @to, parse_address_line($to); $prompting++; } @@ -920,88 +929,97 @@ foreach my $t (@files) { @cc = @initial_cc; @xh = (); my $input_format = undef; - my $header_done = 0; + my @header = (); $message = ""; + # First unfold multiline header fields while(<F>) { - if (!$header_done) { - if (/^From /) { - $input_format = 'mbox'; - next; + last if /^\s*$/; + if (/^\s+\S/ and @header) { + chomp($header[$#header]); + s/^\s+/ /; + $header[$#header] .= $_; + } else { + push(@header, $_); + } + } + # Now parse the header + foreach(@header) { + if (/^From /) { + $input_format = 'mbox'; + next; + } + chomp; + if (!defined $input_format && /^[-A-Za-z]+:\s/) { + $input_format = 'mbox'; + } + + if (defined $input_format && $input_format eq 'mbox') { + if (/^Subject:\s+(.*)$/) { + $subject = $1; } - chomp; - if (!defined $input_format && /^[-A-Za-z]+:\s/) { - $input_format = 'mbox'; + elsif (/^From:\s+(.*)$/) { + ($author, $author_encoding) = unquote_rfc2047($1); + next if ($suppress_cc{'author'}); + printf("(mbox) Adding cc: %s from line '%s'\n", + $1, $_) unless $quiet; + push @cc, $1; } - - if (defined $input_format && $input_format eq 'mbox') { - if (/^Subject:\s+(.*)$/) { - $subject = $1; - - } elsif (/^(Cc|From):\s+(.*)$/) { - if (unquote_rfc2047($2) eq $sender) { + elsif (/^Cc:\s+(.*)$/) { + foreach my $addr (parse_address_line($1)) { + if (unquote_rfc2047($addr) eq $sender) { next if ($suppress_cc{'self'}); - } - elsif ($1 eq 'From') { - ($author, $author_encoding) - = unquote_rfc2047($2); - next if ($suppress_cc{'author'}); } else { next if ($suppress_cc{'cc'}); } printf("(mbox) Adding cc: %s from line '%s'\n", - $2, $_) unless $quiet; - push @cc, $2; + $addr, $_) unless $quiet; + push @cc, $addr; } - elsif (/^Content-type:/i) { - $has_content_type = 1; - if (/charset="?([^ "]+)/) { - $body_encoding = $1; - } - push @xh, $_; - } - elsif (/^Message-Id: (.*)/i) { - $message_id = $1; - } - elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { - push @xh, $_; - } - - } else { - # In the traditional - # "send lots of email" format, - # line 1 = cc - # line 2 = subject - # So let's support that, too. - $input_format = 'lots'; - if (@cc == 0 && !$suppress_cc{'cc'}) { - printf("(non-mbox) Adding cc: %s from line '%s'\n", - $_, $_) unless $quiet; - - push @cc, $_; - - } elsif (!defined $subject) { - $subject = $_; + } + elsif (/^Content-type:/i) { + $has_content_type = 1; + if (/charset="?([^ "]+)/) { + $body_encoding = $1; } + push @xh, $_; } - - # A whitespace line will terminate the headers - if (m/^\s*$/) { - $header_done = 1; + elsif (/^Message-Id: (.*)/i) { + $message_id = $1; } + elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { + push @xh, $_; + } + } else { - $message .= $_; - if (/^(Signed-off-by|Cc): (.*)$/i) { - next if ($suppress_cc{'sob'}); - chomp; - my $c = $2; - chomp $c; - next if ($c eq $sender and $suppress_cc{'self'}); - push @cc, $c; - printf("(sob) Adding cc: %s from line '%s'\n", - $c, $_) unless $quiet; + # In the traditional + # "send lots of email" format, + # line 1 = cc + # line 2 = subject + # So let's support that, too. + $input_format = 'lots'; + if (@cc == 0 && !$suppress_cc{'cc'}) { + printf("(non-mbox) Adding cc: %s from line '%s'\n", + $_, $_) unless $quiet; + push @cc, $_; + } elsif (!defined $subject) { + $subject = $_; } } } + # Now parse the message body + while(<F>) { + $message .= $_; + if (/^(Signed-off-by|Cc): (.*)$/i) { + next if ($suppress_cc{'sob'}); + chomp; + my $c = $2; + chomp $c; + next if ($c eq $sender and $suppress_cc{'self'}); + push @cc, $c; + printf("(sob) Adding cc: %s from line '%s'\n", + $c, $_) unless $quiet; + } + } close F; if (defined $cc_cmd && !$suppress_cc{'cccmd'}) { diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index cb3d183..da54835 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -32,7 +32,7 @@ clean_fake_sendmail() { } test_expect_success 'Extract patches' ' - patches=`git format-patch -n HEAD^1` + patches=`git format-patch --cc="One <one@xxxxxxxxxxx>" --cc=two@xxxxxxxxxxx -n HEAD^1` ' test_expect_success 'Send patches' ' @@ -42,6 +42,8 @@ test_expect_success 'Send patches' ' cat >expected <<\EOF !nobody@xxxxxxxxxxx! !author@xxxxxxxxxxx! +!one@xxxxxxxxxxx! +!two@xxxxxxxxxxx! EOF test_expect_success \ 'Verify commandline' \ @@ -50,13 +52,15 @@ test_expect_success \ cat >expected-show-all-headers <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@xxxxxxxxxxx> from line 'From: A <author@xxxxxxxxxxx>' +(mbox) Adding cc: One <one@xxxxxxxxxxx> from line 'Cc: One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx' +(mbox) Adding cc: two@xxxxxxxxxxx from line 'Cc: One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx' Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@xxxxxxxxxxx> -RCPT TO:<to@xxxxxxxxxxx>,<cc@xxxxxxxxxxx>,<author@xxxxxxxxxxx>,<bcc@xxxxxxxxxxx> +RCPT TO:<to@xxxxxxxxxxx>,<cc@xxxxxxxxxxx>,<author@xxxxxxxxxxx>,<one@xxxxxxxxxxx>,<two@xxxxxxxxxxx>,<bcc@xxxxxxxxxxx> From: Example <from@xxxxxxxxxxx> To: to@xxxxxxxxxxx -Cc: cc@xxxxxxxxxxx, A <author@xxxxxxxxxxx> +Cc: cc@xxxxxxxxxxx, A <author@xxxxxxxxxxx>, One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -170,13 +174,15 @@ test_expect_success 'second message is patch' ' cat >expected-show-all-headers <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@xxxxxxxxxxx> from line 'From: A <author@xxxxxxxxxxx>' +(mbox) Adding cc: One <one@xxxxxxxxxxx> from line 'Cc: One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx' +(mbox) Adding cc: two@xxxxxxxxxxx from line 'Cc: One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx' Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@xxxxxxxxxxx> -RCPT TO:<to@xxxxxxxxxxx>,<cc@xxxxxxxxxxx>,<author@xxxxxxxxxxx> +RCPT TO:<to@xxxxxxxxxxx>,<cc@xxxxxxxxxxx>,<author@xxxxxxxxxxx>,<one@xxxxxxxxxxx>,<two@xxxxxxxxxxx> From: Example <from@xxxxxxxxxxx> To: to@xxxxxxxxxxx -Cc: cc@xxxxxxxxxxx, A <author@xxxxxxxxxxx> +Cc: cc@xxxxxxxxxxx, A <author@xxxxxxxxxxx>, One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -203,13 +209,15 @@ test_expect_success 'sendemail.cc set' ' cat >expected-show-all-headers <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@xxxxxxxxxxx> from line 'From: A <author@xxxxxxxxxxx>' +(mbox) Adding cc: One <one@xxxxxxxxxxx> from line 'Cc: One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx' +(mbox) Adding cc: two@xxxxxxxxxxx from line 'Cc: One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx' Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@xxxxxxxxxxx> -RCPT TO:<to@xxxxxxxxxxx>,<author@xxxxxxxxxxx> +RCPT TO:<to@xxxxxxxxxxx>,<author@xxxxxxxxxxx>,<one@xxxxxxxxxxx>,<two@xxxxxxxxxxx> From: Example <from@xxxxxxxxxxx> To: to@xxxxxxxxxxx -Cc: A <author@xxxxxxxxxxx> +Cc: A <author@xxxxxxxxxxx>, One <one@xxxxxxxxxxx>, two@xxxxxxxxxxx Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING -- 1.6.2.rc0.67.g77afc -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html