my $re_encoded_word = qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/;
# Variables we fill in automatically, or via prompting:
-my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
+my (@to,@cc,@xh,$envelope_sender,
$initial_in_reply_to,$reply_to,$initial_subject,@files,
- $author,$sender,$smtp_authpass,$annotate,$use_xmailer,$compose,$time);
-
-my $envelope_sender;
+ $author,$sender,$smtp_authpass,$annotate,$compose,$time);
+# Things we either get from config, *or* are overridden on the
+# command-line.
+my ($no_cc, $no_to, $no_bcc);
+my (@config_to, @getopt_to);
+my (@config_cc, @getopt_cc);
+my (@config_bcc, @getopt_bcc);
# Example reply to:
#$initial_in_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
}
# Variables with corresponding config settings
-my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc);
+my ($suppress_from, $signed_off_by_cc);
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 ($batch_size, $relogin_delay);
my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
-my ($validate, $confirm);
+my ($confirm);
my (@suppress_cc);
my ($auto_8bit_encoding);
my ($compose_encoding);
-my ($target_xfer_encoding);
-
+# Variables with corresponding config settings & hardcoded defaults
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
+my $thread = 1;
+my $chain_reply_to = 0;
+my $use_xmailer = 1;
+my $validate = 1;
+my $target_xfer_encoding = 'auto';
my %config_bool_settings = (
- "thread" => [\$thread, 1],
- "chainreplyto" => [\$chain_reply_to, 0],
- "suppressfrom" => [\$suppress_from, undef],
- "signedoffbycc" => [\$signed_off_by_cc, undef],
- "cccover" => [\$cover_cc, undef],
- "tocover" => [\$cover_to, undef],
- "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
- "validate" => [\$validate, 1],
- "multiedit" => [\$multiedit, undef],
- "annotate" => [\$annotate, undef],
- "xmailer" => [\$use_xmailer, 1]
+ "thread" => \$thread,
+ "chainreplyto" => \$chain_reply_to,
+ "suppressfrom" => \$suppress_from,
+ "signedoffbycc" => \$signed_off_by_cc,
+ "cccover" => \$cover_cc,
+ "tocover" => \$cover_to,
+ "signedoffcc" => \$signed_off_by_cc,
+ "validate" => \$validate,
+ "multiedit" => \$multiedit,
+ "annotate" => \$annotate,
+ "xmailer" => \$use_xmailer,
);
my %config_settings = (
"smtpauth" => \$smtp_auth,
"smtpbatchsize" => \$batch_size,
"smtprelogindelay" => \$relogin_delay,
- "to" => \@initial_to,
+ "to" => \@config_to,
"tocmd" => \$to_cmd,
- "cc" => \@initial_cc,
+ "cc" => \@config_cc,
"cccmd" => \$cc_cmd,
"aliasfiletype" => \$aliasfiletype,
- "bcc" => \@bcclist,
+ "bcc" => \@config_bcc,
"suppresscc" => \@suppress_cc,
"envelopesender" => \$envelope_sender,
"confirm" => \$confirm,
$SIG{TERM} = \&signal_handler;
$SIG{INT} = \&signal_handler;
+# Read our sendemail.* config
+sub read_config {
+ my ($prefix) = @_;
+
+ foreach my $setting (keys %config_bool_settings) {
+ my $target = $config_bool_settings{$setting};
+ my $v = Git::config_bool(@repo, "$prefix.$setting");
+ $$target = $v if defined $v;
+ }
+
+ foreach my $setting (keys %config_path_settings) {
+ my $target = $config_path_settings{$setting};
+ if (ref($target) eq "ARRAY") {
+ unless (@$target) {
+ my @values = Git::config_path(@repo, "$prefix.$setting");
+ @$target = @values if (@values && defined $values[0]);
+ }
+ }
+ else {
+ my $v = Git::config_path(@repo, "$prefix.$setting");
+ $$target = $v if defined $v;
+ }
+ }
+
+ foreach my $setting (keys %config_settings) {
+ my $target = $config_settings{$setting};
+ if (ref($target) eq "ARRAY") {
+ unless (@$target) {
+ my @values = Git::config(@repo, "$prefix.$setting");
+ @$target = @values if (@values && defined $values[0]);
+ }
+ }
+ else {
+ my $v = Git::config(@repo, "$prefix.$setting");
+ $$target = $v if defined $v;
+ }
+ }
+
+ if (!defined $smtp_encryption) {
+ my $enc = Git::config(@repo, "$prefix.smtpencryption");
+ if (defined $enc) {
+ $smtp_encryption = $enc;
+ } elsif (Git::config_bool(@repo, "$prefix.smtpssl")) {
+ $smtp_encryption = 'ssl';
+ }
+ }
+}
+
+$identity = Git::config(@repo, "sendemail.identity");
+read_config("sendemail.$identity") if defined $identity;
+read_config("sendemail");
+
# Begin by accumulating all the variables (defined above), that we will end up
# needing, first, from the command line:
"in-reply-to=s" => \$initial_in_reply_to,
"reply-to=s" => \$reply_to,
"subject=s" => \$initial_subject,
- "to=s" => \@initial_to,
+ "to=s" => \@getopt_to,
"to-cmd=s" => \$to_cmd,
"no-to" => \$no_to,
- "cc=s" => \@initial_cc,
+ "cc=s" => \@getopt_cc,
"no-cc" => \$no_cc,
- "bcc=s" => \@bcclist,
+ "bcc=s" => \@getopt_bcc,
"no-bcc" => \$no_bcc,
"chain-reply-to!" => \$chain_reply_to,
"no-chain-reply-to" => sub {$chain_reply_to = 0},
"relogin-delay=i" => \$relogin_delay,
);
+# Munge any "either config or getopt, not both" variables
+my @initial_to = @getopt_to ? @getopt_to : ($no_to ? () : @config_to);
+my @initial_cc = @getopt_cc ? @getopt_cc : ($no_cc ? () : @config_cc);
+my @initial_bcc = @getopt_bcc ? @getopt_bcc : ($no_bcc ? () : @config_bcc);
+
usage() if $help;
unless ($rc) {
usage();
"(via command-line or configuration option)\n")
if defined $relogin_delay and not defined $batch_size;
-# Now, let's fill any that aren't set in with defaults:
-
-sub read_config {
- my ($prefix) = @_;
-
- foreach my $setting (keys %config_bool_settings) {
- my $target = $config_bool_settings{$setting}->[0];
- $$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target);
- }
-
- foreach my $setting (keys %config_path_settings) {
- my $target = $config_path_settings{$setting};
- if (ref($target) eq "ARRAY") {
- unless (@$target) {
- my @values = Git::config_path(@repo, "$prefix.$setting");
- @$target = @values if (@values && defined $values[0]);
- }
- }
- else {
- $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target);
- }
- }
-
- foreach my $setting (keys %config_settings) {
- my $target = $config_settings{$setting};
- next if $setting eq "to" and defined $no_to;
- next if $setting eq "cc" and defined $no_cc;
- next if $setting eq "bcc" and defined $no_bcc;
- if (ref($target) eq "ARRAY") {
- unless (@$target) {
- my @values = Git::config(@repo, "$prefix.$setting");
- @$target = @values if (@values && defined $values[0]);
- }
- }
- else {
- $$target = Git::config(@repo, "$prefix.$setting") unless (defined $$target);
- }
- }
-
- if (!defined $smtp_encryption) {
- my $enc = Git::config(@repo, "$prefix.smtpencryption");
- if (defined $enc) {
- $smtp_encryption = $enc;
- } elsif (Git::config_bool(@repo, "$prefix.smtpssl")) {
- $smtp_encryption = 'ssl';
- }
- }
-}
-
-# read configuration from [sendemail "$identity"], fall back on [sendemail]
-$identity = Git::config(@repo, "sendemail.identity") unless (defined $identity);
-read_config("sendemail.$identity") if (defined $identity);
-read_config("sendemail");
-
-# fall back on builtin bool defaults
-foreach my $setting (values %config_bool_settings) {
- ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
-}
-
# 'default' encryption is none -- this only prevents a warning
$smtp_encryption = '' unless (defined $smtp_encryption);
if ($validate) {
foreach my $f (@files) {
unless (-p $f) {
- my $error = validate_patch($f);
+ my $error = validate_patch($f, $target_xfer_encoding);
$error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"),
$f, $error);
}
@initial_to = process_address_list(@initial_to);
@initial_cc = process_address_list(@initial_cc);
-@bcclist = process_address_list(@bcclist);
+@initial_bcc = process_address_list(@initial_bcc);
if ($thread && !defined $initial_in_reply_to && $prompting) {
$initial_in_reply_to = ask(
return File::Spec::Functions::file_name_is_absolute($path);
}
-# Returns 1 if the message was sent, and 0 otherwise.
-# In actuality, the whole program dies when there
-# is an error sending a message.
+# Prepares the email, then asks the user what to do.
+#
+# If the user chooses to send the email, it's sent and 1 is returned.
+# If the user chooses not to send the email, 0 is returned.
+# If the user decides they want to make further edits, -1 is returned and the
+# caller is expected to call send_message again after the edits are performed.
+#
+# If an error occurs sending the email, this just dies.
sub send_message {
my @recipients = unique_email_list(@to);
}
@cc);
my $to = join (",\n\t", @recipients);
- @recipients = unique_email_list(@recipients,@cc,@bcclist);
+ @recipients = unique_email_list(@recipients,@cc,@initial_bcc);
@recipients = (map { extract_valid_address_or_die($_) } @recipients);
my $date = format_2822_time($time++);
my $gitversion = '@@GIT_VERSION@@';
EOF
}
- # TRANSLATORS: Make sure to include [y] [n] [q] [a] in your
+ # TRANSLATORS: Make sure to include [y] [n] [e] [q] [a] in your
# translation. The program will only accept English input
# at this point.
- $_ = ask(__("Send this email? ([y]es|[n]o|[q]uit|[a]ll): "),
- valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+ $_ = ask(__("Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): "),
+ valid_re => qr/^(?:yes|y|no|n|edit|e|quit|q|all|a)/i,
default => $ask_default);
die __("Send this email reply required") unless defined $_;
if (/^n/i) {
return 0;
+ } elsif (/^e/i) {
+ return -1;
} elsif (/^q/i) {
cleanup_compose_files();
exit(0);
$subject = $initial_subject;
$message_num = 0;
-foreach my $t (@files) {
+# Prepares the email, prompts the user, sends it out
+# Returns 0 if an edit was done and the function should be called again, or 1
+# otherwise.
+sub process_file {
+ my ($t) = @_;
+
open my $fh, "<", $t or die sprintf(__("can't open file %s"), $t);
my $author = undef;
}
}
}
- if (defined $target_xfer_encoding) {
- $xfer_encoding = '8bit' if not defined $xfer_encoding;
- $message = apply_transfer_encoding(
- $message, $xfer_encoding, $target_xfer_encoding);
- $xfer_encoding = $target_xfer_encoding;
- }
- if (defined $xfer_encoding) {
- push @xh, "Content-Transfer-Encoding: $xfer_encoding";
- }
- if (defined $xfer_encoding or $has_content_type) {
- unshift @xh, 'MIME-Version: 1.0' unless $has_mime_version;
- }
+ $xfer_encoding = '8bit' if not defined $xfer_encoding;
+ ($message, $xfer_encoding) = apply_transfer_encoding(
+ $message, $xfer_encoding, $target_xfer_encoding);
+ push @xh, "Content-Transfer-Encoding: $xfer_encoding";
+ unshift @xh, 'MIME-Version: 1.0' unless $has_mime_version;
$needs_confirm = (
$confirm eq "always" or
}
my $message_was_sent = send_message();
+ if ($message_was_sent == -1) {
+ do_edit($t);
+ return 0;
+ }
# set up for the next message
if ($thread && $message_was_sent &&
undef $auth;
sleep($relogin_delay) if defined $relogin_delay;
}
+
+ return 1;
+}
+
+foreach my $t (@files) {
+ while (!process_file($t)) {
+ # user edited the file
+ }
}
# Execute a command (e.g. $to_cmd) to get a list of email addresses
$message = MIME::Base64::decode($message)
if ($from eq 'base64');
+ $to = ($message =~ /.{999,}/) ? 'quoted-printable' : '8bit'
+ if $to eq 'auto';
+
die __("cannot send message as 7bit")
if ($to eq '7bit' and $message =~ /[^[:ascii:]]/);
- return $message
+ return ($message, $to)
if ($to eq '7bit' or $to eq '8bit');
- return MIME::QuotedPrint::encode($message, "\n", 0)
+ return (MIME::QuotedPrint::encode($message, "\n", 0), $to)
if ($to eq 'quoted-printable');
- return MIME::Base64::encode($message, "\n")
+ return (MIME::Base64::encode($message, "\n"), $to)
if ($to eq 'base64');
die __("invalid transfer encoding");
}
}
sub validate_patch {
- my $fn = shift;
+ my ($fn, $xfer_encoding) = @_;
if ($repo) {
my $validate_hook = catfile(catdir($repo->repo_path(), 'hooks'),
return $hook_error if $hook_error;
}
- open(my $fh, '<', $fn)
- or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
- while (my $line = <$fh>) {
- if (length($line) > 998) {
- return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
+ # Any long lines will be automatically fixed if we use a suitable transfer
+ # encoding.
+ unless ($xfer_encoding =~ /^(?:auto|quoted-printable|base64)$/) {
+ open(my $fh, '<', $fn)
+ or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
+ while (my $line = <$fh>) {
+ if (length($line) > 998) {
+ return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
+ }
}
}
return;