Merge branch 'ew/send-email-mutt-alias-fix'
authorJunio C Hamano <gitster@pobox.com>
Wed, 20 Jan 2016 19:43:28 +0000 (11:43 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 20 Jan 2016 19:43:28 +0000 (11:43 -0800)
"git send-email" was confused by escaped quotes stored in the alias
files saved by "mutt", which has been corrected.

* ew/send-email-mutt-alias-fix:
git-send-email: do not double-escape quotes from mutt

1  2 
git-send-email.perl
t/t9001-send-email.sh
diff --combined git-send-email.perl
index 6caa5b563fafb09cee5eb8f35283e7b9fa967439,6c140e4ef6014c180257ce544e20a5bba9385447..d356901348042a7f2ec67211857e4d551d169d52
@@@ -46,7 -46,6 +46,7 @@@ package main
  sub usage {
        print <<EOT;
  git send-email [options] <file | directory | rev-list options >
 +git send-email --dump-aliases
  
    Composing:
      --from                  <str>  * Email From:
@@@ -76,8 -75,6 +76,8 @@@
                                       Pass an empty string to disable certificate
                                       verification.
      --smtp-domain           <str>  * The domain name sent to HELO/EHLO handshake
 +    --smtp-auth             <str>  * Space-separated list of allowed AUTH mechanisms.
 +                                     This setting forces to use one of the listed mechanisms.
      --smtp-debug            <0|1>  * Disable, enable Net::SMTP debug.
  
    Automating:
                                       `git format-patch` ones.
      --force                        * Send even if safety checks would prevent it.
  
 +  Information:
 +    --dump-aliases                 * Dump configured aliases and exit.
 +
  EOT
        exit(1);
  }
@@@ -184,7 -178,6 +184,7 @@@ my ($quiet, $dry_run) = (0, 0)
  my $format_patch;
  my $compose_filename;
  my $force = 0;
 +my $dump_aliases = 0;
  
  # Handle interactive edition of files.
  my $multiedit;
@@@ -215,7 -208,7 +215,7 @@@ my ($cover_cc, $cover_to)
  my ($to_cmd, $cc_cmd);
  my ($smtp_server, $smtp_server_port, @smtp_server_options);
  my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
 -my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
 +my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
  my ($validate, $confirm);
  my (@suppress_cc);
  my ($auto_8bit_encoding);
@@@ -244,8 -237,8 +244,8 @@@ my %config_settings = 
      "smtpserveroption" => \@smtp_server_options,
      "smtpuser" => \$smtp_authuser,
      "smtppass" => \$smtp_authpass,
 -    "smtpsslcertpath" => \$smtp_ssl_cert_path,
      "smtpdomain" => \$smtp_domain,
 +    "smtpauth" => \$smtp_auth,
      "to" => \@initial_to,
      "tocmd" => \$to_cmd,
      "cc" => \@initial_cc,
  
  my %config_path_settings = (
      "aliasesfile" => \@alias_files,
 +    "smtpsslcertpath" => \$smtp_ssl_cert_path,
  );
  
  # Handle Uncouth Termination
@@@ -296,11 -288,6 +296,11 @@@ $SIG{INT}  = \&signal_handler
  
  my $help;
  my $rc = GetOptions("h" => \$help,
 +                    "dump-aliases" => \$dump_aliases);
 +usage() unless $rc;
 +die "--dump-aliases incompatible with other options\n"
 +    if !$help and $dump_aliases and @ARGV;
 +$rc = GetOptions(
                    "sender|from=s" => \$sender,
                      "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
                    "smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path,
                    "smtp-debug:i" => \$debug_net_smtp,
                    "smtp-domain:s" => \$smtp_domain,
 +                  "smtp-auth=s" => \$smtp_auth,
                    "identity=s" => \$identity,
                    "annotate!" => \$annotate,
                    "no-annotate" => sub {$annotate = 0},
@@@ -474,11 -460,25 +474,11 @@@ my ($repoauthor, $repocommitter)
  ($repoauthor) = Git::ident_person(@repo, 'author');
  ($repocommitter) = Git::ident_person(@repo, 'committer');
  
 -# Verify the user input
 -
 -foreach my $entry (@initial_to) {
 -      die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
 -}
 -
 -foreach my $entry (@initial_cc) {
 -      die "Comma in --cc entry: $entry'\n" unless $entry !~ m/,/;
 -}
 -
 -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]);
 +              return Git::parse_mailboxes($_[0]);
        }
  }
  
