Merge branch 'js/maint-send-email' into maint

* js/maint-send-email:
  send-email: don't create temporary compose file until it is needed
  send-email: --suppress-cc improvements
  send-email: handle multiple Cc addresses when reading mbox message
  send-email: allow send-email to run outside a repo
This commit is contained in:
Junio C Hamano 2009-03-11 14:01:24 -07:00
commit 7681ed2d2e
3 changed files with 307 additions and 114 deletions

View File

@ -177,14 +177,25 @@ Automating
--suppress-cc::
Specify an additional category of recipients to suppress the
auto-cc of. 'self' will avoid including the sender, 'author' will
avoid including the patch author, 'cc' will avoid including anyone
mentioned in Cc lines in the patch, 'sob' will avoid including
anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid
running the --cc-cmd. 'all' will suppress all auto cc values.
Default is the value of 'sendemail.suppresscc' configuration value;
if that is unspecified, default to 'self' if --suppress-from is
specified, as well as 'sob' if --no-signed-off-cc is specified.
auto-cc of:
+
--
- 'author' will avoid including the patch author
- 'self' will avoid including the sender
- 'cc' will avoid including anyone mentioned in Cc lines in the patch header
except for self (use 'self' for that).
- 'ccbody' will avoid including anyone mentioned in Cc lines in the
patch body (commit message) except for self (use 'self' for that).
- 'sob' will avoid including anyone mentioned in Signed-off-by lines except
for self (use 'self' for that).
- 'cccmd' will avoid running the --cc-cmd.
- 'body' is equivalent to 'sob' + 'ccbody'
- 'all' will suppress all auto cc values.
--
+
Default is the value of 'sendemail.suppresscc' configuration value; if
that is unspecified, default to 'self' if --suppress-from is
specified, as well as 'body' if --no-signed-off-cc is specified.
--[no-]suppress-from::
If this is set, do not add the From: address to the cc: list.

View File

