Merge branch 'maint-1.6.1' into maint-1.6.2
[gitweb.git] / git-send-email.perl
index c6bba47eb638a4d5c9e5013c405ed8f5ed6292ce..09fe3d95c4e076af1bf6230d31ca7b67e1893dc2 100755 (executable)
 use warnings;
 use Term::ReadLine;
 use Getopt::Long;
+use Text::ParseWords;
 use Data::Dumper;
 use Term::ANSIColor;
+use File::Temp qw/ tempdir tempfile /;
+use Error qw(:try);
 use Git;
 
+Getopt::Long::Configure qw/ pass_through /;
+
 package FakeTerm;
 sub new {
        my ($class, $reason) = @_;
@@ -38,73 +43,43 @@ package main;
 
 sub usage {
        print <<EOT;
-git-send-email [options] <file | directory>...
-Options:
-   --from         Specify the "From:" line of the email to be sent.
-
-   --to           Specify the primary "To:" line of the email.
-
-   --cc           Specify an initial "Cc:" list for the entire series
-                  of emails.
-
-   --cc-cmd       Specify a command to execute per file which adds
-                  per file specific cc address entries
-
-   --bcc          Specify a list of email addresses that should be Bcc:
-                 on all the emails.
-
-   --compose      Use \$GIT_EDITOR, core.editor, \$EDITOR, or \$VISUAL to edit
-                 an introductory message for the patch series.
-
-   --subject      Specify the initial "Subject:" line.
-                  Only necessary if --compose is also set.  If --compose
-                 is not set, this will be prompted for.
-
-   --in-reply-to  Specify the first "In-Reply-To:" header line.
-                  Only used if --compose is also set.  If --compose is not
-                 set, this will be prompted for.
-
-   --chain-reply-to If set, the replies will all be to the previous
-                  email sent, rather than to the first email sent.
-                  Defaults to on.
-
-   --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.  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.
-
-   --smtp-ssl     If set, connects to the SMTP server using SSL.
-
-   --suppress-cc  Suppress the specified category of auto-CC.  The category
-                 can be one of 'author' for the patch author, 'self' to
-                 avoid copying yourself, 'sob' for Signed-off-by lines,
-                 'cccmd' for the output of the cccmd, or 'all' to suppress
-                 all of these.
-
-   --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.
-
-   --quiet       Make git-send-email less verbose.  One line per email
-                  should be all that is output.
-
-   --dry-run     Do everything except actually send the emails.
-
-   --envelope-sender   Specify the envelope sender used to send the emails.
-
-   --no-validate       Don't perform any sanity checks on patches.
+git send-email [options] <file | directory | rev-list options >
+
+  Composing:
+    --from                  <str>  * Email From:
+    --to                    <str>  * Email To:
+    --cc                    <str>  * Email Cc:
+    --bcc                   <str>  * Email Bcc:
+    --subject               <str>  * Email "Subject:"
+    --in-reply-to           <str>  * Email "In-Reply-To:"
+    --annotate                     * Review each patch that will be sent in an editor.
+    --compose                      * Open an editor for introduction.
+
+  Sending:
+    --envelope-sender       <str>  * Email envelope sender.
+    --smtp-server       <str:int>  * Outgoing SMTP server to use. The port
+                                     is optional. Default 'localhost'.
+    --smtp-server-port      <int>  * Outgoing SMTP server port.
+    --smtp-user             <str>  * Username for SMTP-AUTH.
+    --smtp-pass             <str>  * Password for SMTP-AUTH; not necessary.
+    --smtp-encryption       <str>  * tls or ssl; anything else disables.
+    --smtp-ssl                     * Deprecated. Use '--smtp-encryption ssl'.
+
+  Automating:
+    --identity              <str>  * Use the sendemail.<id> options.
+    --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`
+    --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.
+
+  Administering:
+    --quiet                        * Output one line of info per email.
+    --dry-run                      * Don't actually send the emails.
+    --[no-]validate                * Perform patch sanity checks. Default on.
+    --[no-]format-patch            * understand any non optional arguments as
+                                     `git format-patch` ones.
 
 EOT
        exit(1);
@@ -150,25 +125,25 @@ 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;
 
 sub unique_email_list(@);
 sub cleanup_compose_files();
 
-# Constants (essentially)
-my $compose_filename = ".msg.$$";
-
 # Variables we fill in automatically, or via prompting:
 my (@to,@cc,@initial_cc,@bcclist,@xh,
-       $initial_reply_to,$initial_subject,@files,$author,$sender,$smtp_authpass,$compose,$time);
+       $initial_reply_to,$initial_subject,@files,
+       $author,$sender,$smtp_authpass,$annotate,$compose,$time);
 
 my $envelope_sender;
 
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
 
-my $repo = Git->repository();
+my $repo = eval { Git->repository() };
+my @repo = $repo ? ($repo) : ();
 my $term = eval {
        $ENV{"GIT_SEND_EMAIL_NOTTY"}
                ? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT
@@ -180,20 +155,42 @@ sub format_2822_time {
 
 # Behavior modification variables
 my ($quiet, $dry_run) = (0, 0);
+my $format_patch;
+my $compose_filename;
+
+# Handle interactive edition of files.
+my $multiedit;
+my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+sub do_edit {
+       if (defined($multiedit) && !$multiedit) {
+               map {
+                       system('sh', '-c', $editor.' "$@"', $editor, $_);
+                       if (($? & 127) || ($? >> 8)) {
+                               die("the editor exited uncleanly, aborting everything");
+                       }
+               } @_;
+       } else {
+               system('sh', '-c', $editor.' "$@"', $editor, @_);
+               if (($? & 127) || ($? >> 8)) {
+                       die("the editor exited uncleanly, aborting everything");
+               }
+       }
+}
 
 # 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_ssl);
+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);
-my ($no_validate);
+my ($validate);
 my (@suppress_cc);
 
 my %config_bool_settings = (
     "thread" => [\$thread, 1],
     "chainreplyto" => [\$chain_reply_to, 1],
     "suppressfrom" => [\$suppress_from, undef],
-    "signedoffcc" => [\$signed_off_cc, undef],
-    "smtpssl" => [\$smtp_ssl, 0],
+    "signedoffbycc" => [\$signed_off_by_cc, undef],
+    "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
+    "validate" => [\$validate, 1],
 );
 
 my %config_settings = (
@@ -202,11 +199,14 @@ sub format_2822_time {
     "smtpuser" => \$smtp_authuser,
     "smtppass" => \$smtp_authpass,
     "to" => \@to,
+    "cc" => \@initial_cc,
     "cccmd" => \$cc_cmd,
     "aliasfiletype" => \$aliasfiletype,
     "bcc" => \@bcclist,
     "aliasesfile" => \@alias_files,
     "suppresscc" => \@suppress_cc,
+    "envelopesender" => \$envelope_sender,
+    "multiedit" => \$multiedit,
 );
 
 # Handle Uncouth Termination
@@ -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;
@@ -246,24 +248,30 @@ sub signal_handler {
                    "smtp-server-port=s" => \$smtp_server_port,
                    "smtp-user=s" => \$smtp_authuser,
                    "smtp-pass:s" => \$smtp_authpass,
-                   "smtp-ssl!" => \$smtp_ssl,
+                   "smtp-ssl" => sub { $smtp_encryption = 'ssl' },
+                   "smtp-encryption=s" => \$smtp_encryption,
                    "identity=s" => \$identity,
+                   "annotate" => \$annotate,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
                    "cc-cmd=s" => \$cc_cmd,
                    "suppress-from!" => \$suppress_from,
                    "suppress-cc=s" => \@suppress_cc,
-                   "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
+                   "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
                    "thread!" => \$thread,
-                   "no-validate" => \$no_validate,
+                   "validate!" => \$validate,
+                   "format-patch!" => \$format_patch,
         );
 
 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 {
@@ -271,25 +279,34 @@ sub read_config {
 
        foreach my $setting (keys %config_bool_settings) {
                my $target = $config_bool_settings{$setting}->[0];
-               $$target = $repo->config_bool("$prefix.$setting") unless (defined $$target);
+               $$target = Git::config_bool(@repo, "$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");
+                               my @values = Git::config(@repo, "$prefix.$setting");
                                @$target = @values if (@values && defined $values[0]);
                        }
                }
                else {
-                       $$target = $repo->config("$prefix.$setting") unless (defined $$target);
+                       $$target = Git::config(@repo, "$prefix.$setting") unless (defined $$target);
+               }
+       }
+
+       if (!defined $smtp_encryption) {
+               my $enc = Git::config(@repo, "$prefix.smtpencryption");
+               if (defined $enc) {
+                       $smtp_encryption = $enc;
+               } elsif (Git::config_bool(@repo, "$prefix.smtpssl")) {
+                       $smtp_encryption = 'ssl';
                }
        }
 }
 
 # read configuration from [sendemail "$identity"], fall back on [sendemail]
-$identity = $repo->config("sendemail.identity") unless (defined $identity);
+$identity = Git::config(@repo, "sendemail.identity") unless (defined $identity);
 read_config("sendemail.$identity") if (defined $identity);
 read_config("sendemail");
 
@@ -298,18 +315,21 @@ sub read_config {
        ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
 }
 
+# 'default' encryption is none -- this only prevents a warning
+$smtp_encryption = '' unless (defined $smtp_encryption);
+
 # Set CC suppressions
 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'};
@@ -317,7 +337,14 @@ sub read_config {
 
 # If explicit old-style ones are specified, they trump --suppress-cc.
 $suppress_cc{'self'} = $suppress_from if defined $suppress_from;
-$suppress_cc{'sob'} = !$signed_off_cc if defined $signed_off_cc;
+$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) {
@@ -327,8 +354,9 @@ sub read_config {
        }
 }
 
-my ($repoauthor) = $repo->ident_person('author');
-my ($repocommitter) = $repo->ident_person('committer');
+my ($repoauthor, $repocommitter);
+($repoauthor) = Git::ident_person(@repo, 'author');
+($repocommitter) = Git::ident_person(@repo, 'committer');
 
 # Verify the user input
 
@@ -344,6 +372,18 @@ sub read_config {
        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, @_);
+}
+
 my %aliases;
 my %parse_alias = (
        # multiline formats can be supported in the future
@@ -352,17 +392,20 @@ sub read_config {
                        my ($alias, $addr) = ($1, $2);
                        $addr =~ s/#.*$//; # mutt allows # comments
                         # commas delimit multiple addresses
-                       $aliases{$alias} = [ split(/\s*,\s*/, $addr) ];
+                       $aliases{$alias} = [ split_addrs($addr) ];
                }}},
        mailrc => sub { my $fh = shift; while (<$fh>) {
                if (/^alias\s+(\S+)\s+(.*)$/) {
                        # spaces delimit multiple addresses
                        $aliases{$1} = [ split(/\s+/, $2) ];
                }}},
-       pine => sub { my $fh = shift; while (<$fh>) {
-               if (/^(\S+)\t.*\t(.*)$/) {
-                       $aliases{$1} = [ split(/\s*,\s*/, $2) ];
-               }}},
+       pine => sub { my $fh = shift; my $f='\t[^\t]*';
+               for (my $x = ''; defined($x); $x = $_) {
+                       chomp $x;
+                       $x .= $1 while(defined($_ = <$fh>) && /^ +(.*)$/);
+                       $x =~ /^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ or next;
+                       $aliases{$1} = [ split_addrs($2) ];
+               }},
        gnus => sub { my $fh = shift; while (<$fh>) {
                if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
                        $aliases{$1} = [ $2 ];
@@ -379,28 +422,61 @@ sub read_config {
 
 ($sender) = expand_aliases($sender) if defined $sender;
 
+# 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);
+               if (defined($format_patch)) {
+                       print "foo\n";
+                       return $format_patch;
+               }
+               die(<<EOF);
+File '$f' exists but it could also be the range of commits
+to produce patches for.  Please disambiguate by...
+
+    * Saying "./$f" if you mean a file; or
+    * Giving --format-patch option if you mean a range.
+EOF
+       } catch Git::Error::Command with {
+               return 0;
+       }
+}
+
 # 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) {
+my @rev_list_opts;
+while (defined(my $f = shift @ARGV)) {
+       if ($f eq "--") {
+               push @rev_list_opts, "--", @ARGV;
+               @ARGV = ();
+       } elsif (-d $f and !check_file_rev_conflict($f)) {
                opendir(DH,$f)
                        or die "Failed to opendir $f: $!";
 
                push @files, grep { -f $_ } map { +$f . "/" . $_ }
                                sort readdir(DH);
-
-       } elsif (-f $f) {
+               closedir(DH);
+       } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) {
                push @files, $f;
-
        } else {
-               print STDERR "Skipping $f - not found.\n";
+               push @rev_list_opts, $f;
        }
 }
 
-if (!$no_validate) {
+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);
+}
+
+if ($validate) {
        foreach my $f (@files) {
-               my $error = validate_patch($f);
-               $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+               unless (-p $f) {
+                       my $error = validate_patch($f);
+                       $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+               }
        }
 }
 
@@ -413,9 +489,114 @@ sub read_config {
        usage();
 }
 
+sub get_patch_subject($) {
+       my $fn = shift;
+       open (my $fh, '<', $fn);
+       while (my $line = <$fh>) {
+               next unless ($line =~ /^Subject: (.*)$/);
+               close $fh;
+               return "GIT: $1\n";
+       }
+       close $fh;
+       die "No subject line in $fn ?";
+}
+
+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: $!";
+
+
+       my $tpl_sender = $sender || $repoauthor || $repocommitter || '';
+       my $tpl_subject = $initial_subject || '';
+       my $tpl_reply_to = $initial_reply_to || '';
+
+       print C <<EOT;
+From $tpl_sender # This line is ignored.
+GIT: Lines beginning in "GIT: " will be removed.
+GIT: Consider including an overall diffstat or table of contents
+GIT: for the patch you are writing.
+GIT:
+GIT: Clear the body content if you don't wish to send a summary.
+From: $tpl_sender
+Subject: $tpl_subject
+In-Reply-To: $tpl_reply_to
+
+EOT
+       for my $f (@files) {
+               print C get_patch_subject($f);
+       }
+       close(C);
+
+       my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+
+       if ($annotate) {
+               do_edit($compose_filename, @files);
+       } else {
+               do_edit($compose_filename);
+       }
+
+       open(C2,">",$compose_filename . ".final")
+               or die "Failed to open $compose_filename.final : " . $!;
+
+       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(<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",
+                                        "Content-Type: text/plain; ",
+                                          "charset=utf-8\n",
+                                        "Content-Transfer-Encoding: 8bit\n";
+                       }
+               } elsif (/^MIME-Version:/i) {
+                       $need_8bit_cte = 0;
+               } elsif (/^Subject:\s*(.+)\s*$/i) {
+                       $initial_subject = $1;
+                       my $subject = $initial_subject;
+                       $_ = "Subject: " .
+                               ($subject =~ /[^[:ascii:]]/ ?
+                                quote_rfc2047($subject) :
+                                $subject) .
+                               "\n";
+               } elsif (/^In-Reply-To:\s*(.+)\s*$/i) {
+                       $initial_reply_to = $1;
+                       next;
+               } elsif (/^From:\s*(.+)\s*$/i) {
+                       $sender = $1;
+                       next;
+               } elsif (/^(?:To|Cc|Bcc):/i) {
+                       print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
+                       next;
+               }
+               print C2 $_;
+       }
+       close(C);
+       close(C2);
+
+       if ($summary_empty) {
+               print "Summary email is empty, skipping it\n";
+               $compose = -1;
+       }
+} elsif ($annotate) {
+       do_edit(@files);
+}
+
 my $prompting = 0;
 if (!defined $sender) {
-       $sender = $repoauthor || $repocommitter;
+       $sender = $repoauthor || $repocommitter || '';
 
        while (1) {
                $_ = $term->readline("Who should the emails appear to be from? [$sender] ");
@@ -438,7 +619,7 @@ sub read_config {
        }
 
        my $to = $_;
-       push @to, split /,/, $to;
+       push @to, parse_address_line($to);
        $prompting++;
 }
 
@@ -457,17 +638,6 @@ sub expand_aliases {
 @initial_cc = expand_aliases(@initial_cc);
 @bcclist = expand_aliases(@bcclist);
 
-if (!defined $initial_subject && $compose) {
-       while (1) {
-               $_ = $term->readline("What subject should the initial email start with? ", $initial_subject);
-               last if defined $_;
-               print "\n";
-       }
-
-       $initial_subject = $_;
-       $prompting++;
-}
-
 if ($thread && !defined $initial_reply_to && $prompting) {
        while (1) {
                $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to);
@@ -494,59 +664,6 @@ sub expand_aliases {
 }
 
 if ($compose) {
-       # Note that this does not need to be secure, but we will make a small
-       # effort to have it be unique
-       open(C,">",$compose_filename)
-               or die "Failed to open for writing $compose_filename: $!";
-       print C "From $sender # This line is ignored.\n";
-       printf C "Subject: %s\n\n", $initial_subject;
-       printf C <<EOT;
-GIT: Please enter your email below.
-GIT: Lines beginning in "GIT: " will be removed.
-GIT: Consider including an overall diffstat or table of contents
-GIT: for the patch you are writing.
-
-EOT
-       close(C);
-
-       my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
-       system('sh', '-c', '$0 $@', $editor, $compose_filename);
-
-       open(C2,">",$compose_filename . ".final")
-               or die "Failed to open $compose_filename.final : " . $!;
-
-       open(C,"<",$compose_filename)
-               or die "Failed to open $compose_filename : " . $!;
-
-       my $need_8bit_cte = file_has_nonascii($compose_filename);
-       my $in_body = 0;
-       while(<C>) {
-               next if m/^GIT: /;
-               if (!$in_body && /^\n$/) {
-                       $in_body = 1;
-                       if ($need_8bit_cte) {
-                               print C2 "MIME-Version: 1.0\n",
-                                        "Content-Type: text/plain; ",
-                                          "charset=utf-8\n",
-                                        "Content-Transfer-Encoding: 8bit\n";
-                       }
-               }
-               if (!$in_body && /^MIME-Version:/i) {
-                       $need_8bit_cte = 0;
-               }
-               if (!$in_body && /^Subject: ?(.*)/i) {
-                       my $subject = $1;
-                       $_ = "Subject: " .
-                               ($subject =~ /[^[:ascii:]]/ ?
-                                quote_rfc2047($subject) :
-                                $subject) .
-                               "\n";
-               }
-               print C2 $_;
-       }
-       close(C);
-       close(C2);
-
        while (1) {
                $_ = $term->readline("Send this email? (y|n) ");
                last if defined $_;
@@ -558,7 +675,9 @@ sub expand_aliases {
                exit(0);
        }
 
-       @files = ($compose_filename . ".final", @files);
+       if ($compose > 0) {
+               @files = ($compose_filename . ".final", @files);
+       }
 }
 
 # Variables we set as part of the loop over files
@@ -702,7 +821,7 @@ sub send_message
 Message-Id: $message_id
 X-Mailer: git-send-email $gitversion
 ";
-       if ($thread && $reply_to) {
+       if ($reply_to) {
 
                $header .= "In-Reply-To: $reply_to\n";
                $header .= "References: $references\n";
@@ -734,7 +853,7 @@ sub send_message
                        die "The required SMTP server is not properly defined."
                }
 
-               if ($smtp_ssl) {
+               if ($smtp_encryption eq 'ssl') {
                        $smtp_server_port ||= 465; # ssmtp
                        require Net::SMTP::SSL;
                        $smtp ||= Net::SMTP::SSL->new($smtp_server, Port => $smtp_server_port);
@@ -744,6 +863,21 @@ sub send_message
                        $smtp ||= Net::SMTP->new((defined $smtp_server_port)
                                                 ? "$smtp_server:$smtp_server_port"
                                                 : $smtp_server);
+                       if ($smtp_encryption eq 'tls') {
+                               require Net::SMTP::SSL;
+                               $smtp->command('STARTTLS');
+                               $smtp->response();
+                               if ($smtp->code == 220) {
+                                       $smtp = Net::SMTP::SSL->start_SSL($smtp)
+                                               or die "STARTTLS failed! ".$smtp->message;
+                                       $smtp_encryption = '';
+                                       # Send EHLO again to receive fresh
+                                       # supported commands
+                                       $smtp->hello();
+                               } else {
+                                       die "Server does not support STARTTLS! ".$smtp->message;
+                               }
+                       }
                }
 
                if (!$smtp) {
@@ -812,86 +946,100 @@ sub send_message
        @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'};
+                               next if $suppress_cc{'self'} and $author eq $sender;
+                               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;
-                               }
-                               elsif (/^Content-type:/i) {
-                                       $has_content_type = 1;
-                                       if (/charset="?[^ "]+/) {
-                                               $body_encoding = $1;
-                                       }
-                                       push @xh, $_;
+                                               $addr, $_) unless $quiet;
+                                       push @cc, $addr;
                                }
-                               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) {
+                       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;
@@ -912,7 +1060,7 @@ sub send_message
                        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) {