send-email: lazily load modules for a big speedup

Optimize the time git-send-email takes to do even the simplest of things (such as serving up "-h") from around ~150ms to ~80ms-~90ms by lazily loading the modules it requires. Before this change Devel::TraceUse would report 99/97 used modules under NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s to run t9001-send-email.sh, down from ~20s. Changing File::Spec::Functions::{catdir,catfile} to invoking class methods on File::Spec itself is idiomatic. See [1] for a more elaborate explanation, the resulting code behaves the same way, just without the now-pointless function wrapper. 1. http://lore.kernel.org/git/8735u8mmj9.fsf@evledraar.gmail.com Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed May 28, 2021 at 11:23 UTC f4dc9432fd287bde9100488943baf3c6a04d90d1
1 file changed +39 -32
git-send-email.perl
+39 -32
@@ -19,20 +19,10 @@
19 use 5.008;
20 use strict;
21 use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();
22 -use POSIX qw/strftime/;
23 -use Term::ReadLine;
22 use Getopt::Long;
25 -use Text::ParseWords;
26 -use Term::ANSIColor;
27 -use File::Temp qw/ tempdir tempfile /;
28 -use File::Spec::Functions qw(catdir catfile);
23 use Git::LoadCPAN::Error qw(:try);
30 -use Cwd qw(abs_path cwd);
24 use Git;
25 use Git::I18N;
33 -use Net::Domain ();
34 -use Net::SMTP ();
35 -use Git::LoadCPAN::Mail::Address;
26
27 Getopt::Long::Configure qw/ pass_through /;
28
@@ -166,7 +156,6 @@ sub format_2822_time {
156 );
157 }
158
169 -my $have_email_valid = eval { require Email::Valid; 1 };
159 my $smtp;
160 my $auth;
161 my $num_sent = 0;
@@ -192,14 +181,6 @@ my (@config_bcc, @getopt_bcc);
181
182 my $repo = eval { Git->repository() };
183 my @repo = $repo ? ($repo) : ();
195 -my $term = eval {
196 - $ENV{"GIT_SEND_EMAIL_NOTTY"}
197 - ? Term::ReadLine->new('git-send-email', \*STDIN, \*STDOUT)
198 - : Term::ReadLine->new('git-send-email');
199 -};
200 -if ($@) {
201 - $term = FakeTerm->new("$@: going non-interactive");
202 -}
184
185 # Behavior modification variables
186 my ($quiet, $dry_run) = (0, 0);
@@ -319,9 +300,9 @@ my %config_path_settings = (
300
301 # Handle Uncouth Termination
302 sub signal_handler {
322 -
303 # Make text normal
324 - print color("reset"), "\n";
304 + require Term::ANSIColor;
305 + print Term::ANSIColor::color("reset"), "\n";
306
307 # SMTP password masked
308 system "stty echo";
@@ -602,11 +583,13 @@ my ($repoauthor, $repocommitter);
583 }
584
585 sub parse_address_line {
586 + require Git::LoadCPAN::Mail::Address;
587 return map { $_->format } Mail::Address->parse($_[0]);
588 }
589
590 sub split_addrs {
609 - return quotewords('\s*,\s*', 1, @_);
591 + require Text::ParseWords;
592 + return Text::ParseWords::quotewords('\s*,\s*', 1, @_);
593 }
594
595 my %aliases;
@@ -655,10 +638,11 @@ my %parse_alias = (
638 s/\\"/"/g foreach @addr;
639 $aliases{$alias} = \@addr
640 }}},
658 - mailrc => sub { my $fh = shift; while (<$fh>) {
641 + mailrc => sub { my $fh = shift; while (<$fh>) {
642 if (/^alias\s+(\S+)\s+(.*?)\s*$/) {
643 + require Text::ParseWords;
644 # spaces delimit multiple addresses
661 - $aliases{$1} = [ quotewords('\s+', 0, $2) ];
645 + $aliases{$1} = [ Text::ParseWords::quotewords('\s+', 0, $2) ];
646 }}},
647 pine => sub { my $fh = shift; my $f='\t[^\t]*';
648 for (my $x = ''; defined($x); $x = $_) {
@@ -730,7 +714,8 @@ while (defined(my $f = shift @ARGV)) {
714 opendir my $dh, $f
715 or die sprintf(__("Failed to opendir %s: %s"), $f, $!);
716
733 - push @files, grep { -f $_ } map { catfile($f, $_) }
717 + require File::Spec;
718 + push @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }
719 sort readdir $dh;
720 closedir $dh;
721 } elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {
@@ -743,7 +728,8 @@ while (defined(my $f = shift @ARGV)) {
728 if (@rev_list_opts) {
729 die __("Cannot run git format-patch from outside a repository\n")
730 unless $repo;
746 - push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
731 + require File::Temp;
732 + push @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);
733 }
734
735 @files = handle_backup_files(@files);
@@ -780,9 +766,10 @@ sub get_patch_subject {
766 if ($compose) {
767 # Note that this does not need to be secure, but we will make a small
768 # effort to have it be unique
769 + require File::Temp;
770 $compose_filename = ($repo ?
784 - tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
785 - tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
771 + File::Temp::tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
772 + File::Temp::tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
773 open my $c, ">", $compose_filename
774 or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!);
775
@@ -889,6 +876,19 @@ EOT3
876 do_edit(@files);
877 }
878
879 +sub term {
880 + my $term = eval {
881 + require Term::ReadLine;
882 + $ENV{"GIT_SEND_EMAIL_NOTTY"}
883 + ? Term::ReadLine->new('git-send-email', \*STDIN, \*STDOUT)
884 + : Term::ReadLine->new('git-send-email');
885 + };
886 + if ($@) {
887 + $term = FakeTerm->new("$@: going non-interactive");
888 + }
889 + return $term;
890 +}
891 +
892 sub ask {
893 my ($prompt, %arg) = @_;
894 my $valid_re = $arg{valid_re};
@@ -896,6 +896,7 @@ sub ask {
896 my $confirm_only = $arg{confirm_only};
897 my $resp;
898 my $i = 0;
899 + my $term = term();
900 return defined $default ? $default : undef
901 unless defined $term->IN and defined fileno($term->IN) and
902 defined $term->OUT and defined fileno($term->OUT);
@@ -1076,6 +1077,7 @@ sub extract_valid_address {
1077 return $address if ($address =~ /^($local_part_regexp)$/);
1078
1079 $address =~ s/^\s*<(.*)>\s*$/$1/;
1080 + my $have_email_valid = eval { require Email::Valid; 1 };
1081 if ($have_email_valid) {
1082 return scalar Email::Valid->address($address);
1083 }
@@ -1135,7 +1137,8 @@ my ($message_id_stamp, $message_id_serial);
1137 sub make_message_id {
1138 my $uniq;
1139 if (!defined $message_id_stamp) {
1138 - $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time));
1140 + require POSIX;
1141 + $message_id_stamp = POSIX::strftime("%Y%m%d%H%M%S.$$", gmtime(time));
1142 $message_id_serial = 0;
1143 }
1144 $message_id_serial++;
@@ -1305,6 +1308,7 @@ sub valid_fqdn {
1308 sub maildomain_net {
1309 my $maildomain;
1310
1311 + require Net::Domain;
1312 my $domain = Net::Domain::domainname();
1313 $maildomain = $domain if valid_fqdn($domain);
1314
@@ -1315,6 +1319,7 @@ sub maildomain_mta {
1319 my $maildomain;
1320
1321 for my $host (qw(mailhost localhost)) {
1322 + require Net::SMTP;
1323 my $smtp = Net::SMTP->new($host);
1324 if (defined $smtp) {
1325 my $domain = $smtp->domain;
@@ -1994,13 +1999,15 @@ sub validate_patch {
1999
2000 if ($repo) {
2001 my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
1997 - my $validate_hook = catfile($hooks_path,
2002 + require File::Spec;
2003 + my $validate_hook = File::Spec->catfile($hooks_path,
2004 'sendemail-validate');
2005 my $hook_error;
2006 if (-x $validate_hook) {
2001 - my $target = abs_path($fn);
2007 + require Cwd;
2008 + my $target = Cwd::abs_path($fn);
2009 # The hook needs a correct cwd and GIT_DIR.
2003 - my $cwd_save = cwd();
2010 + my $cwd_save = Cwd::cwd();
2011 chdir($repo->wc_path() or $repo->repo_path())
2012 or die("chdir: $!");
2013 local $ENV{"GIT_DIR"} = $repo->repo_path();