git-send-email.perlon commit git-var: constness and globalness cleanup. (c7d77da)
   1#!/usr/bin/perl -w
   2#
   3# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
   4# Copyright 2005 Ryan Anderson <ryan@michonline.com>
   5#
   6# GPL v2 (See COPYING)
   7#
   8# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com>
   9#
  10# Sends a collection of emails to the given email addresses, disturbingly fast.
  11#
  12# Supports two formats:
  13# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches)
  14# 2. The original format support by Greg's script:
  15#    first line of the message is who to CC,
  16#    and second line is the subject of the message.
  17#
  18
  19use strict;
  20use warnings;
  21use Term::ReadLine;
  22use Mail::Sendmail qw(sendmail %mailcfg);
  23use Getopt::Long;
  24use Data::Dumper;
  25use Email::Valid;
  26
  27sub unique_email_list(@);
  28sub cleanup_compose_files();
  29
  30# Constants (essentially)
  31my $compose_filename = ".msg.$$";
  32
  33# Variables we fill in automatically, or via prompting:
  34my (@to,@cc,$initial_reply_to,$initial_subject,@files,$from,$compose);
  35
  36# Behavior modification variables
  37my ($chain_reply_to, $smtp_server) = (1, "localhost");
  38
  39# Example reply to:
  40#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
  41
  42my $term = new Term::ReadLine 'git-send-email';
  43
  44# Begin by accumulating all the variables (defined above), that we will end up
  45# needing, first, from the command line:
  46
  47my $rc = GetOptions("from=s" => \$from,
  48                    "in-reply-to=s" => \$initial_reply_to,
  49                    "subject=s" => \$initial_subject,
  50                    "to=s" => \@to,
  51                    "chain-reply-to!" => \$chain_reply_to,
  52                    "smtp-server=s" => \$smtp_server,
  53                    "compose" => \$compose,
  54         );
  55
  56# Now, let's fill any that aren't set in with defaults:
  57
  58open(GITVAR,"-|","git-var","-l")
  59        or die "Failed to open pipe from git-var: $!";
  60
  61my ($author,$committer);
  62while(<GITVAR>) {
  63        chomp;
  64        my ($var,$data) = split /=/,$_,2;
  65        my @fields = split /\s+/, $data;
  66
  67        my $ident = join(" ", @fields[0...(@fields-3)]);
  68
  69        if ($var eq 'GIT_AUTHOR_IDENT') {
  70                $author = $ident;
  71        } elsif ($var eq 'GIT_COMMITTER_IDENT') {
  72                $committer = $ident;
  73        }
  74}
  75close(GITVAR);
  76
  77my $prompting = 0;
  78if (!defined $from) {
  79        $from = $author || $committer;
  80        do {
  81                $_ = $term->readline("Who should the emails appear to be from? ",
  82                        $from);
  83        } while (!defined $_);
  84
  85        $from = $_;
  86        print "Emails will be sent from: ", $from, "\n";
  87        $prompting++;
  88}
  89
  90if (!@to) {
  91        do {
  92                $_ = $term->readline("Who should the emails be sent to? ",
  93                                "");
  94        } while (!defined $_);
  95        my $to = $_;
  96        push @to, split /,/, $to;
  97        $prompting++;
  98}
  99
 100if (!defined $initial_subject && $compose) {
 101        do {
 102                $_ = $term->readline("What subject should the emails start with? ",
 103                        $initial_subject);
 104        } while (!defined $_);
 105        $initial_subject = $_;
 106        $prompting++;
 107}
 108
 109if (!defined $initial_reply_to && $prompting) {
 110        do {
 111                $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
 112                        $initial_reply_to);
 113        } while (!defined $_);
 114
 115        $initial_reply_to = $_;
 116        $initial_reply_to =~ s/(^\s+|\s+$)//g;
 117}
 118
 119if (!defined $smtp_server) {
 120        $smtp_server = "localhost";
 121}
 122
 123if ($compose) {
 124        # Note that this does not need to be secure, but we will make a small
 125        # effort to have it be unique
 126        open(C,">",$compose_filename)
 127                or die "Failed to open for writing $compose_filename: $!";
 128        print C "From \n";
 129        printf C "Subject: %s\n\n", $initial_subject;
 130        printf C <<EOT;
 131GIT: Please enter your email below.
 132GIT: Lines beginning in "GIT: " will be removed.
 133GIT: Consider including an overall diffstat or table of contents
 134GIT: for the patch you are writing.
 135
 136EOT
 137        close(C);
 138
 139        my $editor = $ENV{EDITOR};
 140        $editor = 'vi' unless defined $editor;
 141        system($editor, $compose_filename);
 142
 143        open(C2,">",$compose_filename . ".final")
 144                or die "Failed to open $compose_filename.final : " . $!;
 145
 146        open(C,"<",$compose_filename)
 147                or die "Failed to open $compose_filename : " . $!;
 148
 149        while(<C>) {
 150                next if m/^GIT: /;
 151                print C2 $_;
 152        }
 153        close(C);
 154        close(C2);
 155
 156        do {
 157                $_ = $term->readline("Send this email? (y|n) ");
 158        } while (!defined $_);
 159
 160        if (uc substr($_,0,1) ne 'Y') {
 161                cleanup_compose_files();
 162                exit(0);
 163        }
 164
 165        @files = ($compose_filename . ".final");
 166}
 167
 168
 169# Now that all the defaults are set, process the rest of the command line
 170# arguments and collect up the files that need to be processed.
 171for my $f (@ARGV) {
 172        if (-d $f) {
 173                opendir(DH,$f)
 174                        or die "Failed to opendir $f: $!";
 175
 176                push @files, grep { -f $_ } map { +$f . "/" . $_ }
 177                                sort readdir(DH);
 178
 179        } elsif (-f $f) {
 180                push @files, $f;
 181
 182        } else {
 183                print STDERR "Skipping $f - not found.\n";
 184        }
 185}
 186
 187if (@files) {
 188        print $_,"\n" for @files;
 189} else {
 190        print <<EOT;
 191git-send-email [options] <file | directory> [... file | directory ]
 192Options:
 193   --from         Specify the "From:" line of the email to be sent.
 194
 195   --to           Specify the primary "To:" line of the email.
 196
 197   --compose      Use \$EDITOR to edit an introductory message for the
 198                  patch series.
 199
 200   --subject      Specify the initial "Subject:" line.
 201                  Only necessary if --compose is also set.  If --compose
 202                  is not set, this will be prompted for.
 203
 204   --in-reply-to  Specify the first "In-Reply-To:" header line.
 205                  Only used if --compose is also set.  If --compose is not
 206                  set, this will be prompted for.
 207
 208   --chain-reply-to If set, the replies will all be to the previous
 209                  email sent, rather than to the first email sent.
 210                  Defaults to on.
 211
 212   --smtp-server  If set, specifies the outgoing SMTP server to use.
 213                  Defaults to localhost.
 214
 215Error: Please specify a file or a directory on the command line.
 216EOT
 217        exit(1);
 218}
 219
 220# Variables we set as part of the loop over files
 221our ($message_id, $cc, %mail, $subject, $reply_to, $message);
 222
 223
 224# Usually don't need to change anything below here.
 225
 226# we make a "fake" message id by taking the current number
 227# of seconds since the beginning of Unix time and tacking on
 228# a random number to the end, in case we are called quicker than
 229# 1 second since the last time we were called.
 230
 231# We'll setup a template for the message id, using the "from" address:
 232my $message_id_from = Email::Valid->address($from);
 233my $message_id_template = "<%s-git-send-email-$message_id_from>";
 234
 235sub make_message_id
 236{
 237        my $date = `date "+\%s"`;
 238        chomp($date);
 239        my $pseudo_rand = int (rand(4200));
 240        $message_id = sprintf $message_id_template, "$date$pseudo_rand";
 241        #print "new message id = $message_id\n"; # Was useful for debugging
 242}
 243
 244
 245
 246$cc = "";
 247
 248sub send_message
 249{
 250        my $to = join (", ", unique_email_list(@to));
 251
 252        %mail = (       To      =>      $to,
 253                        From    =>      $from,
 254                        CC      =>      $cc,
 255                        Subject =>      $subject,
 256                        Message =>      $message,
 257                        'Reply-to'      =>      $from,
 258                        'In-Reply-To'   =>      $reply_to,
 259                        'Message-ID'    =>      $message_id,
 260                        'X-Mailer'      =>      "git-send-email",
 261                );
 262
 263        $mail{smtp} = $smtp_server;
 264        $mailcfg{mime} = 0;
 265
 266        #print Data::Dumper->Dump([\%mail],[qw(*mail)]);
 267
 268        sendmail(%mail) or die $Mail::Sendmail::error;
 269
 270        print "OK. Log says:\n", $Mail::Sendmail::log;
 271        print "\n\n"
 272}
 273
 274
 275$reply_to = $initial_reply_to;
 276make_message_id();
 277$subject = $initial_subject;
 278
 279foreach my $t (@files) {
 280        my $F = $t;
 281        open(F,"<",$t) or die "can't open file $t";
 282
 283        @cc = ();
 284        my $found_mbox = 0;
 285        my $header_done = 0;
 286        $message = "";
 287        while(<F>) {
 288                if (!$header_done) {
 289                        $found_mbox = 1, next if (/^From /);
 290                        chomp;
 291
 292                        if ($found_mbox) {
 293                                if (/^Subject:\s+(.*)$/) {
 294                                        $subject = $1;
 295
 296                                } elsif (/^(Cc|From):\s+(.*)$/) {
 297                                        printf("(mbox) Adding cc: %s from line '%s'\n",
 298                                                $2, $_);
 299                                        push @cc, $2;
 300                                }
 301
 302                        } else {
 303                                # In the traditional
 304                                # "send lots of email" format,
 305                                # line 1 = cc
 306                                # line 2 = subject
 307                                # So let's support that, too.
 308                                if (@cc == 0) {
 309                                        printf("(non-mbox) Adding cc: %s from line '%s'\n",
 310                                                $_, $_);
 311
 312                                        push @cc, $_;
 313
 314                                } elsif (!defined $subject) {
 315                                        $subject = $_;
 316                                }
 317                        }
 318
 319                        # A whitespace line will terminate the headers
 320                        if (m/^\s*$/) {
 321                                $header_done = 1;
 322                        }
 323                } else {
 324                        $message .=  $_;
 325                        if (/^Signed-off-by: (.*)$/i) {
 326                                my $c = $1;
 327                                chomp $c;
 328                                push @cc, $c;
 329                                printf("(sob) Adding cc: %s from line '%s'\n",
 330                                        $c, $_);
 331                        }
 332                }
 333        }
 334        close F;
 335
 336        $cc = join(", ", unique_email_list(@cc));
 337
 338        send_message();
 339
 340        # set up for the next message
 341        if ($chain_reply_to || length($reply_to) == 0) {
 342                $reply_to = $message_id;
 343        }
 344        make_message_id();
 345}
 346
 347if ($compose) {
 348        cleanup_compose_files();
 349}
 350
 351sub cleanup_compose_files() {
 352        unlink($compose_filename, $compose_filename . ".final");
 353
 354}
 355
 356
 357
 358sub unique_email_list(@) {
 359        my %seen;
 360        my @emails;
 361
 362        foreach my $entry (@_) {
 363                my $clean = Email::Valid->address($entry);
 364                next if $seen{$clean}++;
 365                push @emails, $entry;
 366        }
 367        return @emails;
 368}