]> asedeno.scripts.mit.edu Git - git.git/commitdiff
Merge branch 'tr/maint-1.6.0-send-email-irt'
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 Mar 2009 01:54:46 +0000 (18:54 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 Mar 2009 01:54:46 +0000 (18:54 -0700)
* tr/maint-1.6.0-send-email-irt:
  send-email: test --no-thread --in-reply-to combination
  send-email: respect in-reply-to regardless of threading

Conflicts:
t/t9001-send-email.sh

1  2 
git-send-email.perl
t/t9001-send-email.sh

diff --combined git-send-email.perl
index 57127aa823833f75fb546e738fcb19381fc331f7,734dc9f4f91dd0d1f1c0dfccc9e746b35562f350..546d2ebc0c72d4a330b3b40ca886af9340afb5af
@@@ -23,12 -23,8 +23,12 @@@ 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) = @_;
@@@ -43,45 -39,76 +43,45 @@@ 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-encryption Specify 'tls' for STARTTLS encryption, or 'ssl' for SSL.
 -                  Any other value disables the feature.
 -
 -   --smtp-ssl     Synonym for '--smtp-encryption=ssl'.  Deprecated.
 -
 -   --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:
 +    --confirm               <str>  * Confirm recipients before sending;
 +                                     auto, cc, compose, always, or never.
 +    --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);
@@@ -127,17 -154,18 +127,17 @@@ 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;
  
@@@ -157,42 -185,19 +157,42 @@@ if ($@) 
  
  # 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 ($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, $confirm);
  my (@suppress_cc);
  
  my %config_bool_settings = (
      "thread" => [\$thread, 1],
      "chainreplyto" => [\$chain_reply_to, 1],
      "suppressfrom" => [\$suppress_from, undef],
 -    "signedoffcc" => [\$signed_off_cc, undef],
 +    "signedoffbycc" => [\$signed_off_by_cc, undef],
 +    "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
 +    "validate" => [\$validate, 1],
  );
  
  my %config_settings = (
      "aliasesfile" => \@alias_files,
      "suppresscc" => \@suppress_cc,
      "envelopesender" => \$envelope_sender,
 +    "multiedit" => \$multiedit,
 +    "confirm"   => \$confirm,
  );
  
  # Handle Uncouth Termination
@@@ -222,13 -225,11 +222,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;
@@@ -254,28 -255,22 +254,28 @@@ my $rc = GetOptions("sender|from=s" => 
                    "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,
 +                  "confirm=s" => \$confirm,
                    "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 {
@@@ -327,13 -322,13 +327,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'};
  
  # 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'};
 +}
 +
 +# Set confirm's default value
 +my $confirm_unconfigured = !defined $confirm;
 +if ($confirm_unconfigured) {
 +      $confirm = scalar %suppress_cc ? 'compose' : 'auto';
 +};
 +die "Unknown --confirm setting: '$confirm'\n"
 +      unless $confirm =~ /^(?:auto|cc|compose|always|never)/;
  
  # Debugging, print out the suppressions.
  if (0) {
@@@ -384,16 -364,8 +384,16 @@@ 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 parse_line('\s*,\s*', 1, @_);
 +      return quotewords('\s*,\s*', 1, @_);
  }
  
  my %aliases;