@@@ -487,45 -487,19 +487,50 @@@ sub split_addrs 
  }
  
  my %aliases;
 +
 +sub parse_sendmail_alias {
 +      local $_ = shift;
 +      if (/"/) {
 +              print STDERR "warning: sendmail alias with quotes is not supported: $_\n";
 +      } elsif (/:include:/) {
 +              print STDERR "warning: `:include:` not supported: $_\n";
 +      } elsif (/[\/|]/) {
 +              print STDERR "warning: `/file` or `|pipe` redirection not supported: $_\n";
 +      } elsif (/^(\S+?)\s*:\s*(.+)$/) {
 +              my ($alias, $addr) = ($1, $2);
 +              $aliases{$alias} = [ split_addrs($addr) ];
 +      } else {
 +              print STDERR "warning: sendmail line is not recognized: $_\n";
 +      }
 +}
 +
 +sub parse_sendmail_aliases {
 +      my $fh = shift;
 +      my $s = '';
 +      while (<$fh>) {
 +              chomp;
 +              next if /^\s*$/ || /^\s*#/;
 +              $s .= $_, next if $s =~ s/\\$// || s/^\s+//;
 +              parse_sendmail_alias($s) if $s;
 +              $s = $_;
 +      }
 +      $s =~ s/\\$//; # silently tolerate stray '\' on last line
 +      parse_sendmail_alias($s) if $s;
 +}
 +
  my %parse_alias = (
        # multiline formats can be supported in the future
        mutt => sub { my $fh = shift; while (<$fh>) {
                if (/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) {
                        my ($alias, $addr) = ($1, $2);
                        $addr =~ s/#.*$//; # mutt allows # comments
-                        # commas delimit multiple addresses
-                       $aliases{$alias} = [ split_addrs($addr) ];
+                       # commas delimit multiple addresses
+                       my @addr = split_addrs($addr);
+                       # quotes may be escaped in the file,
+                       # unescape them so we do not double-escape them later.
+                       s/\\"/"/g foreach @addr;
+                       $aliases{$alias} = \@addr
                }}},
        mailrc => sub { my $fh = shift; while (<$fh>) {
                if (/^alias\s+(\S+)\s+(.*)$/) {
                               $aliases{$alias} = [ split_addrs($addr) ];
                          }
                      } },
 -
 +      sendmail => \&parse_sendmail_aliases,
        gnus => sub { my $fh = shift; while (<$fh>) {
                if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
                        $aliases{$1} = [ $2 ];
@@@ -561,10 -535,7 +566,10 @@@ if (@alias_files and $aliasfiletype an
        }
  }
  
 -($sender) = expand_aliases($sender) if defined $sender;
 +if ($dump_aliases) {
 +    print "$_\n" for (sort keys %aliases);
 +    exit(0);
 +}
  
  # is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if
  # $f is a revision list specification to be passed to format-patch.
@@@ -810,10 -781,7 +815,10 @@@ if (!$force) 
        }
  }
  
 -if (!defined $sender) {
 +if (defined $sender) {
 +      $sender =~ s/^\s+|\s+$//g;
 +      ($sender) = expand_aliases($sender);
 +} else {
        $sender = $repoauthor || $repocommitter || '';
  }
  
@@@ -845,9 -813,12 +850,9 @@@ sub expand_one_alias 
        return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
  }
  
 -@initial_to = expand_aliases(@initial_to);
 -@initial_to = validate_address_list(sanitize_address_list(@initial_to));
 -@initial_cc = expand_aliases(@initial_cc);
 -@initial_cc = validate_address_list(sanitize_address_list(@initial_cc));
 -@bcclist = expand_aliases(@bcclist);
 -@bcclist = validate_address_list(sanitize_address_list(@bcclist));
 +@initial_to = process_address_list(@initial_to);
 +@initial_cc = process_address_list(@initial_cc);
 +@bcclist = process_address_list(@bcclist);
  
  if ($thread && !defined $initial_reply_to && $prompting) {
        $initial_reply_to = ask(
@@@ -1040,17 -1011,15 +1045,17 @@@ sub sanitize_address 
                return $recipient;
        }
  
 +      # remove non-escaped quotes
 +      $recipient_name =~ s/(^|[^\\])"/$1/g;
 +
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
 -              $recipient_name =~ s/^"(.*)"$/$1/;
                $recipient_name = quote_rfc2047($recipient_name);
        }
  
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
 -              $recipient_name =~ s/(["\\\r])/\\$1/g;
 +              $recipient_name =~ s/([\\\r])/\\$1/g;
                $recipient_name = qq["$recipient_name"];
        }
  
@@@ -1062,14 -1031,6 +1067,14 @@@ sub sanitize_address_list 
        return (map { sanitize_address($_) } @_);
  }
  
 +sub process_address_list {
 +      my @addr_list = map { parse_address_line($_) } @_;
 +      @addr_list = expand_aliases(@addr_list);
 +      @addr_list = sanitize_address_list(@addr_list);
 +      @addr_list = validate_address_list(@addr_list);
 +      return @addr_list;
 +}
 +
  # Returns the local Fully Qualified Domain Name (FQDN) if available.
  #
  # Tightly configured MTAa require that a caller sends a real DNS
