2323use Text::ParseWords;
2424use Data::Dumper;
2525use Term::ANSIColor;
26- use File::Temp qw/ tempdir / ;
26+ use File::Temp qw/ tempdir tempfile / ;
2727use Error qw( :try) ;
2828use Git;
2929
@@ -68,9 +68,8 @@ sub usage {
6868 Automating:
6969 --identity <str> * Use the sendemail.<id> options.
7070 --cc-cmd <str> * Email Cc: via `<str> \$ patch_path`
71- --suppress-cc <str> * author, self, sob, cccmd, all.
72- --[no-]signed-off-by-cc * Send to Cc: and Signed-off-by:
73- addresses. Default on.
71+ --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all.
72+ --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.
7473 --[no-]suppress-from * Send to self. Default off.
7574 --[no-]chain-reply-to * Chain In-Reply-To: fields. Default on.
7675 --[no-]thread * Use In-Reply-To: field. Default on.
@@ -126,6 +125,7 @@ sub format_2822_time {
126125}
127126
128127my $have_email_valid = eval { require Email::Valid; 1 };
128+ my $have_mail_address = eval { require Mail::Address; 1 };
129129my $smtp ;
130130my $auth ;
131131
@@ -156,7 +156,7 @@ sub format_2822_time {
156156# Behavior modification variables
157157my ($quiet , $dry_run ) = (0, 0);
158158my $format_patch ;
159- my $compose_filename = $repo -> repo_path() . " /.gitsendemail.msg. $$ " ;
159+ my $compose_filename ;
160160
161161# Handle interactive edition of files.
162162my $multiedit ;
@@ -219,11 +219,13 @@ sub signal_handler {
219219 system " stty echo" ;
220220
221221 # tmp files from --compose
222- if (-e $compose_filename ) {
223- print " '$compose_filename ' contains an intermediate version of the email you were composing.\n " ;
224- }
225- if (-e ($compose_filename . " .final" )) {
226- print " '$compose_filename .final' contains the composed email.\n "
222+ if (defined $compose_filename ) {
223+ if (-e $compose_filename ) {
224+ print " '$compose_filename ' contains an intermediate version of the email you were composing.\n " ;
225+ }
226+ if (-e ($compose_filename . " .final" )) {
227+ print " '$compose_filename .final' contains the composed email.\n "
228+ }
227229 }
228230
229231 exit ;
@@ -267,6 +269,9 @@ sub signal_handler {
267269 usage();
268270}
269271
272+ die " Cannot run git format-patch from outside a repository\n "
273+ if $format_patch and not $repo ;
274+
270275# Now, let's fill any that aren't set in with defaults:
271276
272277sub read_config {
@@ -318,13 +323,13 @@ sub read_config {
318323if (@suppress_cc ) {
319324 foreach my $entry (@suppress_cc ) {
320325 die " Unknown --suppress-cc field: '$entry '\n "
321- unless $entry =~ / ^(all|cccmd|cc|author|self|sob)$ / ;
326+ unless $entry =~ / ^(all|cccmd|cc|author|self|sob|body|bodycc )$ / ;
322327 $suppress_cc {$entry } = 1;
323328 }
324329}
325330
326331if ($suppress_cc {' all' }) {
327- foreach my $entry (qw ( ccmd cc author self sob) ) {
332+ foreach my $entry (qw ( ccmd cc author self sob body bodycc ) ) {
328333 $suppress_cc {$entry } = 1;
329334 }
330335 delete $suppress_cc {' all' };
@@ -334,6 +339,13 @@ sub read_config {
334339$suppress_cc {' self' } = $suppress_from if defined $suppress_from ;
335340$suppress_cc {' sob' } = !$signed_off_by_cc if defined $signed_off_by_cc ;
336341
342+ if ($suppress_cc {' body' }) {
343+ foreach my $entry (qw ( sob bodycc) ) {
344+ $suppress_cc {$entry } = 1;
345+ }
346+ delete $suppress_cc {' body' };
347+ }
348+
337349# Debugging, print out the suppressions.
338350if (0) {
339351 print " suppressions:\n " ;
@@ -360,6 +372,14 @@ sub read_config {
360372 die " Comma in --bcclist entry: $entry '\n " unless $entry !~ m / ,/ ;
361373}
362374
375+ sub parse_address_line {
376+ if ($have_mail_address ) {
377+ return map { $_ -> format } Mail::Address-> parse($_ [0]);
378+ } else {
379+ return split_addrs($_ [0]);
380+ }
381+ }
382+
363383sub split_addrs {
364384 return quotewords(' \s*,\s*' , 1, @_ );
365385}
@@ -404,6 +424,7 @@ sub split_addrs {
404424
405425# returns 1 if the conflict must be solved using it as a format-patch argument
406426sub check_file_rev_conflict ($) {
427+ return unless $repo ;
407428 my $f = shift ;
408429 try {
409430 $repo -> command(' rev-parse' , ' --verify' , ' --quiet' , $f );
445466}
446467
447468if (@rev_list_opts ) {
469+ die " Cannot run git format-patch from outside a repository\n "
470+ unless $repo ;
448471 push @files , $repo -> command(' format-patch' , ' -o' , tempdir(CLEANUP => 1), @rev_list_opts );
449472}
450473
481504if ($compose ) {
482505 # Note that this does not need to be secure, but we will make a small
483506 # effort to have it be unique
507+ $compose_filename = ($repo ?
508+ tempfile(" .gitsendemail.msg.XXXXXX" , DIR => $repo -> repo_path()) :
509+ tempfile(" .gitsendemail.msg.XXXXXX" , DIR => " ." ))[1];
484510 open (C," >" ,$compose_filename )
485511 or die " Failed to open for writing $compose_filename : $! " ;
486512
593619 }
594620
595621 my $to = $_ ;
596- push @to , split_addrs ($to );
622+ push @to , parse_address_line ($to );
597623 $prompting ++;
598624}
599625
@@ -920,88 +946,102 @@ sub send_message
920946 @cc = @initial_cc ;
921947 @xh = ();
922948 my $input_format = undef ;
923- my $header_done = 0 ;
949+ my @header = () ;
924950 $message = " " ;
951+ # First unfold multiline header fields
925952 while (<F>) {
926- if (!$header_done ) {
927- if (/ ^From / ) {
928- $input_format = ' mbox' ;
929- next ;
953+ last if / ^\s *$ / ;
954+ if (/ ^\s +\S / and @header ) {
955+ chomp ($header [$#header ]);
956+ s / ^\s +/ / ;
957+ $header [$#header ] .= $_ ;
958+ } else {
959+ push (@header , $_ );
960+ }
961+ }
962+ # Now parse the header
963+ foreach (@header ) {
964+ if (/ ^From / ) {
965+ $input_format = ' mbox' ;
966+ next ;
967+ }
968+ chomp ;
969+ if (!defined $input_format && / ^[-A-Za-z]+:\s / ) {
970+ $input_format = ' mbox' ;
971+ }
972+
973+ if (defined $input_format && $input_format eq ' mbox' ) {
974+ if (/ ^Subject:\s +(.*)$ / ) {
975+ $subject = $1 ;
930976 }
931- chomp ;
932- if (!defined $input_format && / ^[-A-Za-z]+:\s / ) {
933- $input_format = ' mbox' ;
977+ elsif (/ ^From:\s +(.*)$ / ) {
978+ ($author , $author_encoding ) = unquote_rfc2047($1 );
979+ next if $suppress_cc {' author' };
980+ next if $suppress_cc {' self' } and $author eq $sender ;
981+ printf (" (mbox) Adding cc: %s from line '%s '\n " ,
982+ $1 , $_ ) unless $quiet ;
983+ push @cc , $1 ;
934984 }
935-
936- if (defined $input_format && $input_format eq ' mbox' ) {
937- if (/ ^Subject:\s +(.*)$ / ) {
938- $subject = $1 ;
939-
940- } elsif (/ ^(Cc|From):\s +(.*)$ / ) {
941- if (unquote_rfc2047($2 ) eq $sender ) {
985+ elsif (/ ^Cc:\s +(.*)$ / ) {
986+ foreach my $addr (parse_address_line($1 )) {
987+ if (unquote_rfc2047($addr ) eq $sender ) {
942988 next if ($suppress_cc {' self' });
943- }
944- elsif ($1 eq ' From' ) {
945- ($author , $author_encoding )
946- = unquote_rfc2047($2 );
947- next if ($suppress_cc {' author' });
948989 } else {
949990 next if ($suppress_cc {' cc' });
950991 }
951992 printf (" (mbox) Adding cc: %s from line '%s '\n " ,
952- $2 , $_ ) unless $quiet ;
953- push @cc , $2 ;
954- }
955- elsif (/ ^Content-type:/i ) {
956- $has_content_type = 1;
957- if (/ charset="?([^ "]+)/ ) {
958- $body_encoding = $1 ;
959- }
960- push @xh , $_ ;
961- }
962- elsif (/ ^Message-Id: (.*)/i ) {
963- $message_id = $1 ;
993+ $addr , $_ ) unless $quiet ;
994+ push @cc , $addr ;
964995 }
965- elsif (!/^Date:\s / && / ^[-A-Za-z]+:\s +\S / ) {
966- push @xh , $_ ;
967- }
968-
969- } else {
970- # In the traditional
971- # "send lots of email" format,
972- # line 1 = cc
973- # line 2 = subject
974- # So let's support that, too.
975- $input_format = ' lots' ;
976- if (@cc == 0 && !$suppress_cc {' cc' }) {
977- printf (" (non-mbox) Adding cc: %s from line '%s '\n " ,
978- $_ , $_ ) unless $quiet ;
979-
980- push @cc , $_ ;
981-
982- } elsif (!defined $subject ) {
983- $subject = $_ ;
996+ }
997+ elsif (/ ^Content-type:/i ) {
998+ $has_content_type = 1;
999+ if (/ charset="?([^ "]+)/ ) {
1000+ $body_encoding = $1 ;
9841001 }
1002+ push @xh , $_ ;
9851003 }
986-
987- # A whitespace line will terminate the headers
988- if (m / ^\s *$ / ) {
989- $header_done = 1;
1004+ elsif (/ ^Message-Id: (.*)/i ) {
1005+ $message_id = $1 ;
9901006 }
1007+ elsif (!/^Date:\s / && / ^[-A-Za-z]+:\s +\S / ) {
1008+ push @xh , $_ ;
1009+ }
1010+
9911011 } else {
992- $message .= $_ ;
993- if (/ ^(Signed-off-by|Cc): (.*)$ /i ) {
994- next if ($suppress_cc {' sob' });
995- chomp ;
996- my $c = $2 ;
997- chomp $c ;
998- next if ($c eq $sender and $suppress_cc {' self' });
999- push @cc , $c ;
1000- printf (" (sob) Adding cc: %s from line '%s '\n " ,
1001- $c , $_ ) unless $quiet ;
1012+ # In the traditional
1013+ # "send lots of email" format,
1014+ # line 1 = cc
1015+ # line 2 = subject
1016+ # So let's support that, too.
1017+ $input_format = ' lots' ;
1018+ if (@cc == 0 && !$suppress_cc {' cc' }) {
1019+ printf (" (non-mbox) Adding cc: %s from line '%s '\n " ,
1020+ $_ , $_ ) unless $quiet ;
1021+ push @cc , $_ ;
1022+ } elsif (!defined $subject ) {
1023+ $subject = $_ ;
10021024 }
10031025 }
10041026 }
1027+ # Now parse the message body
1028+ while (<F>) {
1029+ $message .= $_ ;
1030+ if (/ ^(Signed-off-by|Cc): (.*)$ /i ) {
1031+ chomp ;
1032+ my ($what , $c ) = ($1 , $2 );
1033+ chomp $c ;
1034+ if ($c eq $sender ) {
1035+ next if ($suppress_cc {' self' });
1036+ } else {
1037+ next if $suppress_cc {' sob' } and $what =~ / Signed-off-by/i ;
1038+ next if $suppress_cc {' bodycc' } and $what =~ / Cc/i ;
1039+ }
1040+ push @cc , $c ;
1041+ printf (" (body) Adding cc: %s from line '%s '\n " ,
1042+ $c , $_ ) unless $quiet ;
1043+ }
1044+ }
10051045 close F;
10061046
10071047 if (defined $cc_cmd && !$suppress_cc {' cccmd' }) {
@@ -1020,7 +1060,7 @@ sub send_message
10201060 or die " (cc-cmd) failed to close pipe to '$cc_cmd '" ;
10211061 }
10221062
1023- if (defined $author ) {
1063+ if (defined $author and $author ne $sender ) {
10241064 $message = " From: $author \n\n $message " ;
10251065 if (defined $author_encoding ) {
10261066 if ($has_content_type ) {
0 commit comments