@@@ -411,13 -383,10 +411,13 @@@ my %parse_alias = 
                        # spaces delimit multiple addresses
                        $aliases{$1} = [ split(/\s+/, $2) ];
                }}},
 -      pine => sub { my $fh = shift; while (<$fh>) {
 -              if (/^(\S+)\t.*\t(.*)$/) {
 +      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 ];
@@@ -434,56 -403,24 +434,56 @@@ if (@alias_files and $aliasfiletype an
  
  ($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);
                closedir(DH);
 -      } elsif (-f $f or -p $f) {
 +      } 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) {
                unless (-p $f) {
                        my $error = validate_patch($f);
@@@ -501,111 -438,6 +501,111 @@@ if (@files) 
        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 || '';
@@@ -631,7 -463,7 +631,7 @@@ if (!@to) 
        }
  
        my $to = $_;
 -      push @to, split_addrs($to);
 +      push @to, parse_address_line($to);
        $prompting++;
  }
  
@@@ -650,6 -482,17 +650,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);
@@@ -675,13 -518,76 +675,13 @@@ if (!defined $smtp_server) 
        $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
  }
  
 -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} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
 -      system('sh', '-c', $editor.' "$@"', $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 $_;
 -              print "\n";
 -      }
 -
 -      if (uc substr($_,0,1) ne 'Y') {
 -              cleanup_compose_files();
 -              exit(0);
 -      }
 -
 +if ($compose && $compose > 0) {
        @files = ($compose_filename . ".final", @files);
  }
  
  # Variables we set as part of the loop over files
 -our ($message_id, %mail, $subject, $reply_to, $references, $message);
 +our ($message_id, %mail, $subject, $reply_to, $references, $message,
 +      $needs_confirm, $message_num);
  
  sub extract_valid_address {
        my $address = shift;
@@@ -821,7 -727,7 +821,7 @@@ Date: $dat
  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";
        unshift (@sendmail_parameters,
                        '-f', $raw_from) if(defined $envelope_sender);
  
 +      if ($needs_confirm && !$dry_run) {
 +              print "\n$header\n";
 +              if ($needs_confirm eq "inform") {
 +                      $confirm_unconfigured = 0; # squelch this message for the rest of this run
 +                      print "    The Cc list above has been expanded by additional\n";
 +                      print "    addresses found in the patch commit message. By default\n";
 +                      print "    send-email prompts before sending whenever this occurs.\n";
 +                      print "    This behavior is controlled by the sendemail.confirm\n";
 +                      print "    configuration setting.\n";
 +                      print "\n";
 +                      print "    For additional information, run 'git send-email --help'.\n";
 +                      print "    To retain the current behavior, but squelch this message,\n";
 +                      print "    run 'git config --global sendemail.confirm auto'.\n\n";
 +              }
 +              while (1) {
 +                      chomp ($_ = $term->readline(
 +                              "Send this email? ([y]es|[n]o|[q]uit|[a]ll): "
 +                      ));
 +                      last if /^(?:yes|y|no|n|quit|q|all|a)/i;
 +                      print "\n";
 +              }
 +              if (/^n/i) {
 +                      return;
 +              } elsif (/^q/i) {
 +                      cleanup_compose_files();
 +                      exit(0);
 +              } elsif (/^a/i) {
 +                      $confirm = 'never';
 +              }
 +      }
 +
        if ($dry_run) {
                # We don't want to send the email.
        } elsif ($smtp_server =~ m#^/#) {
  $reply_to = $initial_reply_to;
  $references = $initial_reply_to || '';
  $subject = $initial_subject;
 +$message_num = 0;
  
  foreach my $t (@files) {
        open(F,"<",$t) or die "can't open file $t";
        my $author_encoding;
        my $has_content_type;
        my $body_encoding;
 -      @cc = @initial_cc;
 +      @cc = ();
        @xh = ();
        my $input_format = undef;
 -      my $header_done = 0;
 +      my @header = ();
        $message = "";
 +      $message_num++;
 +      # 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, $_;
 -                              }
 -                              elsif (/^Message-Id: (.*)/i) {
 -                                      $message_id = $1;
 -                              }
 -                              elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
 -                                      push @xh, $_;
 +                                              $addr, $_) unless $quiet;
 +                                      push @cc, $addr;
                                }
 -
 -                      } 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;
  
        if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
                        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) {
                }
        }
  
 +      $needs_confirm = (
 +              $confirm eq "always" or
 +              ($confirm =~ /^(?:auto|cc)$/ && @cc) or
 +              ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
 +      $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
 +
 +      @cc = (@initial_cc, @cc);
 +
        send_message();
  
        # set up for the next message
        $message_id = undef;
  }
  
 -if ($compose) {
 -      cleanup_compose_files();
 -}
 +cleanup_compose_files();
  
  sub cleanup_compose_files() {
 -      unlink($compose_filename, $compose_filename . ".final");
 -
 +      unlink($compose_filename, $compose_filename . ".final") if $compose;
  }
  
  $smtp->quit if $smtp;
diff --combined t/t9001-send-email.sh
index 9523305304cf9d062c11efaf9934176a813d8141,a404204b176670ef6e6f0692bdf0ffeb71f23b75..e426c96fb7d0f72b2822d4379b7fa671a04ab733
@@@ -32,76 -32,31 +32,76 @@@ 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 no confirm early to ensure remaining tests will not hang
 +test_no_confirm () {
 +      rm -f no_confirm_okay
 +      echo n | \
 +              GIT_SEND_EMAIL_NOTTY=1 \
 +              git send-email \
 +              --from="Example <from@example.com>" \
 +              --to=nobody@example.com \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              $@ \
 +              $patches > stdout &&
 +              test_must_fail grep "Send this email" stdout &&
 +              > no_confirm_okay
 +}
 +
 +# Exit immediately to prevent hang if a no-confirm test fails
 +check_no_confirm () {
 +      test -f no_confirm_okay || {
 +              say 'No confirm test failed; skipping remaining tests to prevent hanging'
 +              test_done
 +      }
 +}
 +
 +test_expect_success 'No confirm with --suppress-cc' '
 +      test_no_confirm --suppress-cc=sob
 +'
 +check_no_confirm
 +
 +test_expect_success 'No confirm with --confirm=never' '
 +      test_no_confirm --confirm=never
 +'
 +check_no_confirm
 +
 +# leave sendemail.confirm set to never after this so that none of the
 +# remaining tests prompt unintentionally.
 +test_expect_success 'No confirm with sendemail.confirm=never' '
 +      git config sendemail.confirm never &&
 +      test_no_confirm --compose --subject=foo
 +'
 +check_no_confirm
 +
  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' \
 -    'diff commandline1 expected'
 +    'test_cmp expected commandline1'
  
  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
