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 27# Variables we fill in automatically, or via prompting: 28my(@to,@cc,$initial_reply_to,$initial_subject,@files,$from); 29 30# Behavior modification variables 31my($chain_reply_to) = (1); 32 33# Example reply to: 34#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; 35 36my$term= new Term::ReadLine 'git-send-email'; 37 38# Begin by accumulating all the variables (defined above), that we will end up 39# needing, first, from the command line: 40 41my$rc= GetOptions("from=s"=> \$from, 42"in-reply-to=s"=> \$initial_reply_to, 43"subject=s"=> \$initial_subject, 44"to=s"=> \@to, 45"chain-reply-to!"=> \$chain_reply_to, 46); 47 48# Now, let's fill any that aren't set in with defaults: 49 50open(GITVAR,"-|","git-var","-l") 51or die"Failed to open pipe from git-var:$!"; 52 53my($author,$committer); 54while(<GITVAR>) { 55chomp; 56my($var,$data) =split/=/,$_,2; 57my@fields=split/\s+/,$data; 58 59my$ident=join(" ",@fields[0...(@fields-3)]); 60 61if($vareq'GIT_AUTHOR_IDENT') { 62$author=$ident; 63}elsif($vareq'GIT_COMMITTER_IDENT') { 64$committer=$ident; 65} 66} 67close(GITVAR); 68 69 70if(!defined$from) { 71$from=$author||$committer; 72do{ 73$_=$term->readline("Who should the emails appear to be from? ", 74$from); 75while(!defined$_); 76 77$from=$_; 78print"Emails will be sent from: ",$from,"\n"; 79} 80 81if(!@to) { 82do{ 83$_=$term->readline("Who should the emails be sent to? ", 84""); 85}while(!defined$_); 86my$to=$_; 87push@to,split/,/,$to; 88} 89 90if(!defined$initial_subject) { 91do{ 92$_=$term->readline("What subject should the emails start with? ", 93$initial_subject); 94}while(!defined$_); 95$initial_subject=$_; 96} 97 98if(!defined$initial_reply_to) { 99do{ 100$_=$term->readline("Message-ID to be used as In-Reply-To? ", 101$initial_reply_to); 102}while(!defined$_); 103 104$initial_reply_to=$_; 105$initial_reply_to=~s/(^\s+|\s+$)//g; 106} 107 108# Now that all the defaults are set, process the rest of the command line 109# arguments and collect up the files that need to be processed. 110formy$f(@ARGV) { 111if(-d $f) { 112opendir(DH,$f) 113or die"Failed to opendir$f:$!"; 114 115push@files,map{ +$f."/".$_}grep{ -f $_} 116sort readdir(DH); 117 118}elsif(-f $f) { 119push@files,$f; 120 121}else{ 122print STDERR "Skipping$f- not found.\n"; 123} 124} 125 126if(@files) { 127print$_,"\n"for@files; 128}else{ 129print<<EOT; 130git-send-email-script [options] <file | directory> [... file | directory ] 131Options: 132 --from Specify the "From:" line of the email to be sent. 133 --to Specify the primary "To:" line of the email. 134 --subject Specify the initial "Subject:" line. 135 --in-reply-to Specify the first "In-Reply-To:" header line. 136 --chain-reply-to If set, the replies will all be to the first 137 email sent, rather than to the last email sent. 138 139Error: Please specify a file or a directory on the command line. 140EOT 141exit(1); 142} 143 144# Variables we set as part of the loop over files 145our($message_id,$cc,%mail,$subject,$reply_to,$message); 146 147 148# Usually don't need to change anything below here. 149 150# we make a "fake" message id by taking the current number 151# of seconds since the beginning of Unix time and tacking on 152# a random number to the end, in case we are called quicker than 153# 1 second since the last time we were called. 154 155# We'll setup a template for the message id, using the "from" address: 156my$message_id_from= Email::Valid->address($from); 157my$message_id_template="<%s-git-send-email-$from>"; 158 159sub make_message_id 160{ 161my$date=`date "+\%s"`; 162chomp($date); 163my$pseudo_rand=int(rand(4200)); 164$message_id=sprintf$message_id_template,"$date$pseudo_rand"; 165#print "new message id = $message_id\n"; # Was useful for debugging 166} 167 168 169 170$cc=""; 171 172sub send_message 173{ 174my%to; 175$to{lc(Email::Valid->address($_))}++for(@to); 176 177my$to=join(",",keys%to); 178 179%mail= ( To =>$to, 180 From =>$from, 181 CC =>$cc, 182 Subject =>$subject, 183 Message =>$message, 184'Reply-to'=>$from, 185'In-Reply-To'=>$reply_to, 186'Message-ID'=>$message_id, 187'X-Mailer'=>"git-send-email-script", 188); 189 190$mail{smtp} ='localhost'; 191$mailcfg{mime} =0; 192 193#print Data::Dumper->Dump([\%mail],[qw(*mail)]); 194 195 sendmail(%mail)or die$Mail::Sendmail::error; 196 197print"OK. Log says:\n",$Mail::Sendmail::log; 198print"\n\n" 199} 200 201 202$reply_to=$initial_reply_to; 203make_message_id(); 204$subject=$initial_subject; 205 206foreachmy$t(@files) { 207my$F=$t; 208open(F,"<",$t)or die"can't open file$t"; 209 210@cc= (); 211my$found_mbox=0; 212my$header_done=0; 213$message=""; 214while(<F>) { 215if(!$header_done) { 216$found_mbox=1,next if(/^From /); 217chomp; 218 219if($found_mbox) { 220if(/^Subject:\s+(.*)$/) { 221$subject=$1; 222 223}elsif(/^(Cc|From):\s+(.*)$/) { 224printf("(mbox) Adding cc:%sfrom line '%s'\n", 225$2,$_); 226push@cc,$2; 227} 228 229}else{ 230# In the traditional 231# "send lots of email" format, 232# line 1 = cc 233# line 2 = subject 234# So let's support that, too. 235if(@cc==0) { 236printf("(non-mbox) Adding cc:%sfrom line '%s'\n", 237$_,$_); 238 239push@cc,$_; 240 241}elsif(!defined$subject) { 242$subject=$_; 243} 244} 245 246# A whitespace line will terminate the headers 247if(m/^\s*$/) { 248$header_done=1; 249} 250}else{ 251$message.=$_; 252if(/^Signed-off-by: (.*)$/i) { 253my$c=$1; 254chomp$c; 255push@cc,$c; 256printf("(sob) Adding cc:%sfrom line '%s'\n", 257$c,$_); 258} 259} 260} 261close F; 262 263my%clean_ccs; 264$clean_ccs{lc(Email::Valid->address($_))}++for@cc; 265 266$cc=join(",",keys%clean_ccs); 267 268 send_message(); 269 270# set up for the next message 271if($chain_reply_to||length($reply_to) ==0) { 272$reply_to=$message_id; 273} 274 make_message_id(); 275# $subject = "Re: ".$initial_subject; 276}