@@@ -1149,12 -1110,6 +1154,12 @@@ sub smtp_auth_maybe 
                Authen::SASL->import(qw(Perl));
        };
  
 +      # Check mechanism naming as defined in:
 +      # https://tools.ietf.org/html/rfc4422#page-8
 +      if ($smtp_auth && $smtp_auth !~ /^(\b[A-Z0-9-_]{1,20}\s*)*$/) {
 +              die "invalid smtp auth: '${smtp_auth}'";
 +      }
 +
        # TODO: Authentication may fail not because credentials were
        # invalid but due to other reasons, in which we should not
        # reject credentials.
                'password' => $smtp_authpass
        }, sub {
                my $cred = shift;
 +
 +              if ($smtp_auth) {
 +                      my $sasl = Authen::SASL->new(
 +                              mechanism => $smtp_auth,
 +                              callback => {
 +                                      user => $cred->{'username'},
 +                                      pass => $cred->{'password'},
 +                                      authname => $cred->{'username'},
 +                              }
 +                      );
 +
 +                      return !!$smtp->auth($sasl);
 +              }
 +
                return !!$smtp->auth($cred->{'username'}, $cred->{'password'});
        });
  
@@@ -1211,7 -1152,8 +1216,7 @@@ sub ssl_verify_params 
                return (SSL_verify_mode => SSL_VERIFY_PEER(),
                        SSL_ca_file => $smtp_ssl_cert_path);
        } else {
 -              print STDERR "Not using SSL_VERIFY_PEER because the CA path does not exist.\n";
 -              return (SSL_verify_mode => SSL_VERIFY_NONE());
 +              die "CA path \"$smtp_ssl_cert_path\" does not exist";
        }
  }
  