@ -23,7 +23,7 @@ use Getopt::Long;
use Text::ParseWords;
use Data::Dumper;
use Term::ANSIColor;
use File::Temp qw/ tempdir /;
use File::Temp qw/ tempdir tempfile /;
use Error qw(:try);
use Git;
@ -68,9 +68,8 @@ git send-email [options] <file | directory | rev-list options >
Automating:
--identity <str> * Use the sendemail.<id> options.
--cc-cmd <str> * Email Cc: via `<str> \$patch_path`
--suppress-cc <str> * author, self, sob, cccmd, all.
--[no-]signed-off-by-cc * Send to Cc: and Signed-off-by:
addresses. Default on.
--suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all.
--[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.
--[no-]suppress-from * Send to self. Default off.
--[no-]chain-reply-to * Chain In-Reply-To: fields. Default on.
--[no-]thread * Use In-Reply-To: field. Default on.
@ -126,6 +125,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;
@ -156,7 +156,7 @@ if ($@) {
# Behavior modification variables
my ($quiet, $dry_run) = (0, 0);
my $format_patch;
my $compose_filename = $repo->repo_path() . "/.gitsendemail.msg.$$";
my $compose_filename;
# Handle interactive edition of files.
my $multiedit;
@ -219,11 +219,13 @@ sub signal_handler {
system "stty echo";
# tmp files from --compose
if (-e $compose_filename) {
print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
}
if (-e ($compose_filename . ".final")) {
print "'$compose_filename.final' contains the composed email.\n"
if (defined $compose_filename) {
if (-e $compose_filename) {
print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
}
if (-e ($compose_filename . ".final")) {
print "'$compose_filename.final' contains the composed email.\n"
}
}
exit;
@ -267,6 +269,9 @@ unless ($rc) {
usage();
}
die "Cannot run git format-patch from outside a repository\n"
if $format_patch and not $repo;
# Now, let's fill any that aren't set in with defaults:
sub read_config {
@ -318,13 +323,13 @@ 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)$/;
unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
$suppress_cc{$entry} = 1;
}
}
if ($suppress_cc{'all'}) {
foreach my $entry (qw (ccmd cc author self sob)) {
foreach my $entry (qw (ccmd cc author self sob body bodycc)) {
$suppress_cc{$entry} = 1;
}
delete $suppress_cc{'all'};
@ -334,6 +339,13 @@ if ($suppress_cc{'all'}) {
$suppress_cc{'self'} = $suppress_from if defined $suppress_from;
$suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc;
if ($suppress_cc{'body'}) {
foreach my $entry (qw (sob bodycc)) {
$suppress_cc{$entry} = 1;
}
delete $suppress_cc{'body'};
}
# Debugging, print out the suppressions.
if (0) {
print "suppressions:\n";
@ -360,6 +372,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, @_);
}
@ -404,6 +424,7 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
# returns 1 if the conflict must be solved using it as a format-patch argument
sub check_file_rev_conflict($) {
return unless $repo;
my $f = shift;
try {
$repo->command('rev-parse', '--verify', '--quiet', $f);
@ -445,6 +466,8 @@ while (defined(my $f = shift @ARGV)) {
}
if (@rev_list_opts) {
die "Cannot run git format-patch from outside a repository\n"
unless $repo;
push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
}
@ -481,6 +504,9 @@ sub get_patch_subject($) {
if ($compose) {
# Note that this does not need to be secure, but we will make a small
# effort to have it be unique
$compose_filename = ($repo ?
tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
open(C,">",$compose_filename)
or die "Failed to open for writing $compose_filename: $!";
@ -593,7 +619,7 @@ if (!@to) {
}
my $to = $_;
push @to, split_addrs($to);
push @to, parse_address_line($to);
$prompting++;
}
@ -920,88 +946,102 @@ 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;
}
chomp;
if (!defined $input_format && /^[-A-Za-z]+:\s/) {
$input_format = 'mbox';
}
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;
} elsif (/^(Cc|From):\s+(.*)$/) {
if (unquote_rfc2047($2) eq $sender) {
if (defined $input_format && $input_format eq 'mbox') {
if (/^Subject:\s+(.*)$/) {
$subject = $1;
}
elsif (/^From:\s+(.*)$/) {
($author, $author_encoding) = unquote_rfc2047($1);
next if $suppress_cc{'author'};
next if $suppress_cc{'self'} and $author eq $sender;
printf("(mbox) Adding cc: %s from line '%s'\n",
$1, $_) unless $quiet;
push @cc, $1;
}
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;
}
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 = $_;
$addr, $_) unless $quiet;
push @cc, $addr;
}
}
# A whitespace line will terminate the headers
if (m/^\s*$/) {
$header_done = 1;
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 {
$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) {
chomp;
my ($what, $c) = ($1, $2);
chomp $c;
if ($c eq $sender) {
next if ($suppress_cc{'self'});
} else {
next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i;
next if $suppress_cc{'bodycc'} and $what =~ /Cc/i;
}
push @cc, $c;
printf("(body) Adding cc: %s from line '%s'\n",
$c, $_) unless $quiet;
}
}
close F;
if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
@ -1020,7 +1060,7 @@ foreach my $t (@files) {
or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
}
if (defined $author) {
if (defined $author and $author ne $sender) {
$message = "From: $author\n\n$message";
if (defined $author_encoding) {
if ($has_content_type) {

View File

@ -32,16 +32,18 @@ clean_fake_sendmail() {
}
test_expect_success 'Extract patches' '
patches=`git format-patch -n HEAD^1`
patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1`
'
test_expect_success 'Send patches' '
git send-email --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
'
cat >expected <<\EOF
!nobody@example.com!
!author@example.com!
!one@example.com!
!two@example.com!
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@example.com> from line 'From: A <author@example.com>'
(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<bcc@example.com>
RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<bcc@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: cc@example.com, A <author@example.com>
Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
@ -70,6 +74,7 @@ EOF
test_expect_success 'Show all headers' '
git send-email \
--dry-run \
--suppress-cc=sob \
--from="Example <from@example.com>" \
--to=to@example.com \
--cc=cc@example.com \
@ -104,6 +109,28 @@ test_expect_success 'no patch was sent' '
! test -e commandline1
'
test_expect_success 'Author From: in message body' '
clean_fake_sendmail &&
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
sed "1,/^$/d" < msgtxt1 > msgbody1
grep "From: A <author@example.com>" msgbody1
'
test_expect_success 'Author From: not in message body' '
clean_fake_sendmail &&
git send-email \
--from="A <author@example.com>" \
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
sed "1,/^$/d" < msgtxt1 > msgbody1
! grep "From: A <author@example.com>" msgbody1
'
test_expect_success 'allow long lines with --no-validate' '
git send-email \
--from="Example <nobody@example.com>" \
@ -167,16 +194,18 @@ test_expect_success 'second message is patch' '
grep "Subject:.*Second" msgtxt2
'
cat >expected-show-all-headers <<\EOF
cat >expected-suppress-sob <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>
RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: cc@example.com, A <author@example.com>
Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
@ -185,10 +214,10 @@ X-Mailer: X-MAILER-STRING
Result: OK
EOF
test_expect_success 'sendemail.cc set' '
git config sendemail.cc cc@example.com &&
test_suppression () {
git send-email \
--dry-run \
--suppress-cc=$1 \
--from="Example <from@example.com>" \
--to=to@example.com \
--smtp-server relay.example.com \
@ -196,20 +225,27 @@ test_expect_success 'sendemail.cc set' '
sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \
-e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
-e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
>actual-show-all-headers &&
test_cmp expected-show-all-headers actual-show-all-headers
>actual-suppress-$1 &&
test_cmp expected-suppress-$1 actual-suppress-$1
}
test_expect_success 'sendemail.cc set' '
git config sendemail.cc cc@example.com &&
test_suppression sob
'
cat >expected-show-all-headers <<\EOF
cat >expected-suppress-sob <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<author@example.com>
RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: A <author@example.com>
Cc: A <author@example.com>, One <one@example.com>, two@example.com
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
@ -220,17 +256,123 @@ EOF
test_expect_success 'sendemail.cc unset' '
git config --unset sendemail.cc &&
git send-email \
--dry-run \
--from="Example <from@example.com>" \
--to=to@example.com \
--smtp-server relay.example.com \
$patches |
sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \
-e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
-e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
>actual-show-all-headers &&
test_cmp expected-show-all-headers actual-show-all-headers
test_suppression sob
'
cat >expected-suppress-all <<\EOF
0001-Second.patch
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>
From: Example <from@example.com>
To: to@example.com
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING
Result: OK
EOF
test_expect_success '--suppress-cc=all' '
test_suppression all
'
cat >expected-suppress-body <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: A <author@example.com>, One <one@example.com>, two@example.com
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING
Result: OK
EOF
test_expect_success '--suppress-cc=body' '
test_suppression body
'
cat >expected-suppress-sob <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: A <author@example.com>, One <one@example.com>, two@example.com
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING
Result: OK
EOF
test_expect_success '--suppress-cc=sob' '
test_suppression sob
'
cat >expected-suppress-bodycc <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com>
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING
Result: OK
EOF
test_expect_success '--suppress-cc=bodycc' '
test_suppression bodycc
'
cat >expected-suppress-cc <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
Dry-OK. Log says:
Server: relay.example.com
MAIL FROM:<from@example.com>
RCPT TO:<to@example.com>,<author@example.com>,<committer@example.com>
From: Example <from@example.com>
To: to@example.com
Cc: A <author@example.com>, C O Mitter <committer@example.com>
Subject: [PATCH 1/1] Second.
Date: DATE-STRING
Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING
Result: OK
EOF
test_expect_success '--suppress-cc=cc' '
test_suppression cc
'
test_expect_success '--compose adds MIME for utf8 body' '