diff options
-rwxr-xr-x | git-send-email.perl | 133 |
1 files changed, 127 insertions, 6 deletions
diff --git a/git-send-email.perl b/git-send-email.perl index ac3b02da68..b36a925939 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -203,6 +203,7 @@ my ($validate, $confirm); my (@suppress_cc); my ($auto_8bit_encoding); my ($compose_encoding); +my ($msgid_cache_file, $msgid_cache_maxsize); my ($debug_net_smtp) = 0; # Net::SMTP, see send_message() @@ -237,6 +238,8 @@ my %config_settings = ( "from" => \$sender, "assume8bitencoding" => \$auto_8bit_encoding, "composeencoding" => \$compose_encoding, + "msgidcachefile" => \$msgid_cache_file, + "msgidcachemaxsize" => \$msgid_cache_maxsize, ); my %config_path_settings = ( @@ -796,11 +799,23 @@ sub expand_one_alias { @bcclist = expand_aliases(@bcclist); @bcclist = validate_address_list(sanitize_address_list(@bcclist)); +if ($compose && $compose > 0) { + @files = ($compose_filename . ".final", @files); +} + if ($thread && !defined $initial_reply_to && $prompting) { + my @choices = (); + if ($msgid_cache_file) { + my $first_subject = get_patch_subject($files[0]); + $first_subject =~ s/^GIT: //; + @choices = msgid_cache_getmatches($first_subject, 10); + @choices = map {[$_->{id}, sprintf "[%s] %s", format_2822_time($_->{epoch}), $_->{subject}]} @choices; + } $initial_reply_to = ask( "Message-ID to be used as In-Reply-To for the first email (if any)? ", default => "", - valid_re => qr/\@.*\./, confirm_only => 1); + valid_re => qr/\@.*\./, confirm_only => 1, + choices => \@choices); } if (defined $initial_reply_to) { $initial_reply_to =~ s/^\s*<?//; @@ -818,10 +833,6 @@ if (!defined $smtp_server) { $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug* } -if ($compose && $compose > 0) { - @files = ($compose_filename . ".final", @files); -} - # Variables we set as part of the loop over files our ($message_id, %mail, $subject, $reply_to, $references, $message, $needs_confirm, $message_num, $ask_default); @@ -1136,7 +1147,7 @@ sub send_message { my $to = join (",\n\t", @recipients); @recipients = unique_email_list(@recipients,@cc,@bcclist); @recipients = (map { extract_valid_address_or_die($_) } @recipients); - my $date = format_2822_time($time++); + my $date = format_2822_time($time); my $gitversion = '@@GIT_VERSION@@'; if ($gitversion =~ m/..GIT_VERSION../) { $gitversion = Git::version(); @@ -1477,6 +1488,11 @@ foreach my $t (@files) { my $message_was_sent = send_message(); + if ($message_was_sent && $msgid_cache_file && !$dry_run) { + msgid_cache_this($message_id, $message_num == 1 ? 1 : 0, , $time, $subject); + } + $time++; + # set up for the next message if ($thread && $message_was_sent && ($chain_reply_to || !defined $reply_to || length($reply_to) == 0 || @@ -1521,6 +1537,8 @@ sub cleanup_compose_files { $smtp->quit if $smtp; +msgid_cache_write() if $msgid_cache_file && !$dry_run; + sub unique_email_list { my %seen; my @emails; @@ -1569,3 +1587,106 @@ sub body_or_subject_has_nonascii { } return 0; } + +my @msgid_new_entries; +sub msgid_cache_this { + my $msgid = shift; + my $first = shift; + my $epoch = shift; + my $subject = shift; + # Make sure there are no tabs which will confuse us, and save + # some valuable horizontal real-estate by removing redundant + # whitespace. + if ($subject) { + $subject =~ s/^\s+|\s+$//g; + $subject =~ s/\s+/ /g; + } + # Replace undef or the empty string by an actual string. + $subject = '(none)' if (!defined $subject || $subject eq ''); + + push @msgid_new_entries, {id => $msgid, first => $first, subject => $subject, epoch => $epoch}; +} + + +# For now, use a simple tab-separated format: +# +# $id\t$wasfirst\t$unixtime\t$subject\n +sub msgid_cache_read { + my $fh; + my $line; + my @entries; + if (not open ($fh, '<', $msgid_cache_file)) { + # A non-existing cache file is ok, but should we warn if errno != ENOENT? + return (); + } + while ($line = <$fh>) { + chomp($line); + my ($id, $first, $epoch, $subject) = split /\t/, $line; + push @entries, {id=>$id, first=>$first, epoch=>$epoch, subject=>$subject}; + } + close($fh); + return @entries; +} + +sub msgid_cache_getmatches { + my ($first_subject, $maxentries) = @_; + my @list = msgid_cache_read(); + + # We need to find the message-ids which are most likely to be + # useful. There are probably better ways to do this, but for + # now we simply count how many words in the old subject also + # appear in $first_subject. + my %words = map {$_ => 1} msgid_subject_words($first_subject); + for my $item (@list) { + # Emails which were first in a batch are more likely + # to be used for followups (cf. the example in "man + # git-send-email"), so give those a head start. + my $score = $item->{first} ? 3 : 0; + for (msgid_subject_words($item->{subject})) { + $score++ if exists $words{$_}; + } + $item->{score} = $score; + } + @list = sort {$b->{score} <=> $a->{score} || + $b->{epoch} <=> $a->{epoch}} @list; + @list = @list[0 .. $maxentries-1] if (@list > $maxentries); + return @list; +} + +sub msgid_subject_words { + my $subject = shift; + # Ignore initial "[PATCH 02/47]" + $subject =~ s/^\s*\[.*?\]//; + my @words = split /\s+/, $subject; + # Ignore short words. + @words = grep { length > 3 } @words; + return @words; +} + +sub msgid_cache_write { + msgid_cache_do_write(1, \@msgid_new_entries); + + if (defined $msgid_cache_maxsize && $msgid_cache_maxsize =~ m/^\s*([0-9]+)\s*([kKmMgG]?)$/) { + my %SI = ('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9); + $msgid_cache_maxsize = $1 * $SI{lc($2)}; + } + else { + $msgid_cache_maxsize = 100000; + } + if (-s $msgid_cache_file > $msgid_cache_maxsize) { + my @entries = msgid_cache_read(); + splice @entries, 0, int(@entries/2); + msgid_cache_do_write(0, \@entries); + } +} + +sub msgid_cache_do_write { + my $append = shift; + my $entries = shift; + my $fh; + if (not open($fh, $append ? '>>' : '>', $msgid_cache_file)) { + die "cannot open $msgid_cache_file for writing: $!"; + } + printf $fh "%s\t%d\t%s\t%s\n", $_->{id}, $_->{first}, $_->{epoch}, $_->{subject} for (@$entries); + close($fh); +} |