diff --git a/git-send-email.perl b/git-send-email.perl
index ec1428d961206f12e195274aea6ea3ae52374898..ecfa347b85bb6c3c5e6b1b3a1f83ac1acb069b66 100755 (executable)
--- a/git-send-email.perl
+++ b/git-send-email.perl
use strict;
use warnings;
use Term::ReadLine;
use strict;
use warnings;
use Term::ReadLine;
-use Mail::Sendmail qw(sendmail %mailcfg);
use Getopt::Long;
use Data::Dumper;
use Getopt::Long;
use Data::Dumper;
-use Email::Valid;
+use Net::SMTP;
+
+# most mail servers generate the Date: header, but not all...
+$ENV{LC_ALL} = 'C';
+use POSIX qw/strftime/;
+
+my $have_email_valid = eval { require Email::Valid; 1 };
+my $smtp;
sub unique_email_list(@);
sub cleanup_compose_files();
sub unique_email_list(@);
sub cleanup_compose_files();
my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
-my (@to,@cc,$initial_reply_to,$initial_subject,@files,$from,$compose);
+my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
# Behavior modification variables
-my ($chain_reply_to, $smtp_server) = (1, "localhost");
+my ($chain_reply_to, $smtp_server, $quiet, $suppress_from, $no_signed_off_cc) = (1, "localhost", 0, 0, 0);
# Example reply to:
#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
# Example reply to:
#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
"to=s" => \@to,
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
"to=s" => \@to,
+ "cc=s" => \@initial_cc,
"chain-reply-to!" => \$chain_reply_to,
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
"chain-reply-to!" => \$chain_reply_to,
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
+ "quiet" => \$quiet,
+ "suppress-from" => \$suppress_from,
+ "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
);
# Now, let's fill any that aren't set in with defaults:
);
# Now, let's fill any that aren't set in with defaults:
-open(GITVAR,"-|","git-var","-l")
- or die "Failed to open pipe from git-var: $!";
-
-my ($author,$committer);
-while(<GITVAR>) {
- chomp;
- my ($var,$data) = split /=/,$_,2;
- my @fields = split /\s+/, $data;
-
- my $ident = join(" ", @fields[0...(@fields-3)]);
+sub gitvar {
+ my ($var) = @_;
+ my $fh;
+ my $pid = open($fh, '-|');
+ die "$!" unless defined $pid;
+ if (!$pid) {
+ exec('git-var', $var) or die "$!";
+ }
+ my ($val) = <$fh>;
+ close $fh or die "$!";
+ chomp($val);
+ return $val;
+}
- if ($var eq 'GIT_AUTHOR_IDENT') {
- $author = $ident;
- } elsif ($var eq 'GIT_COMMITTER_IDENT') {
- $committer = $ident;
- }
+sub gitvar_ident {
+ my ($name) = @_;
+ my $val = gitvar($name);
+ my @field = split(/\s+/, $val);
+ return join(' ', @field[0...(@field-3)]);
}
}
-close(GITVAR);
+
+my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
+my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
my $prompting = 0;
if (!defined $from) {
my $prompting = 0;
if (!defined $from) {
# effort to have it be unique
open(C,">",$compose_filename)
or die "Failed to open for writing $compose_filename: $!";
# effort to have it be unique
open(C,">",$compose_filename)
or die "Failed to open for writing $compose_filename: $!";
- print C "From \n";
+ print C "From $from # This line is ignored.\n";
printf C "Subject: %s\n\n", $initial_subject;
printf C <<EOT;
GIT: Please enter your email below.
printf C "Subject: %s\n\n", $initial_subject;
printf C <<EOT;
GIT: Please enter your email below.
}
if (@files) {
}
if (@files) {
- print $_,"\n" for @files;
+ unless ($quiet) {
+ print $_,"\n" for (@files);
+ }
} else {
print <<EOT;
git-send-email [options] <file | directory> [... file | directory ]
} else {
print <<EOT;
git-send-email [options] <file | directory> [... file | directory ]
--to Specify the primary "To:" line of the email.
--to Specify the primary "To:" line of the email.
+ --cc Specify an initial "Cc:" list for the entire series
+ of emails.
+
--compose Use \$EDITOR to edit an introductory message for the
patch series.
--compose Use \$EDITOR to edit an introductory message for the
patch series.
email sent, rather than to the first email sent.
Defaults to on.
email sent, rather than to the first email sent.
Defaults to on.
+ --no-signed-off-cc Suppress the automatic addition of email addresses
+ that appear in a Signed-off-by: line, to the cc: list.
+ Note: Using this option is not recommended.
+
--smtp-server If set, specifies the outgoing SMTP server to use.
Defaults to localhost.
--smtp-server If set, specifies the outgoing SMTP server to use.
Defaults to localhost.
+ --suppress-from Supress sending emails to yourself if your address
+ appears in a From: line.
+
+ --quiet Make git-send-email less verbose. One line per email should be
+ all that is output.
+
Error: Please specify a file or a directory on the command line.
EOT
exit(1);
Error: Please specify a file or a directory on the command line.
EOT
exit(1);
# Variables we set as part of the loop over files
our ($message_id, $cc, %mail, $subject, $reply_to, $message);
# Variables we set as part of the loop over files
our ($message_id, $cc, %mail, $subject, $reply_to, $message);
+sub extract_valid_address {
+ my $address = shift;
+ if ($have_email_valid) {
+ return Email::Valid->address($address);
+ } else {
+ # less robust/correct than the monster regexp in Email::Valid,
+ # but still does a 99% job, and one less dependency
+ return ($address =~ /([^\"<>\s]+@[^<>\s]+)/);
+ }
+}
# Usually don't need to change anything below here.
# Usually don't need to change anything below here.
# 1 second since the last time we were called.
# We'll setup a template for the message id, using the "from" address:
# 1 second since the last time we were called.
# We'll setup a template for the message id, using the "from" address:
-my $message_id_from = Email::Valid->address($from);
+my $message_id_from = extract_valid_address($from);
my $message_id_template = "<%s-git-send-email-$message_id_from>";
sub make_message_id
{
my $message_id_template = "<%s-git-send-email-$message_id_from>";
sub make_message_id
{
- my $date = `date "+\%s"`;
- chomp($date);
+ my $date = time;
my $pseudo_rand = int (rand(4200));
$message_id = sprintf $message_id_template, "$date$pseudo_rand";
#print "new message id = $message_id\n"; # Was useful for debugging
my $pseudo_rand = int (rand(4200));
$message_id = sprintf $message_id_template, "$date$pseudo_rand";
#print "new message id = $message_id\n"; # Was useful for debugging
$cc = "";
$cc = "";
+$time = time - scalar $#files;
sub send_message
{
sub send_message
{
- my $to = join (", ", unique_email_list(@to));
-
- %mail = ( To => $to,
- From => $from,
- CC => $cc,
- Subject => $subject,
- Message => $message,
- 'Reply-to' => $from,
- 'In-Reply-To' => $reply_to,
- 'Message-ID' => $message_id,
- 'X-Mailer' => "git-send-email",
- );
-
- $mail{smtp} = $smtp_server;
- $mailcfg{mime} = 0;
-
- #print Data::Dumper->Dump([\%mail],[qw(*mail)]);
-
- sendmail(%mail) or die $Mail::Sendmail::error;
-
- print "OK. Log says:\n", $Mail::Sendmail::log;
- print "\n\n"
+ my @recipients = unique_email_list(@to);
+ my $to = join (",\n\t", @recipients);
+ @recipients = unique_email_list(@recipients,@cc);
+ my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
+
+ my $header = "From: $from
+To: $to
+Cc: $cc
+Subject: $subject
+Reply-To: $from
+Date: $date
+Message-Id: $message_id
+X-Mailer: git-send-email @@GIT_VERSION@@
+";
+ $header .= "In-Reply-To: $reply_to\n" if $reply_to;
+
+ $smtp ||= Net::SMTP->new( $smtp_server );
+ $smtp->mail( $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->dataend() or die $smtp->message;
+ $smtp->ok or die "Failed to send $subject\n".$smtp->message;
+
+ if ($quiet) {
+ printf "Sent %s\n", $subject;
+ } else {
+ print "OK. Log says:
+Date: $date
+Server: $smtp_server Port: 25
+From: $from
+Subject: $subject
+Cc: $cc
+To: $to
+
+Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+ }
}
}
-
$reply_to = $initial_reply_to;
make_message_id();
$subject = $initial_subject;
foreach my $t (@files) {
$reply_to = $initial_reply_to;
make_message_id();
$subject = $initial_subject;
foreach my $t (@files) {
- my $F = $t;
open(F,"<",$t) or die "can't open file $t";
open(F,"<",$t) or die "can't open file $t";
- @cc = ();
+ my $author_not_sender = undef;
+ @cc = @initial_cc;
my $found_mbox = 0;
my $header_done = 0;
$message = "";
my $found_mbox = 0;
my $header_done = 0;
$message = "";
$subject = $1;
} elsif (/^(Cc|From):\s+(.*)$/) {
$subject = $1;
} elsif (/^(Cc|From):\s+(.*)$/) {
+ if ($2 eq $from) {
+ next if ($suppress_from);
+ }
+ else {
+ $author_not_sender = $2;
+ }
printf("(mbox) Adding cc: %s from line '%s'\n",
printf("(mbox) Adding cc: %s from line '%s'\n",
- $2, $_);
+ $2, $_) unless $quiet;
push @cc, $2;
}
push @cc, $2;
}
# So let's support that, too.
if (@cc == 0) {
printf("(non-mbox) Adding cc: %s from line '%s'\n",
# So let's support that, too.
if (@cc == 0) {
printf("(non-mbox) Adding cc: %s from line '%s'\n",
- $_, $_);
+ $_, $_) unless $quiet;
push @cc, $_;
push @cc, $_;
}
} else {
$message .= $_;
}
} else {
$message .= $_;
- if (/^Signed-off-by: (.*)$/i) {
+ if (/^Signed-off-by: (.*)$/i && !$no_signed_off_cc) {
my $c = $1;
chomp $c;
push @cc, $c;
printf("(sob) Adding cc: %s from line '%s'\n",
my $c = $1;
chomp $c;
push @cc, $c;
printf("(sob) Adding cc: %s from line '%s'\n",
- $c, $_);
+ $c, $_) unless $quiet;
}
}
}
close F;
}
}
}
close F;
+ if (defined $author_not_sender) {
+ $message = "From: $author_not_sender\n\n$message";
+ }
$cc = join(", ", unique_email_list(@cc));
$cc = join(", ", unique_email_list(@cc));
}
}
-
+$smtp->quit if $smtp;
sub unique_email_list(@) {
my %seen;
my @emails;
foreach my $entry (@_) {
sub unique_email_list(@) {
my %seen;
my @emails;
foreach my $entry (@_) {
- my $clean = Email::Valid->address($entry);
+ my $clean = extract_valid_address($entry);
next if $seen{$clean}++;
push @emails, $entry;
}
next if $seen{$clean}++;
push @emails, $entry;
}