@@@ -115,7 -70,6 +115,7 @@@ EO
  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 \
@@@ -150,34 -104,12 +150,34 @@@ 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>" \
                --to=nobody@example.com \
                --smtp-server="$(pwd)/fake.sendmail" \
 -              --no-validate \
 +              --novalidate \
                $patches longline.patch \
                2>errors
  '
@@@ -216,13 -148,15 +216,13 @@@ test_set_editor "$(pwd)/fake-editor
  
  test_expect_success '--compose works' '
        clean_fake_sendmail &&
 -      echo y | \
 -              GIT_SEND_EMAIL_NOTTY=1 \
 -              git send-email \
 -              --compose --subject foo \
 -              --from="Example <nobody@example.com>" \
 -              --to=nobody@example.com \
 -              --smtp-server="$(pwd)/fake.sendmail" \
 -              $patches \
 -              2>errors
 +      git send-email \
 +      --compose --subject foo \
 +      --from="Example <nobody@example.com>" \
 +      --to=nobody@example.com \
 +      --smtp-server="$(pwd)/fake.sendmail" \
 +      $patches \
 +      2>errors
  '
  
  test_expect_success 'first message is compose text' '
@@@ -233,18 -167,16 +233,18 @@@ test_expect_success 'second message is 
        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
@@@ -253,10 -185,10 +253,10 @@@ X-Mailer: X-MAILER-STRIN
  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 \
        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
  
  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_confirm () {
 +      echo y | \
 +              GIT_SEND_EMAIL_NOTTY=1 \
 +              git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --to=nobody@example.com \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              $@ \
 +              $patches | grep "Send this email"
 +}
 +
 +test_expect_success '--confirm=always' '
 +      test_confirm --confirm=always --suppress-cc=all
 +'
 +
 +test_expect_success '--confirm=auto' '
 +      test_confirm --confirm=auto
 +'
 +
 +test_expect_success '--confirm=cc' '
 +      test_confirm --confirm=cc
 +'
 +
 +test_expect_success '--confirm=compose' '
 +      test_confirm --confirm=compose --compose
 +'
 +
 +test_expect_success 'confirm by default (due to cc)' '
 +      CONFIRM=$(git config --get sendemail.confirm) &&
 +      git config --unset sendemail.confirm &&
 +      test_confirm &&
 +      git config sendemail.confirm $CONFIRM
 +'
 +
 +test_expect_success 'confirm by default (due to --compose)' '
 +      CONFIRM=$(git config --get sendemail.confirm) &&
 +      git config --unset sendemail.confirm &&
 +      test_confirm --suppress-cc=all --compose
 +      ret="$?"
 +      git config sendemail.confirm ${CONFIRM:-never}
 +      test $ret = "0"
  '
  
  test_expect_success '--compose adds MIME for utf8 body' '
         echo "echo utf8 body: àéìöú >>\"\$1\""
        ) >fake-editor-utf8 &&
        chmod +x fake-editor-utf8 &&
 -      echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \
 -        GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject foo \
          --from="Example <nobody@example.com>" \
@@@ -485,7 -263,9 +485,7 @@@ test_expect_success '--compose respect
         echo " echo utf8 body: àéìöú) >\"\$1\""
        ) >fake-editor-utf8-mime &&
        chmod +x fake-editor-utf8-mime &&
 -      echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \
 -        GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject foo \
          --from="Example <nobody@example.com>" \
  
  test_expect_success '--compose adds MIME for utf8 subject' '
        clean_fake_sendmail &&
 -      echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor\"" \
 -        GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject utf8-sübjëct \
          --from="Example <nobody@example.com>" \
        grep "^Subject: =?utf-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
  '
  
 +test_expect_success 'detects ambiguous reference/file conflict' '
 +      echo master > master &&
 +      git add master &&
 +      git commit -m"add master" &&
 +      test_must_fail git send-email --dry-run master 2>errors &&
 +      grep disambiguate errors
 +'
 +
 +test_expect_success 'feed two files' '
 +      rm -fr outdir &&
 +      git format-patch -2 -o outdir &&
 +      git send-email \
 +      --dry-run \
 +      --from="Example <nobody@example.com>" \
 +      --to=nobody@example.com \
 +      outdir/000?-*.patch 2>errors >out &&
 +      grep "^Subject: " out >subjects &&
 +      test "z$(sed -n -e 1p subjects)" = "zSubject: [PATCH 1/2] Second." &&
 +      test "z$(sed -n -e 2p subjects)" = "zSubject: [PATCH 2/2] add master"
 +'
 +
+ test_expect_success 'in-reply-to but no threading' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --in-reply-to="<in-reply-id@example.com>" \
+               --no-thread \
+               $patches |
+       grep "In-Reply-To: <in-reply-id@example.com>"
+ '
  test_done