Code

Tighten refspec processing
[git.git] / git-send-email.perl
index f9bd2e5a9176ccf7dd8e2ce15233d2d93f9222ec..e5d67f1b6c3237ff67673037be5ca8997cdb191d 100755 (executable)
@@ -100,6 +100,8 @@ Options:
 
    --envelope-sender   Specify the envelope sender used to send the emails.
 
+   --no-validate       Don't perform any sanity checks on patches.
+
 EOT
        exit(1);
 }
@@ -145,6 +147,7 @@ sub format_2822_time {
 
 my $have_email_valid = eval { require Email::Valid; 1 };
 my $smtp;
+my $auth;
 
 sub unique_email_list(@);
 sub cleanup_compose_files();
@@ -163,7 +166,9 @@ my $envelope_sender;
 
 my $repo = Git->repository();
 my $term = eval {
-       new Term::ReadLine 'git-send-email';
+       $ENV{"GIT_SEND_EMAIL_NOTTY"}
+               ? new Term::ReadLine 'git-send-email', \*STDIN, \*STDOUT
+               : new Term::ReadLine 'git-send-email';
 };
 if ($@) {
        $term = new FakeTerm "$@: going non-interactive";
@@ -176,6 +181,7 @@ my ($quiet, $dry_run) = (0, 0);
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
 my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_authpass, $smtp_ssl);
 my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
+my ($no_validate);
 
 my %config_bool_settings = (
     "thread" => [\$thread, 1],
@@ -221,6 +227,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
                    "thread!" => \$thread,
+                   "no-validate" => \$no_validate,
         );
 
 unless ($rc) {
@@ -313,6 +320,40 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
 
 ($sender) = expand_aliases($sender) if defined $sender;
 
+# 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) {
+               opendir(DH,$f)
+                       or die "Failed to opendir $f: $!";
+
+               push @files, grep { -f $_ } map { +$f . "/" . $_ }
+                               sort readdir(DH);
+
+       } elsif (-f $f) {
+               push @files, $f;
+
+       } else {
+               print STDERR "Skipping $f - not found.\n";
+       }
+}
+
+if (!$no_validate) {
+       foreach my $f (@files) {
+               my $error = validate_patch($f);
+               $error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+       }
+}
+
+if (@files) {
+       unless ($quiet) {
+               print $_,"\n" for (@files);
+       }
+} else {
+       print STDERR "\nNo patch files specified!\n\n";
+       usage();
+}
+
 my $prompting = 0;
 if (!defined $sender) {
        $sender = $repoauthor || $repocommitter;
@@ -366,8 +407,11 @@ if ($thread && !defined $initial_reply_to && $prompting) {
        } while (!defined $_);
 
        $initial_reply_to = $_;
-       $initial_reply_to =~ s/^\s+<?/</;
-       $initial_reply_to =~ s/>?\s+$/>/;
+}
+if (defined $initial_reply_to) {
+       $initial_reply_to =~ s/^\s*<?//;
+       $initial_reply_to =~ s/>?\s*$//;
+       $initial_reply_to = "<$initial_reply_to>" if $initial_reply_to ne '';
 }
 
 if (!defined $smtp_server) {
@@ -397,7 +441,7 @@ EOT
        close(C);
 
        my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
-       system($editor, $compose_filename);
+       system('sh', '-c', '$0 $@', $editor, $compose_filename);
 
        open(C2,">",$compose_filename . ".final")
                or die "Failed to open $compose_filename.final : " . $!;
@@ -421,35 +465,7 @@ EOT
                exit(0);
        }
 
-       @files = ($compose_filename . ".final");
-}
-
-
-# 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) {
-               opendir(DH,$f)
-                       or die "Failed to opendir $f: $!";
-
-               push @files, grep { -f $_ } map { +$f . "/" . $_ }
-                               sort readdir(DH);
-
-       } elsif (-f $f) {
-               push @files, $f;
-
-       } else {
-               print STDERR "Skipping $f - not found.\n";
-       }
-}
-
-if (@files) {
-       unless ($quiet) {
-               print $_,"\n" for (@files);
-       }
-} else {
-       print STDERR "\nNo patch files specified!\n\n";
-       usage();
+       @files = ($compose_filename . ".final", @files);
 }
 
 # Variables we set as part of the loop over files