@@@ -1332,13 -1274,6 +1337,13 @@@ Message-Id: $message_i
                        require Net::SMTP::SSL;
                        $smtp_domain ||= maildomain();
                        require IO::Socket::SSL;
 +
 +                      # Suppress "variable accessed once" warning.
 +                      {
 +                              no warnings 'once';
 +                              $IO::Socket::SSL::DEBUG = 1;
 +                      }
 +
                        # Net::SMTP::SSL->new() does not forward any SSL options
                        IO::Socket::SSL::set_client_defaults(
                                ssl_verify_params());
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
 -              $smtp->datasend("$header\n$message") or die $smtp->message;
 +              $smtp->datasend("$header\n") or die $smtp->message;
 +              my @lines = split /^/, $message;
 +              foreach my $line (@lines) {
 +                      $smtp->datasend("$line") or die $smtp->message;
 +              }
                $smtp->dataend() or die $smtp->message;
                $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
        }
@@@ -1609,8 -1540,8 +1614,8 @@@ foreach my $t (@files) 
                ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
        $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
  
 -      @to = validate_address_list(sanitize_address_list(@to));
 -      @cc = validate_address_list(sanitize_address_list(@cc));
 +      @to = process_address_list(@to);
 +      @cc = process_address_list(@cc);
  
        @to = (@initial_to, @to);
        @cc = (@initial_cc, @cc);
diff --combined t/t9001-send-email.sh
index 3c49536e0e8d2ae0e046b498ad6efaccab8e9f1b,ce94c5a4cb871e712907cc3565a7cf104a2b3d24..834d91a691556530cd85cc34b2e3f0330764d253
@@@ -312,19 -312,13 +312,19 @@@ test_expect_success $PREREQ,!AUTOIDENT 
        )
  '
  
 +test_expect_success $PREREQ 'setup tocmd and cccmd scripts' '
 +      write_script tocmd-sed <<-\EOF &&
 +      sed -n -e "s/^tocmd--//p" "$1"
 +      EOF
 +      write_script cccmd-sed <<-\EOF
 +      sed -n -e "s/^cccmd--//p" "$1"
 +      EOF
 +'
 +
  test_expect_success $PREREQ 'tocmd works' '
        clean_fake_sendmail &&
        cp $patches tocmd.patch &&
        echo tocmd--tocmd@example.com >>tocmd.patch &&
 -      write_script tocmd-sed <<-\EOF &&
 -      sed -n -e "s/^tocmd--//p" "$1"
 -      EOF
        git send-email \
                --from="Example <nobody@example.com>" \
                --to-cmd=./tocmd-sed \
@@@ -338,6 -332,9 +338,6 @@@ test_expect_success $PREREQ 'cccmd work
        clean_fake_sendmail &&
        cp $patches cccmd.patch &&
        echo "cccmd--  cccmd@example.com" >>cccmd.patch &&
 -      write_script cccmd-sed <<-\EOF &&
 -      sed -n -e "s/^cccmd--//p" "$1"
 -      EOF
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
@@@ -522,12 -519,6 +522,12 @@@ Result: O
  EOF
  "
  
 +replace_variable_fields () {
 +      sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
 +              -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
 +              -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/"
 +}
 +
  test_suppression () {
        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/" \
 +              $patches | replace_variable_fields \
                >actual-suppress-$1${2+"-$2"} &&
        test_cmp expected-suppress-$1${2+"-$2"} actual-suppress-$1${2+"-$2"}
  }
@@@ -1527,6 -1521,21 +1527,21 @@@ test_expect_success $PREREQ 'cccover ad
        test_cover_addresses "Cc"
  '
  
