use Term::ReadLine;
use Getopt::Long;
use Data::Dumper;
-use Net::SMTP;
+use Git;
# most mail servers generate the Date: header, but not all...
$ENV{LC_ALL} = 'C';
my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
+my (@to,@cc,@initial_cc,@bcclist,
+ $initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
# Example reply to:
#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+my $repo = Git->repository();
+
my $term = new Term::ReadLine 'git-send-email';
# Begin by accumulating all the variables (defined above), that we will end up
"subject=s" => \$initial_subject,
"to=s" => \@to,
"cc=s" => \@initial_cc,
+ "bcc=s" => \@bcclist,
"chain-reply-to!" => \$chain_reply_to,
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
"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:
+# Verify the user input
+
+foreach my $entry (@to) {
+ die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
+}
-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;
+foreach my $entry (@initial_cc) {
+ die "Comma in --cc entry: $entry'\n" unless $entry !~ m/,/;
}
-sub gitvar_ident {
- my ($name) = @_;
- my $val = gitvar($name);
- my @field = split(/\s+/, $val);
- return join(' ', @field[0...(@field-3)]);
+foreach my $entry (@bcclist) {
+ die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
}
-my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
-my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
+# Now, let's fill any that aren't set in with defaults:
+
+my ($author) = $repo->ident_person('author');
+my ($committer) = $repo->ident_person('committer');
my %aliases;
-chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`);
-chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`);
+my @alias_files = $repo->config('sendemail.aliasesfile');
+my $aliasfiletype = $repo->config('sendemail.aliasfiletype');
my %parse_alias = (
# multiline formats can be supported in the future
mutt => sub { my $fh = shift; while (<$fh>) {
}}}
);
-if (@alias_files && defined $parse_alias{$aliasfiletype}) {
+if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
foreach my $file (@alias_files) {
open my $fh, '<', $file or die "opening $file: $!\n";
$parse_alias{$aliasfiletype}->($fh);
@to = expand_aliases(@to);
@initial_cc = expand_aliases(@initial_cc);
+@bcclist = expand_aliases(@bcclist);
if (!defined $initial_subject && $compose) {
do {
--cc Specify an initial "Cc:" list for the entire series
of emails.
+ --bcc Specify a list of email addresses that should be Bcc:
+ on all the emails.
+
--compose Use \$EDITOR to edit an introductory message for the
patch series.
}
# Variables we set as part of the loop over files
-our ($message_id, $cc, %mail, $subject, $reply_to, $message);
+our ($message_id, $cc, %mail, $subject, $reply_to, $references, $message);
sub extract_valid_address {
my $address = shift;
+ my $local_part_regexp = '[^<>"\s@]+';
+ my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+';
+
+ # check for a local address:
+ return $address if ($address =~ /^($local_part_regexp)$/);
+
if ($have_email_valid) {
- return Email::Valid->address($address);
+ return scalar 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]+)/);
+ $address =~ /($local_part_regexp\@$domain_regexp)/;
+ return $1;
}
}
{
my @recipients = unique_email_list(@to);
my $to = join (",\n\t", @recipients);
- @recipients = unique_email_list(@recipients,@cc);
+ @recipients = unique_email_list(@recipients,@cc,@bcclist);
my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
my $gitversion = '@@GIT_VERSION@@';
if ($gitversion =~ m/..GIT_VERSION../) {
- $gitversion = `git --version`;
- chomp $gitversion;
- # keep only what's after the last space
- $gitversion =~ s/^.* //;
+ $gitversion = Git::version();
}
my $header = "From: $from
Message-Id: $message_id
X-Mailer: git-send-email $gitversion
";
- $header .= "In-Reply-To: $reply_to\n" if $reply_to;
+ if ($reply_to) {
+
+ $header .= "In-Reply-To: $reply_to\n";
+ $header .= "References: $references\n";
+ }
if ($smtp_server =~ m#^/#) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
- exec($smtp_server,'-i',@recipients) or die $!;
+ exec($smtp_server,'-i',
+ map { extract_valid_address($_) }
+ @recipients) or die $!;
}
print $sm "$header\n$message";
close $sm or die $?;
} else {
+ require Net::SMTP;
$smtp ||= Net::SMTP->new( $smtp_server );
$smtp->mail( $from ) or die $smtp->message;
$smtp->to( @recipients ) or die $smtp->message;
}
$reply_to = $initial_reply_to;
+$references = $initial_reply_to || '';
make_message_id();
$subject = $initial_subject;
# set up for the next message
if ($chain_reply_to || length($reply_to) == 0) {
$reply_to = $message_id;
+ if (length $references > 0) {
+ $references .= " $message_id";
+ } else {
+ $references = "$message_id";
+ }
}
make_message_id();
}
my @emails;
foreach my $entry (@_) {
- my $clean = extract_valid_address($entry);
- next if $seen{$clean}++;
- push @emails, $entry;
+ if (my $clean = extract_valid_address($entry)) {
+ $seen{$clean} ||= 0;
+ next if $seen{$clean}++;
+ push @emails, $entry;
+ } else {
+ print STDERR "W: unable to extract a valid address",
+ " from: $entry\n";
+ }
}
return @emails;
}