@@ -514,11 +530,13 @@ $time = time - scalar $#files;
 
 sub unquote_rfc2047 {
        local ($_) = @_;
-       if (s/=\?utf-8\?q\?(.*)\?=/$1/g) {
+       my $encoding;
+       if (s/=\?([^?]+)\?q\?(.*)\?=/$2/g) {
+               $encoding = $1;
                s/_/ /g;
                s/=([0-9A-F]{2})/chr(hex($1))/eg;
        }
-       return "$_";
+       return wantarray ? ($_, $encoding) : $_;
 }
 
 # use the simplest quoting being able to handle the recipient
@@ -555,7 +573,11 @@ sub sanitize_address
 sub send_message
 {
        my @recipients = unique_email_list(@to);
-       @cc = (map { sanitize_address($_) } @cc);
+       @cc = (grep { my $cc = extract_valid_address($_);
+                     not grep { $cc eq $_ } @recipients
+                   }
+              map { sanitize_address($_) }
+              @cc);
        my $to = join (",\n\t", @recipients);
        @recipients = unique_email_list(@recipients,@cc,@bcclist);
        @recipients = (map { extract_valid_address($_) } @recipients);
@@ -571,7 +593,7 @@ sub send_message
                $ccline = "\nCc: $cc";
        }
        my $sanitized_sender = sanitize_address($sender);
-       make_message_id();
+       make_message_id() unless defined($message_id);
 
        my $header = "From: $sanitized_sender
 To: $to${ccline}
@@ -629,7 +651,7 @@ X-Mailer: git-send-email $gitversion
                }
 
                if ((defined $smtp_authuser) && (defined $smtp_authpass)) {
-                       $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
+                       $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message;
                }
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
@@ -641,7 +663,7 @@ X-Mailer: git-send-email $gitversion
        if ($quiet) {
                printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
        } else {
-               print (($dry_run ? "Dry-" : "")."OK. Log says:\nDate: $date\n");
+               print (($dry_run ? "Dry-" : "")."OK. Log says:\n");
                if ($smtp_server !~ m#^/#) {
                        print "Server: $smtp_server\n";
                        print "MAIL FROM:<$raw_from>\n";
@@ -649,7 +671,7 @@ X-Mailer: git-send-email $gitversion
                } else {
                        print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
                }
-               print "From: $sanitized_sender\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+               print $header, "\n";
                if ($smtp) {
                        print "Result: ", $smtp->code, ' ',
                                ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
@@ -667,6 +689,9 @@ foreach my $t (@files) {
        open(F,"<",$t) or die "can't open file $t";
 
        my $author = undef;
+       my $author_encoding;
+       my $has_content_type;
+       my $body_encoding;
        @cc = @initial_cc;
        @xh = ();
        my $input_format = undef;
@@ -692,12 +717,23 @@ foreach my $t (@files) {
                                                next if ($suppress_from);
                                        }
                                        elsif ($1 eq 'From') {
-                                               $author = unquote_rfc2047($2);
+                                               ($author, $author_encoding)
+                                                 = unquote_rfc2047($2);
                                        }
                                        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, $_;
                                }
@@ -756,6 +792,22 @@ foreach my $t (@files) {
 
        if (defined $author) {
                $message = "From: $author\n\n$message";
+               if (defined $author_encoding) {
+                       if ($has_content_type) {
+                               if ($body_encoding eq $author_encoding) {
+                                       # ok, we already have the right encoding
+                               }
+                               else {
+                                       # uh oh, we should re-encode
+                               }
+                       }
+                       else {
+                               push @xh,
+                                 'MIME-Version: 1.0',
+                                 "Content-Type: text/plain; charset=$author_encoding",
+                                 'Content-Transfer-Encoding: 8bit';
+                       }
+               }
        }
 
        send_message();
@@ -769,6 +821,7 @@ foreach my $t (@files) {
                        $references = "$message_id";
                }
        }
+       $message_id = undef;
 }
 
 if ($compose) {
@@ -798,3 +851,15 @@ sub unique_email_list(@) {
        }
        return @emails;
 }
+
+sub validate_patch {
+       my $fn = shift;
+       open(my $fh, '<', $fn)
+               or die "unable to open $fn: $!\n";
+       while (my $line = <$fh>) {
+               if (length($line) > 998) {
+                       return "$.: patch contains a line longer than 998 characters";
+               }
+       }
+       return undef;
+}