+ test_expect_success $PREREQ 'escaped quotes in sendemail.aliasfiletype=mutt' '
+       clean_fake_sendmail &&
+       echo "alias sbd \\\"Dot U. Sir\\\" <somebody@example.org>" >.mutt &&
+       git config --replace-all sendemail.aliasesfile "$(pwd)/.mutt" &&
+       git config sendemail.aliasfiletype mutt &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --to=sbd \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               outdir/0001-*.patch \
+               2>errors >out &&
+       grep "^!somebody@example\.org!$" commandline1 &&
+       grep -F "To: \"Dot U. Sir\" <somebody@example.org>" out
+ '
  test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' '
        clean_fake_sendmail &&
        echo "alias sbd  somebody@example.org" >.mailrc &&
  
  test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' '
        clean_fake_sendmail &&
 -      echo "alias sbd  someone@example.org" >~/.mailrc &&
 +      echo "alias sbd  someone@example.org" >"$HOME/.mailrc" &&
        git config --replace-all sendemail.aliasesfile "~/.mailrc" &&
        git config sendemail.aliasfiletype mailrc &&
        git send-email \
        grep "^!someone@example\.org!$" commandline1
  '
  
 +test_dump_aliases () {
 +      msg="$1" && shift &&
 +      filetype="$1" && shift &&
 +      printf '%s\n' "$@" >expect &&
 +      cat >.tmp-email-aliases &&
 +
 +      test_expect_success $PREREQ "$msg" '
 +              clean_fake_sendmail && rm -fr outdir &&
 +              git config --replace-all sendemail.aliasesfile \
 +                      "$(pwd)/.tmp-email-aliases" &&
 +              git config sendemail.aliasfiletype "$filetype" &&
 +              git send-email --dump-aliases 2>errors >actual &&
 +              test_cmp expect actual
 +      '
 +}
 +
 +test_dump_aliases '--dump-aliases sendmail format' \
 +      'sendmail' \
 +      'abgroup' \
 +      'alice' \
 +      'bcgrp' \
 +      'bob' \
 +      'chloe' <<-\EOF
 +      alice: Alice W Land <awol@example.com>
 +      bob: Robert Bobbyton <bob@example.com>
 +      chloe: chloe@example.com
 +      abgroup: alice, bob
 +      bcgrp: bob, chloe, Other <o@example.com>
 +      EOF
 +
 +test_dump_aliases '--dump-aliases mutt format' \
 +      'mutt' \
 +      'alice' \
 +      'bob' \
 +      'chloe' \
 +      'donald' <<-\EOF
 +      alias alice Alice W Land <awol@example.com>
 +      alias donald Donald C Carlton <donc@example.com>
 +      alias bob Robert Bobbyton <bob@example.com>
 +      alias chloe chloe@example.com
 +      EOF
 +
 +test_dump_aliases '--dump-aliases mailrc format' \
 +      'mailrc' \
 +      'alice' \
 +      'bob' \
 +      'chloe' \
 +      'eve' <<-\EOF
 +      alias alice   Alice W Land <awol@example.com>
 +      alias eve     Eve <eve@example.com>
 +      alias bob     Robert Bobbyton <bob@example.com>
 +      alias chloe   chloe@example.com
 +      EOF
 +
 +test_dump_aliases '--dump-aliases pine format' \
 +      'pine' \
 +      'alice' \
 +      'bob' \
 +      'chloe' \
 +      'eve' <<-\EOF
 +      alice   Alice W Land    <awol@example.com>
 +      eve     Eve     <eve@example.com>
 +      bob     Robert  Bobbyton <bob@example.com>
 +      chloe           chloe@example.com
 +      EOF
 +
 +test_dump_aliases '--dump-aliases gnus format' \
 +      'gnus' \
 +      'alice' \
 +      'bob' \
 +      'chloe' \
 +      'eve' <<-\EOF
 +      (define-mail-alias "alice" "awol@example.com")
 +      (define-mail-alias "eve" "eve@example.com")
 +      (define-mail-alias "bob" "bob@example.com")
 +      (define-mail-alias "chloe" "chloe@example.com")
 +      EOF
 +
 +test_expect_success '--dump-aliases must be used alone' '
 +      test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting
 +'
 +
 +test_sendmail_aliases () {
 +      msg="$1" && shift &&
 +      expect="$@" &&
 +      cat >.tmp-email-aliases &&
 +
 +      test_expect_success $PREREQ "$msg" '
 +              clean_fake_sendmail && rm -fr outdir &&
 +              git format-patch -1 -o outdir &&
 +              git config --replace-all sendemail.aliasesfile \
 +                      "$(pwd)/.tmp-email-aliases" &&
 +              git config sendemail.aliasfiletype sendmail &&
 +              git send-email \
 +                      --from="Example <nobody@example.com>" \
 +                      --to=alice --to=bcgrp \
 +                      --smtp-server="$(pwd)/fake.sendmail" \
 +                      outdir/0001-*.patch \
 +                      2>errors >out &&
 +              for i in $expect
 +              do
 +                      grep "^!$i!$" commandline1 || return 1
 +              done
 +      '
 +}
 +
 +test_sendmail_aliases 'sendemail.aliasfiletype=sendmail' \
 +      'awol@example\.com' \
 +      'bob@example\.com' \
 +      'chloe@example\.com' \
 +      'o@example\.com' <<-\EOF
 +      alice: Alice W Land <awol@example.com>
 +      bob: Robert Bobbyton <bob@example.com>
 +      # this is a comment
 +         # this is also a comment
 +      chloe: chloe@example.com
 +      abgroup: alice, bob
 +      bcgrp: bob, chloe, Other <o@example.com>
 +      EOF
 +
 +test_sendmail_aliases 'sendmail aliases line folding' \
 +      alice1 \
 +      bob1 bob2 \
 +      chuck1 chuck2 \
 +      darla1 darla2 darla3 \
 +      elton1 elton2 elton3 \
 +      fred1 fred2 \
 +      greg1 <<-\EOF
 +      alice: alice1
 +      bob: bob1,\
 +      bob2
 +      chuck: chuck1,
 +          chuck2
 +      darla: darla1,\
 +      darla2,
 +          darla3
 +      elton: elton1,
 +          elton2,\
 +      elton3
 +      fred: fred1,\
 +          fred2
 +      greg: greg1
 +      bcgrp: bob, chuck, darla, elton, fred, greg
 +      EOF
 +
 +test_sendmail_aliases 'sendmail aliases tolerate bogus line folding' \
 +      alice1 bob1 <<-\EOF
 +          alice: alice1
 +      bcgrp: bob1\
 +      EOF
 +
 +test_sendmail_aliases 'sendmail aliases empty' alice bcgrp <<-\EOF
 +      EOF
 +
 +test_expect_success $PREREQ 'alias support in To header' '
 +      clean_fake_sendmail &&
 +      echo "alias sbd  someone@example.org" >.mailrc &&
 +      test_config sendemail.aliasesfile ".mailrc" &&
 +      test_config sendemail.aliasfiletype mailrc &&
 +      git format-patch --stdout -1 --to=sbd >aliased.patch &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              aliased.patch \
 +              2>errors >out &&
 +      grep "^!someone@example\.org!$" commandline1
 +'
 +
 +test_expect_success $PREREQ 'alias support in Cc header' '
 +      clean_fake_sendmail &&
 +      echo "alias sbd  someone@example.org" >.mailrc &&
 +      test_config sendemail.aliasesfile ".mailrc" &&
 +      test_config sendemail.aliasfiletype mailrc &&
 +      git format-patch --stdout -1 --cc=sbd >aliased.patch &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              aliased.patch \
 +              2>errors >out &&
 +      grep "^!someone@example\.org!$" commandline1
 +'
 +
 +test_expect_success $PREREQ 'tocmd works with aliases' '
 +      clean_fake_sendmail &&
 +      echo "alias sbd  someone@example.org" >.mailrc &&
 +      test_config sendemail.aliasesfile ".mailrc" &&
 +      test_config sendemail.aliasfiletype mailrc &&
 +      git format-patch --stdout -1 >tocmd.patch &&
 +      echo tocmd--sbd >>tocmd.patch &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --to-cmd=./tocmd-sed \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              tocmd.patch \
 +              2>errors >out &&
 +      grep "^!someone@example\.org!$" commandline1
 +'
 +
 +test_expect_success $PREREQ 'cccmd works with aliases' '
 +      clean_fake_sendmail &&
 +      echo "alias sbd  someone@example.org" >.mailrc &&
 +      test_config sendemail.aliasesfile ".mailrc" &&
 +      test_config sendemail.aliasfiletype mailrc &&
 +      git format-patch --stdout -1 >cccmd.patch &&
 +      echo cccmd--sbd >>cccmd.patch &&
 +      git send-email \
 +              --from="Example <nobody@example.com>" \
 +              --cc-cmd=./cccmd-sed \
 +              --smtp-server="$(pwd)/fake.sendmail" \
 +              cccmd.patch \
 +              2>errors >out &&
 +      grep "^!someone@example\.org!$" commandline1
 +'
 +
  do_xmailer_test () {
        expected=$1 params=$2 &&
        git format-patch -1 &&
@@@ -1802,72 -1597,4 +1817,72 @@@ test_expect_success $PREREQ '--[no-]xma
        do_xmailer_test 1 "--xmailer"
  '
  
 +test_expect_success $PREREQ 'setup expected-list' '
 +      git send-email \
 +      --dry-run \
 +      --from="Example <from@example.com>" \
 +      --to="To 1 <to1@example.com>" \
 +      --to="to2@example.com" \
 +      --to="to3@example.com" \
 +      --cc="Cc 1 <cc1@example.com>" \
 +      --cc="Cc2 <cc2@example.com>" \
 +      --bcc="bcc1@example.com" \
 +      --bcc="bcc2@example.com" \
 +      0001-add-master.patch | replace_variable_fields \
 +      >expected-list
 +'
 +
 +test_expect_success $PREREQ 'use email list in --cc --to and --bcc' '
 +      git send-email \
 +      --dry-run \
 +      --from="Example <from@example.com>" \
 +      --to="To 1 <to1@example.com>, to2@example.com" \
 +      --to="to3@example.com" \
 +      --cc="Cc 1 <cc1@example.com>, Cc2 <cc2@example.com>" \
 +      --bcc="bcc1@example.com, bcc2@example.com" \
 +      0001-add-master.patch | replace_variable_fields \
 +      >actual-list &&
 +      test_cmp expected-list actual-list
 +'
 +
 +test_expect_success $PREREQ 'aliases work with email list' '
 +      echo "alias to2 to2@example.com" >.mutt &&
 +      echo "alias cc1 Cc 1 <cc1@example.com>" >>.mutt &&
 +      test_config sendemail.aliasesfile ".mutt" &&
 +      test_config sendemail.aliasfiletype mutt &&
 +      git send-email \
 +      --dry-run \
 +      --from="Example <from@example.com>" \
 +      --to="To 1 <to1@example.com>, to2, to3@example.com" \
 +      --cc="cc1, Cc2 <cc2@example.com>" \
 +      --bcc="bcc1@example.com, bcc2@example.com" \
 +      0001-add-master.patch | replace_variable_fields \
 +      >actual-list &&
 +      test_cmp expected-list actual-list
 +'
 +
 +test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
 +      echo "alias to2 to2@example.com" >.mutt &&
 +      echo "alias cc1 Cc 1 <cc1@example.com>" >>.mutt &&
 +      test_config sendemail.aliasesfile ".mutt" &&
 +      test_config sendemail.aliasfiletype mutt &&
 +      TO1=$(echo "QTo 1 <to1@example.com>" | q_to_tab) &&
 +      TO2=$(echo "QZto2" | qz_to_tab_space) &&
 +      CC1=$(echo "cc1" | append_cr) &&
 +      BCC1=$(echo "Q bcc1@example.com Q" | q_to_nul) &&
 +      git send-email \
 +      --dry-run \
 +      --from="        Example <from@example.com>" \
 +      --to="$TO1" \
 +      --to="$TO2" \
 +      --to="  to3@example.com   " \
 +      --cc="$CC1" \
 +      --cc="Cc2 <cc2@example.com>" \
 +      --bcc="$BCC1" \
 +      --bcc="bcc2@example.com" \
 +      0001-add-master.patch | replace_variable_fields \
 +      >actual-list &&
 +      test_cmp expected-list actual-list
 +'
 +
  test_done