codekingpro/portable-devtools
114k
1#!/usr/bin/perl2 3# BEGIN RUNTIME_PREFIX generated code.4#5# This finds our Git::* libraries relative to the script's runtime path.6sub __git_system_path {7 my ($relpath) = @_;8 my $gitexecdir_relative = 'libexec/git-core';9 10 # GIT_EXEC_PATH is supplied by `git` or the test suite.11 my $exec_path;12 if (exists $ENV{GIT_EXEC_PATH}) {13 $exec_path = $ENV{GIT_EXEC_PATH};14 } else {15 # This can happen if this script is being directly invoked instead of run16 # by "git".17 require FindBin;18 $exec_path = $FindBin::Bin;19 }20 21 # Trim off the relative gitexecdir path to get the system path.22 (my $prefix = $exec_path) =~ s/\Q$gitexecdir_relative\E$//;23 24 require File::Spec;25 return File::Spec->catdir($prefix, $relpath);26}27 28BEGIN {29 use lib split /;/,30 (31 $ENV{GITPERLLIB} ||32 do {33 my $perllibdir = __git_system_path('share/perl5');34 (-e $perllibdir) || die("Invalid system path ($relpath): $path");35 $perllibdir;36 }37 );38 39 # Export the system locale directory to the I18N module. The locale directory40 # is only installed if NO_GETTEXT is set.41 $Git::I18N::TEXTDOMAINDIR = __git_system_path('share/locale');42}43 44# END RUNTIME_PREFIX generated code.45#46# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>47# Copyright 2005 Ryan Anderson <ryan@michonline.com>48#49# GPL v2 (See COPYING)50#51# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com>52#53# Sends a collection of emails to the given email addresses, disturbingly fast.54#55# Supports two formats:56# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches)57# 2. The original format support by Greg's script:58# first line of the message is who to CC,59# and second line is the subject of the message.60#61 62require v5.26;63use strict;64use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();65use Getopt::Long;66use Git::LoadCPAN::Error qw(:try);67use Git;68use Git::I18N;69use Encode qw(find_encoding);70 71Getopt::Long::Configure qw/ pass_through /;72 73sub usage {74 print <<EOT;75git send-email [<options>] <file|directory>76git send-email [<options>] <format-patch options>77git send-email --dump-aliases78git send-email --translate-aliases79 80 Composing:81 --from <str> * Email From:82 --[no-]to <str> * Email To:83 --[no-]cc <str> * Email Cc:84 --[no-]bcc <str> * Email Bcc:85 --subject <str> * Email "Subject:"86 --reply-to <str> * Email "Reply-To:"87 --in-reply-to <str> * Email "In-Reply-To:"88 --[no-]outlook-id-fix * The SMTP host is an Outlook server that munges the89 Message-ID. Retrieve it from the server.90 --[no-]xmailer * Add "X-Mailer:" header (default).91 --[no-]annotate * Review each patch that will be sent in an editor.92 --compose * Open an editor for introduction.93 --compose-encoding <str> * Encoding to assume for introduction.94 --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared95 --transfer-encoding <str> * Transfer encoding to use (quoted-printable, 8bit, base64)96 --[no-]mailmap * Use mailmap file to map all email addresses to canonical97 real names and email addresses.98 99 Sending:100 --envelope-sender <str> * Email envelope sender.101 --sendmail-cmd <str> * Command to run to send email.102 --smtp-server <str:int> * Outgoing SMTP server to use. The port103 is optional. Default 'localhost'.104 --smtp-server-option <str> * Outgoing SMTP server option to use.105 --smtp-server-port <int> * Outgoing SMTP server port.106 --smtp-user <str> * Username for SMTP-AUTH.107 --smtp-pass <str> * Password for SMTP-AUTH; not necessary.108 --smtp-encryption <str> * tls or ssl; anything else disables.109 --smtp-ssl * Deprecated. Use `--smtp-encryption ssl`.110 --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file).111 Pass an empty string to disable certificate112 verification.113 --smtp-ssl-client-cert <str> * Path to the client certificate file114 --smtp-ssl-client-key <str> * Path to the private key file for the client certificate115 --smtp-domain <str> * The domain name sent to HELO/EHLO handshake116 --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or117 "none" to disable authentication.118 This setting forces to use one of the listed mechanisms.119 --no-smtp-auth * Disable SMTP authentication. Shorthand for120 `--smtp-auth=none`121 --smtp-debug <0|1> * Disable, enable Net::SMTP debug.122 --imap-sent-folder <str> * IMAP folder where a copy of the emails should be sent.123 Make sure `git imap-send` is set up to use this feature.124 --[no-]use-imap-only * Only copy emails to the IMAP folder specified by125 `--imap-sent-folder` instead of actually sending them.126 127 --batch-size <int> * send max <int> message per connection.128 --relogin-delay <int> * delay <int> seconds between two successive login.129 This option can only be used with --batch-size130 131 Automating:132 --identity <str> * Use the sendemail.<id> options.133 --to-cmd <str> * Email To: via `<str> \$patch_path`.134 --cc-cmd <str> * Email Cc: via `<str> \$patch_path`.135 --header-cmd <str> * Add headers via `<str> \$patch_path`.136 --no-header-cmd * Disable any header command in use.137 --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, misc-by, all.138 --[no-]cc-cover * Email Cc: addresses in the cover letter.139 --[no-]to-cover * Email To: addresses in the cover letter.140 --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on.141 --[no-]suppress-from * Send to self. Default off.142 --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off.143 --[no-]thread * Use In-Reply-To: field. Default on.144 145 Administering:146 --confirm <str> * Confirm recipients before sending;147 auto, cc, compose, always, or never.148 --quiet * Output one line of info per email.149 --dry-run * Don't actually send the emails.150 --[no-]validate * Perform patch sanity checks. Default on.151 --[no-]format-patch * understand any non optional arguments as152 `git format-patch` ones.153 --force * Send even if safety checks would prevent it.154 155 Information:156 --dump-aliases * Dump configured aliases and exit.157 --translate-aliases * Translate aliases read from standard158 input according to the configured email159 alias file(s), outputting the result to160 standard output.161 162EOT163 exit(1);164}165 166sub uniq {167 my %seen;168 grep !$seen{$_}++, @_;169}170 171sub completion_helper {172 my ($original_opts) = @_;173 my %not_for_completion = (174 "git-completion-helper" => undef,175 "h" => undef,176 );177 my @send_email_opts = ();178 179 foreach my $key (keys %$original_opts) {180 unless (exists $not_for_completion{$key}) {181 my $negatable = ($key =~ s/!$//);182 183 if ($key =~ /[:=][si]$/) {184 $key =~ s/[:=][si]$//;185 push (@send_email_opts, "--$_=") foreach (split (/\|/, $key));186 } else {187 push (@send_email_opts, "--$_") foreach (split (/\|/, $key));188 if ($negatable) {189 push (@send_email_opts, "--no-$_") foreach (split (/\|/, $key));190 }191 }192 }193 }194 195 my @format_patch_opts = split(/ /, Git::command('format-patch', '--git-completion-helper'));196 my @opts = (@send_email_opts, @format_patch_opts);197 @opts = uniq (grep !/^$/, @opts);198 # There's an implicit '\n' here already, no need to add an explicit one.199 print "@opts";200 exit(0);201}202 203# most mail servers generate the Date: header, but not all...204sub format_2822_time {205 my ($time) = @_;206 my @localtm = localtime($time);207 my @gmttm = gmtime($time);208 my $localmin = $localtm[1] + $localtm[2] * 60;209 my $gmtmin = $gmttm[1] + $gmttm[2] * 60;210 if ($localtm[0] != $gmttm[0]) {211 die __("local zone differs from GMT by a non-minute interval\n");212 }213 if ((($gmttm[6] + 1) % 7) == $localtm[6]) {214 $localmin += 1440;215 } elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) {216 $localmin -= 1440;217 } elsif ($gmttm[6] != $localtm[6]) {218 die __("local time offset greater than or equal to 24 hours\n");219 }220 my $offset = $localmin - $gmtmin;221 my $offhour = $offset / 60;222 my $offmin = abs($offset % 60);223 if (abs($offhour) >= 24) {224 die __("local time offset greater than or equal to 24 hours\n");225 }226 227 return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d",228 qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]],229 $localtm[3],230 qw(Jan Feb Mar Apr May Jun231 Jul Aug Sep Oct Nov Dec)[$localtm[4]],232 $localtm[5]+1900,233 $localtm[2],234 $localtm[1],235 $localtm[0],236 ($offset >= 0) ? '+' : '-',237 abs($offhour),238 $offmin,239 );240}241 242my $smtp;243my $auth;244my $num_sent = 0;245 246# Regexes for RFC 2047 productions.247my $re_token = qr/[^][()<>@,;:\\"\/?.= \000-\037\177-\377]+/;248my $re_encoded_text = qr/[^? \000-\037\177-\377]+/;249my $re_encoded_word = qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/;250 251# Variables we fill in automatically, or via prompting:252my (@to,@cc,@xh,$envelope_sender,253 $initial_in_reply_to,$reply_to,$initial_subject,@files,@imap_copy,254 $author,$sender,$smtp_authpass,$annotate,$compose,$time);255# Things we either get from config, *or* are overridden on the256# command-line.257my ($no_cc, $no_to, $no_bcc, $no_identity, $no_header_cmd);258my (@config_to, @getopt_to);259my (@config_cc, @getopt_cc);260my (@config_bcc, @getopt_bcc);261 262# Example reply to:263#$initial_in_reply_to = ''; #<20050203173208.GA23964@foobar.com>';264 265my $repo = eval { Git->repository() };266my @repo = $repo ? ($repo) : ();267 268# Behavior modification variables269my ($quiet, $dry_run) = (0, 0);270my $format_patch;271my $compose_filename;272my $force = 0;273my $dump_aliases = 0;274my $translate_aliases = 0;275 276# Variables to prevent short format-patch options from being captured277# as abbreviated send-email options278my $reroll_count;279 280# Handle interactive edition of files.281my $multiedit;282my $editor;283 284sub system_or_msg {285 my ($args, $msg, $cmd_name) = @_;286 system(@$args);287 my $signalled = $? & 127;288 my $exit_code = $? >> 8;289 return unless $signalled or $exit_code;290 291 my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);292 if (defined $msg) {293 # Quiet the 'redundant' warning category, except we294 # need to support down to Perl 5.8.1, so we can't do a295 # "no warnings 'redundant'", since that category was296 # introduced in perl 5.22, and asking for it will die297 # on older perls.298 no warnings;299 return sprintf($msg, @sprintf_args);300 }301 return sprintf(__("fatal: command '%s' died with exit code %d"),302 @sprintf_args);303}304 305sub system_or_die {306 my $msg = system_or_msg(@_);307 die $msg if $msg;308}309 310sub do_edit {311 if (!defined($editor)) {312 $editor = Git::command_oneline('var', 'GIT_EDITOR');313 }314 my $die_msg = __("the editor exited uncleanly, aborting everything");315 if (defined($multiedit) && !$multiedit) {316 system_or_die(['sh', '-c', $editor.' "$@"', $editor, $_], $die_msg) for @_;317 } else {318 system_or_die(['sh', '-c', $editor.' "$@"', $editor, @_], $die_msg);319 }320}321 322# Variables with corresponding config settings323my ($suppress_from, $signed_off_by_cc);324my ($cover_cc, $cover_to);325my ($to_cmd, $cc_cmd, $header_cmd);326my ($smtp_server, $smtp_server_port, @smtp_server_options);327my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);328my ($smtp_ssl_client_cert, $smtp_ssl_client_key);329my ($batch_size, $relogin_delay);330my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);331my ($imap_sent_folder);332my ($confirm);333my (@suppress_cc);334my ($auto_8bit_encoding);335my ($compose_encoding);336my ($sendmail_cmd);337my ($mailmap_file, $mailmap_blob);338# Variables with corresponding config settings & hardcoded defaults339my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()340my $thread = 1;341my $chain_reply_to = 0;342my $use_xmailer = 1;343my $validate = 1;344my $mailmap = 0;345my $target_xfer_encoding = 'auto';346my $forbid_sendmail_variables = 1;347my $outlook_id_fix = 'auto';348my $use_imap_only = 0;349 350my %config_bool_settings = (351 "thread" => \$thread,352 "chainreplyto" => \$chain_reply_to,353 "suppressfrom" => \$suppress_from,354 "signedoffbycc" => \$signed_off_by_cc,355 "cccover" => \$cover_cc,356 "tocover" => \$cover_to,357 "signedoffcc" => \$signed_off_by_cc,358 "validate" => \$validate,359 "multiedit" => \$multiedit,360 "annotate" => \$annotate,361 "xmailer" => \$use_xmailer,362 "forbidsendmailvariables" => \$forbid_sendmail_variables,363 "mailmap" => \$mailmap,364 "outlookidfix" => \$outlook_id_fix,365 "useimaponly" => \$use_imap_only,366);367 368my %config_settings = (369 "smtpencryption" => \$smtp_encryption,370 "smtpserver" => \$smtp_server,371 "smtpserverport" => \$smtp_server_port,372 "smtpserveroption" => \@smtp_server_options,373 "smtpuser" => \$smtp_authuser,374 "smtppass" => \$smtp_authpass,375 "smtpdomain" => \$smtp_domain,376 "smtpauth" => \$smtp_auth,377 "smtpbatchsize" => \$batch_size,378 "smtprelogindelay" => \$relogin_delay,379 "imapsentfolder" => \$imap_sent_folder,380 "to" => \@config_to,381 "tocmd" => \$to_cmd,382 "cc" => \@config_cc,383 "cccmd" => \$cc_cmd,384 "headercmd" => \$header_cmd,385 "aliasfiletype" => \$aliasfiletype,386 "bcc" => \@config_bcc,387 "suppresscc" => \@suppress_cc,388 "envelopesender" => \$envelope_sender,389 "confirm" => \$confirm,390 "from" => \$sender,391 "assume8bitencoding" => \$auto_8bit_encoding,392 "composeencoding" => \$compose_encoding,393 "transferencoding" => \$target_xfer_encoding,394 "sendmailcmd" => \$sendmail_cmd,395);396 397my %config_path_settings = (398 "aliasesfile" => \@alias_files,399 "smtpsslcertpath" => \$smtp_ssl_cert_path,400 "smtpsslclientcert" => \$smtp_ssl_client_cert,401 "smtpsslclientkey" => \$smtp_ssl_client_key,402 "mailmap.file" => \$mailmap_file,403 "mailmap.blob" => \$mailmap_blob,404);405 406# Handle Uncouth Termination407sub signal_handler {408 # Make text normal409 require Term::ANSIColor;410 print Term::ANSIColor::color("reset"), "\n";411 412 # SMTP password masked413 system "stty echo";414 415 # tmp files from --compose416 if (defined $compose_filename) {417 if (-e $compose_filename) {418 printf __("'%s' contains an intermediate version ".419 "of the email you were composing.\n"),420 $compose_filename;421 }422 if (-e ($compose_filename . ".final")) {423 printf __("'%s.final' contains the composed email.\n"),424 $compose_filename;425 }426 }427 428 exit;429};430 431$SIG{TERM} = \&signal_handler;432$SIG{INT} = \&signal_handler;433 434# Read our sendemail.* config435sub read_config {436 my ($known_keys, $configured, $prefix) = @_;437 438 foreach my $setting (keys %config_bool_settings) {439 my $target = $config_bool_settings{$setting};440 my $key = "$prefix.$setting";441 next unless exists $known_keys->{$key};442 my $v = (@{$known_keys->{$key}} == 1 &&443 (defined $known_keys->{$key}->[0] &&444 $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))445 ? $known_keys->{$key}->[0] eq 'true'446 : Git::config_bool(@repo, $key);447 next unless defined $v;448 next if $configured->{$setting}++;449 $$target = $v;450 }451 452 foreach my $setting (keys %config_path_settings) {453 my $target = $config_path_settings{$setting};454 my $key = "$prefix.$setting";455 next unless exists $known_keys->{$key};456 if (ref($target) eq "ARRAY") {457 my @values = Git::config_path(@repo, $key);458 next unless @values;459 next if $configured->{$setting}++;460 @$target = @values;461 }462 else {463 my $v = Git::config_path(@repo, "$prefix.$setting");464 next unless defined $v;465 next if $configured->{$setting}++;466 $$target = $v;467 }468 }469 470 foreach my $setting (keys %config_settings) {471 my $target = $config_settings{$setting};472 my $key = "$prefix.$setting";473 next unless exists $known_keys->{$key};474 if (ref($target) eq "ARRAY") {475 my @values = @{$known_keys->{$key}};476 @values = grep { defined } @values;477 next if $configured->{$setting}++;478 @$target = @values;479 }480 else {481 my $v = $known_keys->{$key}->[-1];482 next unless defined $v;483 next if $configured->{$setting}++;484 $$target = $v;485 }486 }487}488 489sub config_regexp {490 my ($regex) = @_;491 my @ret;492 eval {493 my $ret = Git::command(494 'config',495 '--null',496 '--get-regexp',497 $regex,498 );499 @ret = map {500 # We must always return ($k, $v) here, since501 # empty config values will be just "key\0",502 # not "key\nvalue\0".503 my ($k, $v) = split /\n/, $_, 2;504 ($k, $v);505 } split /\0/, $ret;506 1;507 } or do {508 # If we have no keys we're OK, otherwise re-throw509 die $@ if $@->value != 1;510 };511 return @ret;512}513 514# Save ourselves a lot of work of shelling out to 'git config' (it515# parses 'bool' etc.) by only doing so for config keys that exist.516my %known_config_keys;517{518 my @kv = config_regexp("^sende?mail[.]");519 while (my ($k, $v) = splice @kv, 0, 2) {520 push @{$known_config_keys{$k}} => $v;521 }522}523 524# sendemail.identity yields to --identity. We must parse this525# special-case first before the rest of the config is read.526{527 my $key = "sendemail.identity";528 $identity = Git::config(@repo, $key) if exists $known_config_keys{$key};529}530my %identity_options = (531 "identity=s" => \$identity,532 "no-identity" => \$no_identity,533);534my $rc = GetOptions(%identity_options);535usage() unless $rc;536undef $identity if $no_identity;537 538# Now we know enough to read the config539{540 my %configured;541 read_config(\%known_config_keys, \%configured, "sendemail.$identity") if defined $identity;542 read_config(\%known_config_keys, \%configured, "sendemail");543}544 545# Begin by accumulating all the variables (defined above), that we will end up546# needing, first, from the command line:547 548my $help;549my $git_completion_helper;550my %dump_aliases_options = (551 "h" => \$help,552 "dump-aliases" => \$dump_aliases,553 "translate-aliases" => \$translate_aliases,554);555$rc = GetOptions(%dump_aliases_options);556usage() unless $rc;557die __("--dump-aliases incompatible with other options\n")558 if !$help and ($dump_aliases or $translate_aliases) and @ARGV;559die __("--dump-aliases and --translate-aliases are mutually exclusive\n")560 if !$help and $dump_aliases and $translate_aliases;561my %options = (562 "sender|from=s" => \$sender,563 "in-reply-to=s" => \$initial_in_reply_to,564 "reply-to=s" => \$reply_to,565 "subject=s" => \$initial_subject,566 "to=s" => \@getopt_to,567 "to-cmd=s" => \$to_cmd,568 "no-to" => \$no_to,569 "cc=s" => \@getopt_cc,570 "no-cc" => \$no_cc,571 "bcc=s" => \@getopt_bcc,572 "no-bcc" => \$no_bcc,573 "chain-reply-to!" => \$chain_reply_to,574 "sendmail-cmd=s" => \$sendmail_cmd,575 "smtp-server=s" => \$smtp_server,576 "smtp-server-option=s" => \@smtp_server_options,577 "smtp-server-port=s" => \$smtp_server_port,578 "smtp-user=s" => \$smtp_authuser,579 "smtp-pass:s" => \$smtp_authpass,580 "smtp-ssl" => sub { $smtp_encryption = 'ssl' },581 "smtp-encryption=s" => \$smtp_encryption,582 "smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path,583 "smtp-ssl-client-cert=s" => \$smtp_ssl_client_cert,584 "smtp-ssl-client-key=s" => \$smtp_ssl_client_key,585 "smtp-debug:i" => \$debug_net_smtp,586 "smtp-domain:s" => \$smtp_domain,587 "smtp-auth=s" => \$smtp_auth,588 "no-smtp-auth" => sub {$smtp_auth = 'none'},589 "imap-sent-folder=s" => \$imap_sent_folder,590 "use-imap-only!" => \$use_imap_only,591 "annotate!" => \$annotate,592 "compose" => \$compose,593 "quiet" => \$quiet,594 "cc-cmd=s" => \$cc_cmd,595 "header-cmd=s" => \$header_cmd,596 "no-header-cmd" => \$no_header_cmd,597 "suppress-from!" => \$suppress_from,598 "suppress-cc=s" => \@suppress_cc,599 "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,600 "cc-cover!" => \$cover_cc,601 "to-cover!" => \$cover_to,602 "confirm=s" => \$confirm,603 "dry-run" => \$dry_run,604 "envelope-sender=s" => \$envelope_sender,605 "thread!" => \$thread,606 "validate!" => \$validate,607 "transfer-encoding=s" => \$target_xfer_encoding,608 "mailmap!" => \$mailmap,609 "use-mailmap!" => \$mailmap,610 "format-patch!" => \$format_patch,611 "8bit-encoding=s" => \$auto_8bit_encoding,612 "compose-encoding=s" => \$compose_encoding,613 "force" => \$force,614 "xmailer!" => \$use_xmailer,615 "batch-size=i" => \$batch_size,616 "relogin-delay=i" => \$relogin_delay,617 "git-completion-helper" => \$git_completion_helper,618 "v=s" => \$reroll_count,619 "outlook-id-fix!" => \$outlook_id_fix,620);621$rc = GetOptions(%options);622 623# Munge any "either config or getopt, not both" variables624my @initial_to = @getopt_to ? @getopt_to : ($no_to ? () : @config_to);625my @initial_cc = @getopt_cc ? @getopt_cc : ($no_cc ? () : @config_cc);626my @initial_bcc = @getopt_bcc ? @getopt_bcc : ($no_bcc ? () : @config_bcc);627 628usage() if $help;629my %all_options = (%options, %dump_aliases_options, %identity_options);630completion_helper(\%all_options) if $git_completion_helper;631unless ($rc) {632 usage();633}634 635if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {636 die __("fatal: found configuration options for 'sendmail'\n" .637 "git-send-email is configured with the sendemail.* options - note the 'e'.\n" .638 "Set sendemail.forbidSendmailVariables to false to disable this check.\n");639}640 641die __("Cannot run git format-patch from outside a repository\n")642 if $format_patch and not $repo;643 644die __("`batch-size` and `relogin` must be specified together " .645 "(via command-line or configuration option)\n")646 if defined $relogin_delay and not defined $batch_size;647 648# 'default' encryption is none -- this only prevents a warning649$smtp_encryption = '' unless (defined $smtp_encryption);650 651# Set CC suppressions652my(%suppress_cc);653if (@suppress_cc) {654 foreach my $entry (@suppress_cc) {655 # Please update $__git_send_email_suppresscc_options656 # in git-completion.bash when you add new options.657 die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry)658 unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc|misc-by)$/;659 $suppress_cc{$entry} = 1;660 }661}662 663if ($suppress_cc{'all'}) {664 foreach my $entry (qw (cccmd cc author self sob body bodycc misc-by)) {665 $suppress_cc{$entry} = 1;666 }667 delete $suppress_cc{'all'};668}669 670# If explicit old-style ones are specified, they trump --suppress-cc.671$suppress_cc{'self'} = $suppress_from if defined $suppress_from;672$suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc;673 674if ($suppress_cc{'body'}) {675 foreach my $entry (qw (sob bodycc misc-by)) {676 $suppress_cc{$entry} = 1;677 }678 delete $suppress_cc{'body'};679}680 681# Set confirm's default value682my $confirm_unconfigured = !defined $confirm;683if ($confirm_unconfigured) {684 $confirm = scalar %suppress_cc ? 'compose' : 'auto';685};686# Please update $__git_send_email_confirm_options in687# git-completion.bash when you add new options.688die sprintf(__("Unknown --confirm setting: '%s'\n"), $confirm)689 unless $confirm =~ /^(?:auto|cc|compose|always|never)/;690 691# Debugging, print out the suppressions.692if (0) {693 print "suppressions:\n";694 foreach my $entry (keys %suppress_cc) {695 printf " %-5s -> $suppress_cc{$entry}\n", $entry;696 }697}698 699my ($repoauthor, $repocommitter);700{701 my %cache;702 my ($author, $committer);703 my $common = sub {704 my ($what) = @_;705 return $cache{$what} if exists $cache{$what};706 ($cache{$what}) = Git::ident_person(@repo, $what);707 return $cache{$what};708 };709 $repoauthor = sub { $common->('author') };710 $repocommitter = sub { $common->('committer') };711}712 713sub parse_address_line {714 require Git::LoadCPAN::Mail::Address;715 return map { $_->format } Mail::Address->parse($_[0]);716}717 718sub split_addrs {719 require Text::ParseWords;720 return Text::ParseWords::quotewords('\s*,\s*', 1, @_);721}722 723my %aliases;724 725sub parse_sendmail_alias {726 local $_ = shift;727 if (/"/) {728 printf STDERR __("warning: sendmail alias with quotes is not supported: %s\n"), $_;729 } elsif (/:include:/) {730 printf STDERR __("warning: `:include:` not supported: %s\n"), $_;731 } elsif (/[\/|]/) {732 printf STDERR __("warning: `/file` or `|pipe` redirection not supported: %s\n"), $_;733 } elsif (/^(\S+?)\s*:\s*(.+)$/) {734 my ($alias, $addr) = ($1, $2);735 $aliases{$alias} = [ split_addrs($addr) ];736 } else {737 printf STDERR __("warning: sendmail line is not recognized: %s\n"), $_;738 }739}740 741sub parse_sendmail_aliases {742 my $fh = shift;743 my $s = '';744 while (<$fh>) {745 chomp;746 next if /^\s*$/ || /^\s*#/;747 $s .= $_, next if $s =~ s/\\$// || s/^\s+//;748 parse_sendmail_alias($s) if $s;749 $s = $_;750 }751 $s =~ s/\\$//; # silently tolerate stray '\' on last line752 parse_sendmail_alias($s) if $s;753}754 755my %parse_alias = (756 # multiline formats can be supported in the future757 mutt => sub { my $fh = shift; while (<$fh>) {758 if (/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) {759 my ($alias, $addr) = ($1, $2);760 $addr =~ s/#.*$//; # mutt allows # comments761 # commas delimit multiple addresses762 my @addr = split_addrs($addr);763 764 # quotes may be escaped in the file,765 # unescape them so we do not double-escape them later.766 s/\\"/"/g foreach @addr;767 $aliases{$alias} = \@addr768 }}},769 mailrc => sub { my $fh = shift; while (<$fh>) {770 if (/^alias\s+(\S+)\s+(.*?)\s*$/) {771 require Text::ParseWords;772 # spaces delimit multiple addresses773 $aliases{$1} = [ Text::ParseWords::quotewords('\s+', 0, $2) ];774 }}},775 pine => sub { my $fh = shift; my $f='\t[^\t]*';776 for (my $x = ''; defined($x); $x = $_) {777 chomp $x;778 $x .= $1 while(defined($_ = <$fh>) && /^ +(.*)$/);779 $x =~ /^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ or next;780 $aliases{$1} = [ split_addrs($2) ];781 }},782 elm => sub { my $fh = shift;783 while (<$fh>) {784 if (/^(\S+)\s+=\s+[^=]+=\s(\S+)/) {785 my ($alias, $addr) = ($1, $2);786 $aliases{$alias} = [ split_addrs($addr) ];787 }788 } },789 sendmail => \&parse_sendmail_aliases,790 gnus => sub { my $fh = shift; while (<$fh>) {791 if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {792 $aliases{$1} = [ $2 ];793 }}}794 # Please update _git_config() in git-completion.bash when you795 # add new MUAs.796);797 798if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {799 foreach my $file (@alias_files) {800 open my $fh, '<', $file or die "opening $file: $!\n";801 $parse_alias{$aliasfiletype}->($fh);802 close $fh;803 }804}805 806if ($dump_aliases) {807 print "$_\n" for (sort keys %aliases);808 exit(0);809}810 811if ($translate_aliases) {812 while (<STDIN>) {813 my @addr_list = parse_address_line($_);814 @addr_list = expand_aliases(@addr_list);815 @addr_list = sanitize_address_list(@addr_list);816 print "$_\n" for @addr_list;817 }818 exit(0);819}820 821# is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if822# $f is a revision list specification to be passed to format-patch.823sub is_format_patch_arg {824 return unless $repo;825 my $f = shift;826 try {827 $repo->command('rev-parse', '--verify', '--quiet', $f);828 if (defined($format_patch)) {829 return $format_patch;830 }831 die sprintf(__(<<EOF), $f, $f);832File '%s' exists but it could also be the range of commits833to produce patches for. Please disambiguate by...834 835 * Saying "./%s" if you mean a file; or836 * Giving --format-patch option if you mean a range.837EOF838 } catch Git::Error::Command with {839 # Not a valid revision. Treat it as a filename.840 return 0;841 }842}843 844# Now that all the defaults are set, process the rest of the command line845# arguments and collect up the files that need to be processed.846my @rev_list_opts;847while (defined(my $f = shift @ARGV)) {848 if ($f eq "--") {849 push @rev_list_opts, "--", @ARGV;850 @ARGV = ();851 } elsif (-d $f and !is_format_patch_arg($f)) {852 opendir my $dh, $f853 or die sprintf(__("Failed to opendir %s: %s"), $f, $!);854 855 require File::Spec;856 push @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }857 sort readdir $dh;858 closedir $dh;859 } elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {860 push @files, $f;861 } else {862 push @rev_list_opts, $f;863 }864}865 866if (@rev_list_opts) {867 die __("Cannot run git format-patch from outside a repository\n")868 unless $repo;869 require File::Temp;870 push @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1),871 defined $reroll_count ? ('-v', $reroll_count) : (),872 @rev_list_opts);873}874 875if (defined $sender) {876 $sender =~ s/^\s+|\s+$//g;877 ($sender) = expand_aliases($sender);878} else {879 $sender = $repoauthor->() || $repocommitter->() || '';880}881 882# $sender could be an already sanitized address883# (e.g. sendemail.from could be manually sanitized by user).884# But it's a no-op to run sanitize_address on an already sanitized address.885$sender = sanitize_address($sender);886 887$time = time - scalar $#files;888 889@files = handle_backup_files(@files);890 891if (@files) {892 unless ($quiet) {893 print $_,"\n" for (@files);894 }895} else {896 print STDERR __("\nNo patch files specified!\n\n");897 usage();898}899 900sub get_patch_subject {901 my $fn = shift;902 open (my $fh, '<', $fn);903 while (my $line = <$fh>) {904 next unless ($line =~ /^Subject: (.*)$/);905 close $fh;906 return "GIT: $1\n";907 }908 close $fh;909 die sprintf(__("No subject line in %s?"), $fn);910}911 912if ($compose) {913 # Note that this does not need to be secure, but we will make a small914 # effort to have it be unique915 require File::Temp;916 $compose_filename = ($repo ?917 File::Temp::tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :918 File::Temp::tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];919 open my $c, ">", $compose_filename920 or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!);921 922 923 my $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || '';924 my $tpl_subject = $initial_subject || '';925 my $tpl_in_reply_to = $initial_in_reply_to || '';926 my $tpl_reply_to = $reply_to || '';927 my $tpl_to = join(',', @initial_to);928 my $tpl_cc = join(',', @initial_cc);929 my $tpl_bcc = join(', ', @initial_bcc);930 931 print $c <<EOT1, Git::prefix_lines("GIT: ", __(<<EOT2)), <<EOT3;932From $tpl_sender # This line is ignored.933EOT1934Lines beginning in "GIT:" will be removed.935Consider including an overall diffstat or table of contents936for the patch you are writing.937 938Clear the body content if you don't wish to send a summary.939EOT2940From: $tpl_sender941To: $tpl_to942Cc: $tpl_cc943Bcc: $tpl_bcc944Reply-To: $tpl_reply_to945Subject: $tpl_subject946In-Reply-To: $tpl_in_reply_to947 948EOT3949 for my $f (@files) {950 print $c get_patch_subject($f);951 }952 close $c;953 954 if ($annotate) {955 do_edit($compose_filename, @files);956 } else {957 do_edit($compose_filename);958 }959 960 open my $c2, ">", $compose_filename . ".final"961 or die sprintf(__("Failed to open %s.final: %s"), $compose_filename, $!);962 963 open $c, "<", $compose_filename964 or die sprintf(__("Failed to open %s: %s"), $compose_filename, $!);965 966 my $need_8bit_cte = file_has_nonascii($compose_filename);967 my $in_body = 0;968 my $summary_empty = 1;969 if (!defined $compose_encoding) {970 $compose_encoding = "UTF-8";971 }972 while(<$c>) {973 next if m/^GIT:/;974 if ($in_body) {975 $summary_empty = 0 unless (/^\n$/);976 } elsif (/^\n$/) {977 $in_body = 1;978 if ($need_8bit_cte) {979 print $c2 "MIME-Version: 1.0\n",980 "Content-Type: text/plain; ",981 "charset=$compose_encoding\n",982 "Content-Transfer-Encoding: 8bit\n";983 }984 } elsif (/^MIME-Version:/i) {985 $need_8bit_cte = 0;986 } elsif (/^Subject:\s*(.+)\s*$/i) {987 $initial_subject = $1;988 my $subject = $initial_subject;989 $_ = "Subject: " .990 quote_subject($subject, $compose_encoding) .991 "\n";992 } elsif (/^In-Reply-To:\s*(.+)\s*$/i) {993 $initial_in_reply_to = $1;994 next;995 } elsif (/^Reply-To:\s*(.+)\s*$/i) {996 $reply_to = $1;997 } elsif (/^From:\s*(.+)\s*$/i) {998 $sender = $1;999 next;1000 } elsif (/^To:\s*(.+)\s*$/i) {1001 @initial_to = parse_address_line($1);1002 next;1003 } elsif (/^Cc:\s*(.+)\s*$/i) {1004 @initial_cc = parse_address_line($1);1005 next;1006 } elsif (/^Bcc:/i) {1007 @initial_bcc = parse_address_line($1);1008 next;1009 }1010 print $c2 $_;1011 }1012 close $c;1013 close $c2;1014 1015 if ($summary_empty) {1016 print __("Summary email is empty, skipping it\n");1017 $compose = -1;1018 }1019} elsif ($annotate) {1020 do_edit(@files);1021}1022 1023{1024 # Only instantiate one $term per program run, since some1025 # Term::ReadLine providers refuse to create a second instance.1026 my $term;1027 sub term {1028 require Term::ReadLine;1029 if (!defined $term) {1030 $term = $ENV{"GIT_SEND_EMAIL_NOTTY"}1031 ? Term::ReadLine->new('git-send-email', \*STDIN, \*STDOUT)1032 : Term::ReadLine->new('git-send-email');1033 }1034 return $term;1035 }1036}1037 1038sub ask {1039 my ($prompt, %arg) = @_;1040 my $valid_re = $arg{valid_re};1041 my $default = $arg{default};1042 my $confirm_only = $arg{confirm_only};1043 my $resp;1044 my $i = 0;1045 my $term = term();1046 return defined $default ? $default : undef1047 unless defined $term->IN and defined fileno($term->IN) and1048 defined $term->OUT and defined fileno($term->OUT);1049 while ($i++ < 10) {1050 $resp = $term->readline($prompt);1051 if (!defined $resp) { # EOF1052 print "\n";1053 return defined $default ? $default : undef;1054 }1055 if ($resp eq '' and defined $default) {1056 return $default;1057 }1058 if (!defined $valid_re or $resp =~ /$valid_re/) {1059 return $resp;1060 }1061 if ($confirm_only) {1062 my $yesno = $term->readline(1063 # TRANSLATORS: please keep [y/N] as is.1064 sprintf(__("Are you sure you want to use <%s> [y/N]? "), $resp));1065 if (defined $yesno && $yesno =~ /y/i) {1066 return $resp;1067 }1068 }1069 }1070 return;1071}1072 1073my %broken_encoding;1074 1075sub file_declares_8bit_cte {1076 my $fn = shift;1077 open (my $fh, '<', $fn);1078 while (my $line = <$fh>) {1079 last if ($line =~ /^$/);1080 return 1 if ($line =~ /^Content-Transfer-Encoding: .*8bit.*$/);1081 }1082 close $fh;1083 return 0;1084}1085 1086foreach my $f (@files) {1087 next unless (body_or_subject_has_nonascii($f)1088 && !file_declares_8bit_cte($f));1089 $broken_encoding{$f} = 1;1090}1091 1092if (!defined $auto_8bit_encoding && scalar %broken_encoding) {1093 print __("The following files are 8bit, but do not declare " .1094 "a Content-Transfer-Encoding.\n");1095 foreach my $f (sort keys %broken_encoding) {1096 print " $f\n";1097 }1098 while (1) {1099 my $encoding = ask(1100 __("Declare which 8bit encoding to use [default: UTF-8]? "),1101 valid_re => qr/^\S+$/,1102 default => "UTF-8");1103 next unless defined $encoding;1104 if (find_encoding($encoding)) {1105 $auto_8bit_encoding = $encoding;1106 last;1107 }1108 my $yesno = ask(1109 sprintf(1110 __("'%s' does not appear to be a valid charset name. Use it anyway [y/N]? "),1111 $encoding),1112 valid_re => qr/^(?:y|n)/i,1113 default => "n");1114 if (defined $yesno && $yesno =~ /^y/i) {1115 $auto_8bit_encoding = $encoding;1116 last;1117 }1118 }1119}1120 1121if (!$force) {1122 for my $f (@files) {1123 if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) {1124 die sprintf(__("Refusing to send because the patch\n\t%s\n"1125 . "has the template subject '*** SUBJECT HERE ***'. "1126 . "Pass --force if you really want to send.\n"), $f);1127 }1128 }1129}1130 1131my $to_whom = __("To whom should the emails be sent (if anyone)?");1132my $prompting = 0;1133if (!@initial_to && !defined $to_cmd) {1134 my $to = ask("$to_whom ",1135 default => "",1136 valid_re => qr/\@.*\./, confirm_only => 1);1137 push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later1138 $prompting++;1139}1140 1141sub expand_aliases {1142 return map { expand_one_alias($_) } @_;1143}1144 1145my %EXPANDED_ALIASES;1146sub expand_one_alias {1147 my $alias = shift;1148 if ($EXPANDED_ALIASES{$alias}) {1149 die sprintf(__("fatal: alias '%s' expands to itself\n"), $alias);1150 }1151 local $EXPANDED_ALIASES{$alias} = 1;1152 return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;1153}1154 1155@initial_to = process_address_list(@initial_to);1156@initial_cc = process_address_list(@initial_cc);1157@initial_bcc = process_address_list(@initial_bcc);1158 1159if ($thread && !defined $initial_in_reply_to && $prompting) {1160 $initial_in_reply_to = ask(1161 __("Message-ID to be used as In-Reply-To for the first email (if any)? "),1162 default => "",1163 valid_re => qr/\@.*\./, confirm_only => 1);1164}1165if (defined $initial_in_reply_to) {1166 $initial_in_reply_to =~ s/^\s*<?//;1167 $initial_in_reply_to =~ s/>?\s*$//;1168 $initial_in_reply_to = "<$initial_in_reply_to>" if $initial_in_reply_to ne '';1169}1170 1171if (defined $reply_to) {1172 $reply_to =~ s/^\s+|\s+$//g;1173 ($reply_to) = expand_aliases($reply_to);1174 $reply_to = sanitize_address($reply_to);1175}1176 1177if (!defined $sendmail_cmd && !defined $smtp_server) {1178 my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );1179 push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};1180 foreach (@sendmail_paths) {1181 if (-x $_) {1182 $sendmail_cmd = $_;1183 last;1184 }1185 }1186 1187 if (!defined $sendmail_cmd) {1188 $smtp_server = 'localhost'; # could be 127.0.0.1, too... *shrug*1189 }1190}1191 1192if ($compose && $compose > 0) {1193 @files = ($compose_filename . ".final", @files);1194}1195 1196# Variables we set as part of the loop over files1197our ($message_id, %mail, $subject, $in_reply_to, $references, $message,1198 $needs_confirm, $message_num, $ask_default);1199 1200sub mailmap_address_list {