This is on top of my just-submitted [1] which in turn is on top of
send-email work of mine sitting in "next".
I was meaning to hold off on these patches for a bit, but given the
concurrent on-list discussion about doing config discovery in
send-email I wanted to send this now.
This combines by not-picked-up[1] recent patches to remove the support
for the "sendemail.smtpssl" variable with the later patches showing
where that effort was really going.
As noted in the subject this speeds up git-send-email invocations by
~2x or more, and brings the very slow t9001 test from running in ~26s
on my box to ~12s. It's no longer consistently the slowest test I run.
This is basically done in two ways: We lazily invoke "git config" to
get config, before it's very eager, and deferring Perl compilation
with s/use/require/g.
1. https://lore.kernel.org/git/patch-1.1-92571a8cf7-20210512T094803Z-avarab@gmail.com/
2. https://lore.kernel.org/git/cover-0.2-00000000000-20210411T144128Z-avarab@gmail.com/
Ævar Arnfjörð Bjarmason (9):
send-email: remove non-working support for "sendemail.smtpssl"
send-email: refactor sendemail.smtpencryption config parsing
send-email: lazily load config for a big speedup
send-email: lazily shell out to "git var"
send-email: use function syntax instead of barewords
send-email: get rid of indirect object syntax
send-email: lazily load modules for a big speedup
perl: lazily load some common Git.pm setup code
send-email: move trivial config handling to Perl
Documentation/config/sendemail.txt | 3 -
git-send-email.perl | 145 +++++++++++++++++------------
perl/Git.pm | 49 +++++-----
3 files changed, 111 insertions(+), 86 deletions(-)
--
2.31.1.909.g789bb6d90e
Remove the already dead code to support "sendemail.smtssl" by finally
removing the dead code supporting the configuration option.
In f6bebd121ac (git-send-email: add support for TLS via
Net::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was
documented as deprecated, later in 65180c66186 (List send-email config
options in config.txt., 2009-07-22) the "sendemail.smtpssl"
configuration option was also documented as such.
Then in in 3ff15040e22 (send-email: fix regression in
sendemail.identity parsing, 2019-05-17) I unintentionally removed
support for it by introducing a bug in read_config().
As can be seen from the diff context we've already returned unless
$enc i defined, so it's not possible for us to reach the "elsif"
branch here. This code was therefore already dead since Git v2.23.0.
So let's just remove it. We were already 11 years into a stated
deprecation period of this variable when 3ff15040e22 landed, now it's
around 13. Since it hasn't worked anyway for around 2 years it looks
like we can safely remove it.
The --smtp-ssl option is still deprecated, if someone cares they can
follow-up and remove that too, but unlike the config option that one
could still be in use in the wild. I'm just removing this code that's
provably unused already.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Documentation/config/sendemail.txt | 3 ---
git-send-email.perl | 6 +-----
2 files changed, 1 insertion(+), 8 deletions(-)
@@ -8,9 +8,6 @@ sendemail.smtpEncryption:: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism.-sendemail.smtpssl (deprecated)::- Deprecated alias for 'sendemail.smtpEncryption = ssl'.- sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification.
With the removal of the support for sendemail.smtpssl in the preceding
commit the parsing of sendemail.smtpencryption is no longer special,
and can by moved to %config_settings.
This gets us rid of an unconditional call to Git::config(), which as
we'll see in subsequent commits matters for startup performance.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
@@ -277,6 +277,7 @@ sub do_edit {);my%config_settings=(+"smtpencryption"=>\$smtp_encryption,"smtpserver"=>\$smtp_server,"smtpserverport"=>\$smtp_server_port,"smtpserveroption"=>\@smtp_server_options,
@@ -377,14 +378,6 @@ sub read_config {$$target=$v;}}--if(!defined$smtp_encryption){-my$setting="$prefix.smtpencryption";-my$enc=Git::config(@repo,$setting);-returnunlessdefined$enc;-returnif$configured->{$setting}++;-$smtp_encryption=$enc;-}}# sendemail.identity yields to --identity. We must parse this
Reduce the time it takes git-send-email to get to even the most
trivial of tasks (such as serving up its "-h" output) by first listing
config keys that exist, and only then only call e.g. "git config
--bool" on them if they do.
Over a lot of runs this speeds the time to "-h" up for me from ~250ms
to ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to
~20s.
This introduces a race condition where we'll do the "wrong" thing if a
config key were to be inserted between us discovering the list and
calling read_config(), i.e. we won't know about the racily added
key. In theory this is a change in behavior, in practice it doesn't
matter.
The config_regexp() function being changed here was added in
dd84e528a34 (git-send-email: die if sendmail.* config is set,
2020-07-23) for use by git-send-email. So we can change its odd return
value in the case where no values are found by "git config". The
difference in the *.pm code would matter if it was invoked in scalar
context, but now it no longer is.
Arguably this caching belongs in Git.pm itself, but in lieu of
modifying it for all its callers let's only do this for "git
send-email". The other big potential win would be "git svn", but
unlike "git send-email" it doesn't check tens of config variables one
at a time at startup (in my brief testing it doesn't check any).
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 35 ++++++++++++++++++++++++++---------
perl/Git.pm | 4 ++--
2 files changed, 28 insertions(+), 11 deletions(-)
@@ -349,8 +351,10 @@ sub read_config {foreachmy$setting(keys%config_path_settings){my$target=$config_path_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config_path(@repo,"$prefix.$setting");+my@values=Git::config_path(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;
@@ -365,14 +369,16 @@ sub read_config {foreachmy$setting(keys%config_settings){my$target=$config_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,"$prefix.$setting");+my@values=Git::config(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,"$prefix.$setting");+my$v=Git::config(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -380,9 +386,20 @@ sub read_config {}}+# Save ourselves a lot of work of shelling out to 'git config' (it+# parses 'bool' etc.) by only doing so for config keys that exist.+my%known_config_keys;+{+my@known_config_keys=Git::config_regexp("^sende?mail[.]");+@known_config_keys{@known_config_keys}=();+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.-$identity=Git::config(@repo,"sendemail.identity");+{+my$key="sendemail.identity";+$identity=Git::config(@repo,$key)ifexists$known_config_keys{$key};+}my$rc=GetOptions("identity=s"=>\$identity,"no-identity"=>\$no_identity,
@@ -393,8 +410,8 @@ sub read_config {# Now we know enough to read the config{my%configured;-read_config(\%configured,"sendemail.$identity")ifdefined$identity;-read_config(\%configured,"sendemail");+read_config(\%known_config_keys,\%configured,"sendemail.$identity")ifdefined$identity;+read_config(\%known_config_keys,\%configured,"sendemail");}# Begin by accumulating all the variables (defined above), that we will end up
@@ -478,7 +495,7 @@ sub read_config {usage();}-if($forbid_sendmail_variables&&(scalarGit::config_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&grep{/^sendmail/s}keys%known_config_keys){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
@@ -754,8 +754,8 @@ sub config_regexp {}catchGit::Error::Commandwith{my$E=shift;if($E->value()==1){-my@matches=();-return@matches;+# Key(s) not found.+return;}else{throw$E;}
Optimize git-send-email by only shelling out to "git var" if we need
to. This is easily done by re-inventing our own small version of
perl's Memoize module.
I suppose I could just use Memoize itself, but in a subsequent patch
I'll be micro-optimizing send-email's use of dependencies. Using
Memoize is a measly extra 5-10 milliseconds, but as we'll see that'll
end up mattering for us in the end.
This brings the runtime of a plain "send-email" from around ~160-170ms
to ~140m-150ms. The runtime of the tests is around the same, or around
~20s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
@@ -560,8 +560,18 @@ sub read_config {}my($repoauthor,$repocommitter);-($repoauthor)=Git::ident_person(@repo,'author');-($repocommitter)=Git::ident_person(@repo,'committer');+{+my%cache;+my($author,$committer);+my$common=sub{+my($what)=@_;+return$cache{$what}ifexists$cache{$what};+($cache{$what})=Git::ident_person(@repo,$what);+return$cache{$what};+};+$repoauthor=sub{$common->('author')};+$repocommitter=sub{$common->('committer')};+}subparse_address_line{returnmap{$_->format}Mail::Address->parse($_[0]);
@@ -749,7 +759,7 @@ sub get_patch_subject {ordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);-my$tpl_sender=$sender||$repoauthor||$repocommitter||'';+my$tpl_sender=$sender||$repoauthor->()||$repocommitter->()||'';my$tpl_subject=$initial_subject||'';my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';
@@ -955,7 +965,7 @@ sub file_declares_8bit_cte {$sender=~s/^\s+|\s+$//g;($sender)=expand_aliases($sender);}else{-$sender=$repoauthor||$repocommitter||'';+$sender=$repoauthor->()||$repocommitter->()||'';}# $sender could be an already sanitized address
@@ -1104,7 +1114,7 @@ sub make_message_id {$uniq="$message_id_stamp-$message_id_serial";my$du_part;-for($sender,$repocommitter,$repoauthor){+for($sender,$repocommitter->(),$repoauthor->()){$du_part=extract_valid_address(sanitize_address($_));lastif(defined$du_partand$du_partne'');}
Change calls like "__ 'foo'" to "__('foo')" so the Perl compiler
doesn't have to guess that "__" is a function. This makes the code
more readable.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -678,7 +678,7 @@ sub is_format_patch_arg {if(defined($format_patch)){return$format_patch;}-diesprintf(__<<EOF,$f,$f);+diesprintf(__(<<EOF),$f,$f);File'%s'existsbutitcouldalsobetherangeofcommitstoproducepatchesfor.Pleasedisambiguateby...
@@ -764,7 +764,7 @@ sub get_patch_subject {my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';-print$c<<EOT1,Git::prefix_lines("GIT: ",__<<EOT2),<<EOT3;+print$c<<EOT1,Git::prefix_lines("GIT: ",__(<<EOT2)),<<EOT3;From$tpl_sender# This line is ignored.EOT1Linesbeginningin"GIT:"willberemoved.
Change indirect object syntax such as "new X ARGS" to
"X->new(ARGS)". This allows perl to see what "new" is at compile-time
without having loaded Term::ReadLine. This doesn't matter now, but
will in a subsequent commit when we start lazily loading it.
Let's do the same for the adjacent "FakeTerm" package for consistency,
even though we're not going to conditionally load it.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
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.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 70 +++++++++++++++++++++++++--------------------
1 file changed, 39 insertions(+), 31 deletions(-)
@@ -702,7 +687,8 @@ sub is_format_patch_arg {opendirmy$dh,$fordiesprintf(__("Failed to opendir %s: %s"),$f,$!);-push@files,grep{-f$_}map{catfile($f,$_)}+requireFile::Spec;+push@files,grep{-f$_}map{File::Spec->catfile($f,$_)}sortreaddir$dh;closedir$dh;}elsif((-f$for-p$f)and!is_format_patch_arg($f)){
@@ -715,7 +701,8 @@ sub is_format_patch_arg {if(@rev_list_opts){die__("Cannot run git format-patch from outside a repository\n")unless$repo;-push@files,$repo->command('format-patch','-o',tempdir(CLEANUP=>1),@rev_list_opts);+requireFile::Temp;+push@files,$repo->command('format-patch','-o',File::Temp::tempdir(CLEANUP=>1),@rev_list_opts);}@files=handle_backup_files(@files);
@@ -752,9 +739,10 @@ sub get_patch_subject {if($compose){# Note that this does not need to be secure, but we will make a small# effort to have it be unique+requireFile::Temp;$compose_filename=($repo?-tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):-tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];openmy$c,">",$compose_filenameordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);
@@ -861,6 +849,19 @@ sub get_patch_subject {do_edit(@files);}+subterm{+my$term=eval{+requireTerm::ReadLine;+$ENV{"GIT_SEND_EMAIL_NOTTY"}+?Term::ReadLine->new('git-send-email',\*STDIN,\*STDOUT)+:Term::ReadLine->new('git-send-email');+};+if($@){+$term=FakeTerm->new("$@: going non-interactive");+}+return$term;+}+subask{my($prompt,%arg)=@_;my$valid_re=$arg{valid_re};
@@ -868,6 +869,7 @@ sub ask {my$confirm_only=$arg{confirm_only};my$resp;my$i=0;+my$term=term();returndefined$default?$default:undefunlessdefined$term->INanddefinedfileno($term->IN)anddefined$term->OUTanddefinedfileno($term->OUT);
@@ -1048,6 +1050,7 @@ sub extract_valid_address {return$addressif($address=~ /^($local_part_regexp)$/);$address=~s/^\s*<(.*)>\s*$/$1/;+my$have_email_valid=eval{requireEmail::Valid;1};if($have_email_valid){returnscalarEmail::Valid->address($address);}
@@ -1107,7 +1110,8 @@ sub validate_address_list {submake_message_id{my$uniq;if(!defined$message_id_stamp){-$message_id_stamp=strftime("%Y%m%d%H%M%S.$$",gmtime(time));+requirePOSIX;+$message_id_stamp=POSIX::strftime("%Y%m%d%H%M%S.$$",gmtime(time));$message_id_serial=0;}$message_id_serial++;
@@ -1277,6 +1281,7 @@ sub valid_fqdn {submaildomain_net{my$maildomain;+requireNet::Domain;my$domain=Net::Domain::domainname();$maildomain=$domainifvalid_fqdn($domain);
@@ -1287,6 +1292,7 @@ sub maildomain_mta {my$maildomain;formy$host(qw(mailhost localhost)){+requireNet::SMTP;my$smtp=Net::SMTP->new($host);if(defined$smtp){my$domain=$smtp->domain;
@@ -1965,13 +1971,15 @@ sub validate_patch {my($fn,$xfer_encoding)=@_;if($repo){-my$validate_hook=catfile($repo->hooks_path(),+requireFile::Spec;+my$validate_hook=File::Spec->catfile($repo->hooks_path(),'sendemail-validate');my$hook_error;if(-x$validate_hook){-my$target=abs_path($fn);+requireCwd;+my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=cwd();+my$cwd_save=Cwd::cwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
Instead of unconditionally requiring modules such as File::Spec, let's
only load them when needed. This speeds up code that only needs a
subset of the features Git.pm provides.
This brings a plain invocation of "git send-email" down from 52/37
loaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes
~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh
test is down to ~13s from ~15s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
perl/Git.pm | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
@@ -191,13 +185,15 @@ sub repository {$dir=undef;};+requireCwd;if($dir){+requireFile::Spec;File::Spec->file_name_is_absolute($dir)or$dir=$opts{Directory}.'/'.$dir;-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);# If --git-dir went ok, this shouldn't die either.my$prefix=$search->command_oneline('rev-parse','--show-prefix');-$dir=abs_path($opts{Directory}).'/';+$dir=Cwd::abs_path($opts{Directory}).'/';if($prefix){if(substr($dir,-length($prefix))ne$prefix){throwError::Simple("rev-parse confused me - $dir does not have trailing $prefix");
@@ -223,7 +219,7 @@ sub repository {throwError::Simple("fatal: Not a git repository: $dir");}-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);}delete$opts{Directory};
@@ -408,10 +404,12 @@ sub command_bidi_pipe {my$cwd_save=undef;if($self){shift;-$cwd_save=cwd();+requireCwd;+$cwd_save=Cwd::cwd();_setup_git_cmd_env($self);}-$pid=open2($in,$out,'git',@_);+requireIPC::Open2;+$pid=IPC::Open2::open2($in,$out,'git',@_);chdir($cwd_save)if$cwd_save;return($pid,$in,$out,join(' ',@_));}
@@ -538,7 +536,8 @@ sub get_tz_offset {my$t=shift||time;my@t=localtime($t);$t[5]+=1900;-my$gm=timegm(@t);+requireTime::Local;+my$gm=Time::Local::timegm(@t);my$sign=qw(++-)[$gm<=>$t];returnsprintf("%s%02d%02d",$sign,(gmtime(abs($t-$gm)))[2,1]);}
@@ -629,7 +628,8 @@ sub hooks_path {my($self)=@_;my$dir=$self->command_oneline('rev-parse','--git-path','hooks');-my$abs=abs_path($dir);+requireCwd;+my$abs=Cwd::abs_path($dir);return$abs;}
@@ -1353,6 +1353,7 @@ sub _temp_cache {my$n=$name;$n=~s/\W/_/g;#nostrangechars+requireFile::Temp;($$temp_fd,$fname)=File::Temp::tempfile("Git_${n}_XXXXXX",UNLINK=>1,DIR=>$tmpdir,)orthrowError::Simple("couldn't open new temp file");
@@ -1375,9 +1376,9 @@ sub temp_reset {truncate$temp_fd,0orthrowError::Simple("couldn't truncate file");-sysseek($temp_fd,0,SEEK_SET)andseek($temp_fd,0,SEEK_SET)+sysseek($temp_fd,0,Fcntl::SEEK_SET())andseek($temp_fd,0,Fcntl::SEEK_SET())orthrowError::Simple("couldn't seek to beginning of file");-sysseek($temp_fd,0,SEEK_CUR)==0andtell($temp_fd)==0+sysseek($temp_fd,0,Fcntl::SEEK_CUR())==0andtell($temp_fd)==0orthrowError::Simple("expected file position to be reset");}
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
values we're interested in.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
other "--bool" cases to "git config". As noted in a preceding commit
we're free to change the config_regexp() function, it's only used by
"git send-email".
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We no run just one "git config"
invocation on startup instead of 8, the exact number will differ based
on the local sendemail.* config. I happen to have 8 of those set.
This brings the runtime of t9001-send-email.sh from ~13s down to ~12s
for me. The change there is less impressive as many of those tests set
various config values, and we're also getting to the point of
diminishing returns for optimizing "git send-email" itself.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 17 ++++++++++-------
perl/Git.pm | 10 +++++-----
2 files changed, 15 insertions(+), 12 deletions(-)
@@ -325,7 +325,10 @@ sub read_config {my$target=$config_bool_settings{$setting};my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};-my$v=Git::config_bool(@repo,$key);+my$v=(@{$known_keys->{$key}}==1&&+$known_keys->{$key}->[0]=~ /^(?:true|false)$/s)+?$known_keys->{$key}->[0]eq'true'+:Git::config_bool(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -354,14 +357,12 @@ sub read_config {my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,$key);-nextunless@values;+my@values=@{$known_keys->{$key}};nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,$key);-nextunlessdefined$v;+my$v=$known_keys->{$key}->[0];nextif$configured->{$setting}++;$$target=$v;}
@@ -372,8 +373,10 @@ sub read_config {# parses 'bool' etc.) by only doing so for config keys that exist.my%known_config_keys;{-my@known_config_keys=Git::config_regexp("^sende?mail[.]");-@known_config_keys{@known_config_keys}=();+my@kv=Git::config_regexp("^sende?mail[.]");+while(my($k,$v)=splice@kv,0,2){+push@{$known_config_keys{$k}}=>$v;+}}# sendemail.identity yields to --identity. We must parse this
From: Eric Wong <hidden> Date: 2021-05-12 20:35:11
Ævar Arnfjörð Bjarmason [off-list ref] wrote:
As noted in the subject this speeds up git-send-email invocations by
~2x or more, and brings the very slow t9001 test from running in ~26s
on my box to ~12s. It's no longer consistently the slowest test I run.
This is basically done in two ways: We lazily invoke "git config" to
get config, before it's very eager, and deferring Perl compilation
with s/use/require/g.
Nice. I've been doing similar things elsewhere and hoping to
find time to get around to git-svn at some point.
Ævar Arnfjörð Bjarmason (9):
send-email: remove non-working support for "sendemail.smtpssl"
send-email: refactor sendemail.smtpencryption config parsing
send-email: lazily load config for a big speedup
send-email: lazily shell out to "git var"
send-email: use function syntax instead of barewords
send-email: get rid of indirect object syntax
send-email: lazily load modules for a big speedup
perl: lazily load some common Git.pm setup code
send-email: move trivial config handling to Perl
I spotted some further optimizations for 7 and 8,
but otherwise consider this series:
Reviewed-by: Eric Wong <redacted>
From: Eric Wong <hidden> Date: 2021-05-12 20:36:40
Ævar Arnfjörð Bjarmason [off-list ref] wrote:
quoted hunk
@@ -408,10 +404,12 @@ sub command_bidi_pipe { my $cwd_save = undef; if ($self) { shift;- $cwd_save = cwd();+ require Cwd;+ $cwd_save = Cwd::cwd(); _setup_git_cmd_env($self);
(This also applies to 7/9)
Cwd::cwd() execs /bin/pwd (at least on Perl 5.28.1 in Debian 10x).
There should be a benefit from using Cwd::getcwd() here, instead.
strace -f -e execve perl -MCwd -E 'Cwd::cwd()
...confirms the execve is happening.
getcwd() takes 25ms vs 27ms of cwd() for me using the
"schedutil" CPU governor under Linux:
time perl -MCwd -E 'Cwd::getcwd()'
time perl -MCwd -E 'Cwd::cwd()'
From: Eric Sunshine <hidden> Date: 2021-05-12 20:37:15
On Wed, May 12, 2021 at 9:48 AM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
values we're interested in.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
other "--bool" cases to "git config". As noted in a preceding commit
we're free to change the config_regexp() function, it's only used by
"git send-email".
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We no run just one "git config"
s/no/now/
invocation on startup instead of 8, the exact number will differ based
on the local sendemail.* config. I happen to have 8 of those set.
This brings the runtime of t9001-send-email.sh from ~13s down to ~12s
for me. The change there is less impressive as many of those tests set
various config values, and we're also getting to the point of
diminishing returns for optimizing "git send-email" itself.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
From: Jeff King <hidden> Date: 2021-05-12 23:28:01
On Wed, May 12, 2021 at 03:48:25PM +0200, Ævar Arnfjörð Bjarmason wrote:
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
values we're interested in.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
other "--bool" cases to "git config". As noted in a preceding commit
we're free to change the config_regexp() function, it's only used by
"git send-email".
I think both of these cases should be safe.
It is a bit unfortunate to have to go through these contortions, but
this is definitely the best we can do for now. I think in the long run
it would be nice to have a "--stdin" mode for git-config, where we could
do something like:
git config --stdin <<\EOF
key=foo.bar
type=bool
default=false
key=another.key
type=color
default=red
EOF
But that doesn't exist yet, and using it would probably involve
rearranging send-email a bit (we would need an up-front list of all of
the keys we care about and their types). So I'm perfectly content with
this strategy in the meantime.
@@ -678,7 +678,7 @@ sub is_format_patch_arg {if(defined($format_patch)){return$format_patch;}-diesprintf(__<<EOF,$f,$f);+diesprintf(__(<<EOF),$f,$f);File'%s'existsbutitcouldalsobetherangeofcommitstoproducepatchesfor.Pleasedisambiguateby...
is how far the "EOF" marker is from the actual here-doc, syntactically
(plus the ugly indentation). I suspect this and other places might be
nicer to assign from the here-doc into a well-named variable.
But I think it's fine to leave for now (and I worry a bit that it may
turn into a rabbit hole, so we might be better to just leave it alone
anyway).
-Peff
From: Jeff King <hidden> Date: 2021-05-12 23:53:29
On Wed, May 12, 2021 at 03:48:16PM +0200, Ævar Arnfjörð Bjarmason wrote:
This combines by not-picked-up[1] recent patches to remove the support
for the "sendemail.smtpssl" variable with the later patches showing
where that effort was really going.
As noted in the subject this speeds up git-send-email invocations by
~2x or more, and brings the very slow t9001 test from running in ~26s
on my box to ~12s. It's no longer consistently the slowest test I run.
Nice. I have observed that with a decent number of cores, the running
time of the entire test suite correlates strongly with the running time
of t9001. :)
Here are timings for individual tests run with "prove --state=slow,save".
(This is on an 8-core machine using -j32, skipping cvs/svn/p4 tests,
and using a tmpfs via --root). The timings were computed with:
perl -MYAML -e '
$_ = do { local $/; <> };
# prove puts this non-YAML cruft at the end
s/\.\.\.$//s;
my $t = YAML::Load($_)->{tests};
print "$_->[1] $_->[0]\n" for
sort { $b->[1] <=> $a->[1] }
map { [$_, $t->{$_}->{elapsed}] }
keys(%$t);
' t/.prove | head
Before your patches, the whole sweet takes ~60-63s, and the top timings
(from a 63s run) are:
63.2607979774475 t9001-send-email.sh
51.742644071579 t0027-auto-crlf.sh
37.7909920215607 t3070-wildmatch.sh
27.09605717659 t7610-mergetool.sh
24.7028169631958 t7112-reset-submodule.sh
24.5535898208618 t5572-pull-submodule.sh
23.8404550552368 t9500-gitweb-standalone-no-errors.sh
22.3544380664825 t7400-submodule-basic.sh
21.7017750740051 t5510-fetch.sh
21.4575610160828 t3305-notes-fanout.sh
Now after, which takes ~54-59s (this is from a 54s run):
46.796669960022 t0027-auto-crlf.sh
32.5747599601746 t3070-wildmatch.sh
21.5069420337677 t7610-mergetool.sh
20.8392388820648 t1701-racy-split-index.sh
19.7403028011322 t5572-pull-submodule.sh
19.7386808395386 t9001-send-email.sh
19.4622302055359 t7112-reset-submodule.sh
18.9555768966675 t9500-gitweb-standalone-no-errors.sh
18.0672709941864 t7400-submodule-basic.sh
17.641391992569 t5510-fetch.sh
I have some messy patches to split t9001 into two segments. They were
waiting to get polished, but perhaps I can just discard them now. :)
Some side notes for those interested in timing the test suite:
- If I run t9001 standalone, it goes much faster, of course; the CPU
throttles down when we're running all the tests in parallel.
- Those are with "-x --verbose-log", which is nice for catching flaky
results. Dropping those seems to shave a few seconds off.
- A big chunk of time for t0027 and t3070 is spent running the sed-based
chain-linting for their huge tables of auto-generated tests (1400+
and 1800+ respectively). Dropping the sed linting for just those
tests knocks off about 30 CPU-seconds.
-Peff
From: Jeff King <hidden> Date: 2021-05-12 23:53:50
On Wed, May 12, 2021 at 03:48:16PM +0200, Ævar Arnfjörð Bjarmason wrote:
As noted in the subject this speeds up git-send-email invocations by
~2x or more, and brings the very slow t9001 test from running in ~26s
on my box to ~12s. It's no longer consistently the slowest test I run.
This is basically done in two ways: We lazily invoke "git config" to
get config, before it's very eager, and deferring Perl compilation
with s/use/require/g.
Splitting my reply, since the other one got deep into test-suite timing
details.
The techniques here look overall pretty reasonable. I think the module
lazy-loading makes the overall code a _little_ uglier, but IMHO the
speedup you're getting is worth it (I was surprised how much of the
improvement comes from that versus avoiding git-config subprocesses).
My only concern is changing the interface of Git::config_regexp() in the
final patch. Do we need to have a config_regexp_with_values() to avoid
breaking third-party users of the module?
-Peff
From: Felipe Contreras <hidden> Date: 2021-05-13 07:04:16
Jeff King wrote:
It is a bit unfortunate to have to go through these contortions, but
this is definitely the best we can do for now. I think in the long run
it would be nice to have a "--stdin" mode for git-config, where we could
do something like:
git config --stdin <<\EOF
key=foo.bar
type=bool
default=false
key=another.key
type=color
default=red
EOF
Why do we even have to specify the type? Shouldn't there be a registry
of configurations (a schema), so that all users don't have to do this?
--
Felipe Contreras
From: Jeff King <hidden> Date: 2021-05-13 07:26:57
On Thu, May 13, 2021 at 02:04:08AM -0500, Felipe Contreras wrote:
Jeff King wrote:
quoted
It is a bit unfortunate to have to go through these contortions, but
this is definitely the best we can do for now. I think in the long run
it would be nice to have a "--stdin" mode for git-config, where we could
do something like:
git config --stdin <<\EOF
key=foo.bar
type=bool
default=false
key=another.key
type=color
default=red
EOF
Why do we even have to specify the type? Shouldn't there be a registry
of configurations (a schema), so that all users don't have to do this?
One of the purposes of git-config is to serve third-party scripts that
store their own config keys that Git does not know about. So we can't
know the set of all possible types that will be asked about.
Obviously we could have git-config know internally about all of the keys
other parts of Git would ask about. But generally we have pushed that
knowledge out to the users of the keys, rather than any kind of central
registry.
-Peff
It is a bit unfortunate to have to go through these contortions, but
this is definitely the best we can do for now. I think in the long run
it would be nice to have a "--stdin" mode for git-config, where we could
do something like:
git config --stdin <<\EOF
key=foo.bar
type=bool
default=false
key=another.key
type=color
default=red
EOF
Why do we even have to specify the type? Shouldn't there be a registry
of configurations (a schema), so that all users don't have to do this?
Yes, we should be moving towards that. Currently we're not there, the
closest we've got is the generated config-list.h.
If we closed that loop properly you should be able to do:
git config --get --type=dwim clean.requireForce # or auto, or no --type
Or whatever, instead of:
git config --get --type=bool clean.requireForce
But we're not there yet, there's also edge cases here and there that
make a plain exhaustive registry hard, and send-email is one of them.
In its case the value of sendemail.identity=something (if any)
determines if/how e.g. sendemail.something.smtpEncryption is
interpreted.
I think we can do it with just a list of config variables where the
variable is either a string or a regex (in this case,
"^sendemail(?:[^.]+\.)?\.smtpEncryption$"), but I haven't tried. There
may be other tricky special-cases.
On Wed, May 12, 2021 at 03:48:16PM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
As noted in the subject this speeds up git-send-email invocations by
~2x or more, and brings the very slow t9001 test from running in ~26s
on my box to ~12s. It's no longer consistently the slowest test I run.
This is basically done in two ways: We lazily invoke "git config" to
get config, before it's very eager, and deferring Perl compilation
with s/use/require/g.
Splitting my reply, since the other one got deep into test-suite timing
details.
The techniques here look overall pretty reasonable. I think the module
lazy-loading makes the overall code a _little_ uglier, but IMHO the
speedup you're getting is worth it (I was surprised how much of the
improvement comes from that versus avoiding git-config subprocesses).
Yeah, it's mostly uglier, but I think it's worth it. Some parts are
better afterwards though, i.e. the SOME_RARE_BARE_WORD is now
Module::It::Is::In::SOME_RARE_BARE_WORD, which makes it easier to
understand where it's from.
My only concern is changing the interface of Git::config_regexp() in the
final patch. Do we need to have a config_regexp_with_values() to avoid
breaking third-party users of the module?
As noted in 3/9 I don't think we need to worry about it, it's recently
introduced (a few months) API in Git.pm for send-email itself. I think
we can just change it.
In general I think it's unfortunate that we have (at least in principle)
a "public by default" module like Git.pm that's mostly for our own use.
This series doesn't try to deal with that in general at all, I'm
somewhat of the opinion that we should just fork it at this
point. I.e. have a Git.pm we freeze in time, and a Git/Ours.pm that's
going to be the private API.
I stopped with these optimizations at the point of refactoring away
Error.pm, which is a large contributor to compilation time, but as long
as it's a public API that can't be done without changing the public
API. If all we needed to worry about was send-email, git-svn etc. just
changing it to Perl-native exceptions would be trivial.
From: Jeff King <hidden> Date: 2021-05-13 07:49:12
On Thu, May 13, 2021 at 09:37:36AM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
My only concern is changing the interface of Git::config_regexp() in the
final patch. Do we need to have a config_regexp_with_values() to avoid
breaking third-party users of the module?
As noted in 3/9 I don't think we need to worry about it, it's recently
introduced (a few months) API in Git.pm for send-email itself. I think
we can just change it.
Ah, thanks for pointing that out. I _thought_ I had seen you mention it
earlier, but when I went back to look I couldn't find it.
I'm not entirely convinced, though. I agree it's probably not heavily
used, but the existing interface was shipped in three releases already
(v2.29 and up).
In general I think it's unfortunate that we have (at least in principle)
a "public by default" module like Git.pm that's mostly for our own use.
I'd certainly agree with that sentiment. :)
This series doesn't try to deal with that in general at all, I'm
somewhat of the opinion that we should just fork it at this
point. I.e. have a Git.pm we freeze in time, and a Git/Ours.pm that's
going to be the private API.
I stopped with these optimizations at the point of refactoring away
Error.pm, which is a large contributor to compilation time, but as long
as it's a public API that can't be done without changing the public
API. If all we needed to worry about was send-email, git-svn etc. just
changing it to Perl-native exceptions would be trivial.
Yeah, I don't have any real problem with that, as long as we don't break
third-party scripts that we've promised not to. I'd even be OK with
deprecating Git.pm and eventually phasing it out, if we think it's a
maintenance burden.
-Peff
From: Felipe Contreras <hidden> Date: 2021-05-13 08:15:42
Jeff King wrote:
On Thu, May 13, 2021 at 02:04:08AM -0500, Felipe Contreras wrote:
quoted
Jeff King wrote:
quoted
It is a bit unfortunate to have to go through these contortions, but
this is definitely the best we can do for now. I think in the long run
it would be nice to have a "--stdin" mode for git-config, where we could
do something like:
git config --stdin <<\EOF
key=foo.bar
type=bool
default=false
key=another.key
type=color
default=red
EOF
Why do we even have to specify the type? Shouldn't there be a registry
of configurations (a schema), so that all users don't have to do this?
One of the purposes of git-config is to serve third-party scripts that
store their own config keys that Git does not know about. So we can't
know the set of all possible types that will be asked about.
Yes, I know, I maintain several tools that have such configurations. For
those you would need to specify the type (or find some way to install
the schema so that git parses it).
But I'm talking about git.git configurations. If you don't specify the
type in --stdin it should fetch it from some database. That would be
much more user-friendly.
--
Felipe Contreras
On Thu, May 13, 2021 at 02:04:08AM -0500, Felipe Contreras wrote:
quoted
Jeff King wrote:
quoted
It is a bit unfortunate to have to go through these contortions, but
this is definitely the best we can do for now. I think in the long run
it would be nice to have a "--stdin" mode for git-config, where we could
do something like:
git config --stdin <<\EOF
key=foo.bar
type=bool
default=false
key=another.key
type=color
default=red
EOF
Why do we even have to specify the type? Shouldn't there be a registry
of configurations (a schema), so that all users don't have to do this?
One of the purposes of git-config is to serve third-party scripts that
store their own config keys that Git does not know about. So we can't
know the set of all possible types that will be asked about.
Yes, I know, I maintain several tools that have such configurations. For
those you would need to specify the type (or find some way to install
the schema so that git parses it).
But I'm talking about git.git configurations. If you don't specify the
type in --stdin it should fetch it from some database. That would be
much more user-friendly.
For what it's worth my idea of hacking a plumbing thingy for
git-send-email here before ultimately deciding that my simpler caching
approach was easier and gave me 95% of the win, was to just teach it a
mode where it spews out all config variables \0-delimited with all
possible interpretations of it. I.e.:
some.variable 123 bool true path 123 string 123 [...]
It's rather cheap to do the "interpret this as --type=X for me" on the
C-level, so we might as well spew out all possible interpretations.
That means that any external tool would be guaranteed to only need one
"git config" invocation to parse any of its config, i.e. in a case where
variable X decides if variable Y is a bool or path or whatever. They'd
already have all possible values.
Something like:
git config -l -z --type=bool,path
git config -l -z --type=ALL
Remove the already dead code to support "sendemail.smtpssl" by finally
removing the dead code supporting the configuration option.
In f6bebd121ac (git-send-email: add support for TLS via
Net::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was
documented as deprecated, later in 65180c66186 (List send-email config
options in config.txt., 2009-07-22) the "sendemail.smtpssl"
configuration option was also documented as such.
Then in in 3ff15040e22 (send-email: fix regression in
sendemail.identity parsing, 2019-05-17) I unintentionally removed
support for it by introducing a bug in read_config().
As can be seen from the diff context we've already returned unless
$enc i defined, so it's not possible for us to reach the "elsif"
branch here. This code was therefore already dead since Git v2.23.0.
So let's just remove it. We were already 11 years into a stated
deprecation period of this variable when 3ff15040e22 landed, now it's
around 13. Since it hasn't worked anyway for around 2 years it looks
like we can safely remove it.
The --smtp-ssl option is still deprecated, if someone cares they can
follow-up and remove that too, but unlike the config option that one
could still be in use in the wild. I'm just removing this code that's
provably unused already.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Documentation/config/sendemail.txt | 3 ---
git-send-email.perl | 6 +-----
2 files changed, 1 insertion(+), 8 deletions(-)
@@ -8,9 +8,6 @@ sendemail.smtpEncryption:: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism.-sendemail.smtpssl (deprecated)::- Deprecated alias for 'sendemail.smtpEncryption = ssl'.- sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification.
A re-roll of [1], the work I based v1 on top of has landed in master,
so this has been rebased on master.
The changes here are minor, just a typo fix / commit message
clarification, moving "require" closer to where it's used, and finally
a new 10/10 patch to s/cwd/getcwd/g.
As noted in the commit message I don't think that'll make any
difference in practice. The "time" Eric posted was for loading Cwd.pm
and then doing cwd() or getcwd(), but when we run it we've already
paid the cost of loading Cwd.pm. But it was an easy change to make, so
let's make it anyway.
1. https://lore.kernel.org/git/cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com/
Ævar Arnfjörð Bjarmason (10):
send-email: remove non-working support for "sendemail.smtpssl"
send-email: refactor sendemail.smtpencryption config parsing
send-email: lazily load config for a big speedup
send-email: lazily shell out to "git var"
send-email: use function syntax instead of barewords
send-email: get rid of indirect object syntax
send-email: lazily load modules for a big speedup
perl: lazily load some common Git.pm setup code
send-email: move trivial config handling to Perl
perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()
Documentation/config/sendemail.txt | 3 -
git-send-email.perl | 146 +++++++++++++++++------------
perl/Git.pm | 49 +++++-----
3 files changed, 111 insertions(+), 87 deletions(-)
Range-diff against v1:
1: 92571a8cf7f < -: ----------- Makefile: make PERL_DEFINES recursively expanded
2: 85b706d43fc ! 1: 8474acae689 send-email: remove non-working support for "sendemail.smtpssl"
@@ Metadata
## Commit message ##
send-email: remove non-working support for "sendemail.smtpssl"
- Remove the already dead code to support "sendemail.smtssl" by finally
+ Remove the already dead code to support "sendemail.smtpssl" by finally
removing the dead code supporting the configuration option.
In f6bebd121ac (git-send-email: add support for TLS via
3: c22af817f10 = 2: b87f53adbed send-email: refactor sendemail.smtpencryption config parsing
4: 1e14d322535 = 3: 1b27a393ae3 send-email: lazily load config for a big speedup
5: e1df469d5fe = 4: acee22b77d2 send-email: lazily shell out to "git var"
6: 8846d40fc02 = 5: f317cd1c01e send-email: use function syntax instead of barewords
7: 0dde0e14ef6 = 6: fc27024f838 send-email: get rid of indirect object syntax
8: 55a0b07062f ! 7: f86f5453d7a send-email: lazily load modules for a big speedup
@@ git-send-email.perl: sub do_edit {
# Handle Uncouth Termination
sub signal_handler {
-+ require Term::ANSIColor;
-
+-
# Make text normal
- print color("reset"), "\n";
++ require Term::ANSIColor;
+ print Term::ANSIColor::color("reset"), "\n";
# SMTP password masked
9: 2312346f71e = 8: 86641377c0d perl: lazily load some common Git.pm setup code
10: 0d87c9a5a37 ! 9: 895c9e29a96 send-email: move trivial config handling to Perl
@@ Commit message
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
- values we're interested in.
+ values we're interested in. See the earlier "send-email: lazily load
+ config for a big speedup" commit for why changing its interface is OK.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
@@ Commit message
"git send-email".
This brings the runtime of "git send-email" from ~60-~70ms to a very
- steady ~40ms on my test box. We no run just one "git config"
+ steady ~40ms on my test box. We now run just one "git config"
invocation on startup instead of 8, the exact number will differ based
on the local sendemail.* config. I happen to have 8 of those set.
-: ----------- > 10: 97455f993d5 perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()
--
2.32.0.rc0.405.g5d387561bb3
With the removal of the support for sendemail.smtpssl in the preceding
commit the parsing of sendemail.smtpencryption is no longer special,
and can by moved to %config_settings.
This gets us rid of an unconditional call to Git::config(), which as
we'll see in subsequent commits matters for startup performance.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
@@ -277,6 +277,7 @@ sub do_edit {);my%config_settings=(+"smtpencryption"=>\$smtp_encryption,"smtpserver"=>\$smtp_server,"smtpserverport"=>\$smtp_server_port,"smtpserveroption"=>\@smtp_server_options,
@@ -377,14 +378,6 @@ sub read_config {$$target=$v;}}--if(!defined$smtp_encryption){-my$setting="$prefix.smtpencryption";-my$enc=Git::config(@repo,$setting);-returnunlessdefined$enc;-returnif$configured->{$setting}++;-$smtp_encryption=$enc;-}}# sendemail.identity yields to --identity. We must parse this
Reduce the time it takes git-send-email to get to even the most
trivial of tasks (such as serving up its "-h" output) by first listing
config keys that exist, and only then only call e.g. "git config
--bool" on them if they do.
Over a lot of runs this speeds the time to "-h" up for me from ~250ms
to ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to
~20s.
This introduces a race condition where we'll do the "wrong" thing if a
config key were to be inserted between us discovering the list and
calling read_config(), i.e. we won't know about the racily added
key. In theory this is a change in behavior, in practice it doesn't
matter.
The config_regexp() function being changed here was added in
dd84e528a34 (git-send-email: die if sendmail.* config is set,
2020-07-23) for use by git-send-email. So we can change its odd return
value in the case where no values are found by "git config". The
difference in the *.pm code would matter if it was invoked in scalar
context, but now it no longer is.
Arguably this caching belongs in Git.pm itself, but in lieu of
modifying it for all its callers let's only do this for "git
send-email". The other big potential win would be "git svn", but
unlike "git send-email" it doesn't check tens of config variables one
at a time at startup (in my brief testing it doesn't check any).
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 35 ++++++++++++++++++++++++++---------
perl/Git.pm | 4 ++--
2 files changed, 28 insertions(+), 11 deletions(-)
@@ -349,8 +351,10 @@ sub read_config {foreachmy$setting(keys%config_path_settings){my$target=$config_path_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config_path(@repo,"$prefix.$setting");+my@values=Git::config_path(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;
@@ -365,14 +369,16 @@ sub read_config {foreachmy$setting(keys%config_settings){my$target=$config_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,"$prefix.$setting");+my@values=Git::config(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,"$prefix.$setting");+my$v=Git::config(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -380,9 +386,20 @@ sub read_config {}}+# Save ourselves a lot of work of shelling out to 'git config' (it+# parses 'bool' etc.) by only doing so for config keys that exist.+my%known_config_keys;+{+my@known_config_keys=Git::config_regexp("^sende?mail[.]");+@known_config_keys{@known_config_keys}=();+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.-$identity=Git::config(@repo,"sendemail.identity");+{+my$key="sendemail.identity";+$identity=Git::config(@repo,$key)ifexists$known_config_keys{$key};+}my$rc=GetOptions("identity=s"=>\$identity,"no-identity"=>\$no_identity,
@@ -393,8 +410,8 @@ sub read_config {# Now we know enough to read the config{my%configured;-read_config(\%configured,"sendemail.$identity")ifdefined$identity;-read_config(\%configured,"sendemail");+read_config(\%known_config_keys,\%configured,"sendemail.$identity")ifdefined$identity;+read_config(\%known_config_keys,\%configured,"sendemail");}# Begin by accumulating all the variables (defined above), that we will end up
@@ -478,7 +495,7 @@ sub read_config {usage();}-if($forbid_sendmail_variables&&(scalarGit::config_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&grep{/^sendmail/s}keys%known_config_keys){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
@@ -754,8 +754,8 @@ sub config_regexp {}catchGit::Error::Commandwith{my$E=shift;if($E->value()==1){-my@matches=();-return@matches;+# Key(s) not found.+return;}else{throw$E;}
Optimize git-send-email by only shelling out to "git var" if we need
to. This is easily done by re-inventing our own small version of
perl's Memoize module.
I suppose I could just use Memoize itself, but in a subsequent patch
I'll be micro-optimizing send-email's use of dependencies. Using
Memoize is a measly extra 5-10 milliseconds, but as we'll see that'll
end up mattering for us in the end.
This brings the runtime of a plain "send-email" from around ~160-170ms
to ~140m-150ms. The runtime of the tests is around the same, or around
~20s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
@@ -560,8 +560,18 @@ sub read_config {}my($repoauthor,$repocommitter);-($repoauthor)=Git::ident_person(@repo,'author');-($repocommitter)=Git::ident_person(@repo,'committer');+{+my%cache;+my($author,$committer);+my$common=sub{+my($what)=@_;+return$cache{$what}ifexists$cache{$what};+($cache{$what})=Git::ident_person(@repo,$what);+return$cache{$what};+};+$repoauthor=sub{$common->('author')};+$repocommitter=sub{$common->('committer')};+}subparse_address_line{returnmap{$_->format}Mail::Address->parse($_[0]);
@@ -749,7 +759,7 @@ sub get_patch_subject {ordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);-my$tpl_sender=$sender||$repoauthor||$repocommitter||'';+my$tpl_sender=$sender||$repoauthor->()||$repocommitter->()||'';my$tpl_subject=$initial_subject||'';my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';
@@ -955,7 +965,7 @@ sub file_declares_8bit_cte {$sender=~s/^\s+|\s+$//g;($sender)=expand_aliases($sender);}else{-$sender=$repoauthor||$repocommitter||'';+$sender=$repoauthor->()||$repocommitter->()||'';}# $sender could be an already sanitized address
@@ -1104,7 +1114,7 @@ sub make_message_id {$uniq="$message_id_stamp-$message_id_serial";my$du_part;-for($sender,$repocommitter,$repoauthor){+for($sender,$repocommitter->(),$repoauthor->()){$du_part=extract_valid_address(sanitize_address($_));lastif(defined$du_partand$du_partne'');}
Change calls like "__ 'foo'" to "__('foo')" so the Perl compiler
doesn't have to guess that "__" is a function. This makes the code
more readable.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -678,7 +678,7 @@ sub is_format_patch_arg {if(defined($format_patch)){return$format_patch;}-diesprintf(__<<EOF,$f,$f);+diesprintf(__(<<EOF),$f,$f);File'%s'existsbutitcouldalsobetherangeofcommitstoproducepatchesfor.Pleasedisambiguateby...
@@ -764,7 +764,7 @@ sub get_patch_subject {my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';-print$c<<EOT1,Git::prefix_lines("GIT: ",__<<EOT2),<<EOT3;+print$c<<EOT1,Git::prefix_lines("GIT: ",__(<<EOT2)),<<EOT3;From$tpl_sender# This line is ignored.EOT1Linesbeginningin"GIT:"willberemoved.
Change indirect object syntax such as "new X ARGS" to
"X->new(ARGS)". This allows perl to see what "new" is at compile-time
without having loaded Term::ReadLine. This doesn't matter now, but
will in a subsequent commit when we start lazily loading it.
Let's do the same for the adjacent "FakeTerm" package for consistency,
even though we're not going to conditionally load it.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
Instead of unconditionally requiring modules such as File::Spec, let's
only load them when needed. This speeds up code that only needs a
subset of the features Git.pm provides.
This brings a plain invocation of "git send-email" down from 52/37
loaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes
~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh
test is down to ~13s from ~15s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
perl/Git.pm | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
@@ -191,13 +185,15 @@ sub repository {$dir=undef;};+requireCwd;if($dir){+requireFile::Spec;File::Spec->file_name_is_absolute($dir)or$dir=$opts{Directory}.'/'.$dir;-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);# If --git-dir went ok, this shouldn't die either.my$prefix=$search->command_oneline('rev-parse','--show-prefix');-$dir=abs_path($opts{Directory}).'/';+$dir=Cwd::abs_path($opts{Directory}).'/';if($prefix){if(substr($dir,-length($prefix))ne$prefix){throwError::Simple("rev-parse confused me - $dir does not have trailing $prefix");
@@ -223,7 +219,7 @@ sub repository {throwError::Simple("fatal: Not a git repository: $dir");}-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);}delete$opts{Directory};
@@ -408,10 +404,12 @@ sub command_bidi_pipe {my$cwd_save=undef;if($self){shift;-$cwd_save=cwd();+requireCwd;+$cwd_save=Cwd::cwd();_setup_git_cmd_env($self);}-$pid=open2($in,$out,'git',@_);+requireIPC::Open2;+$pid=IPC::Open2::open2($in,$out,'git',@_);chdir($cwd_save)if$cwd_save;return($pid,$in,$out,join(' ',@_));}
@@ -538,7 +536,8 @@ sub get_tz_offset {my$t=shift||time;my@t=localtime($t);$t[5]+=1900;-my$gm=timegm(@t);+requireTime::Local;+my$gm=Time::Local::timegm(@t);my$sign=qw(++-)[$gm<=>$t];returnsprintf("%s%02d%02d",$sign,(gmtime(abs($t-$gm)))[2,1]);}
@@ -629,7 +628,8 @@ sub hooks_path {my($self)=@_;my$dir=$self->command_oneline('rev-parse','--git-path','hooks');-my$abs=abs_path($dir);+requireCwd;+my$abs=Cwd::abs_path($dir);return$abs;}
@@ -1353,6 +1353,7 @@ sub _temp_cache {my$n=$name;$n=~s/\W/_/g;#nostrangechars+requireFile::Temp;($$temp_fd,$fname)=File::Temp::tempfile("Git_${n}_XXXXXX",UNLINK=>1,DIR=>$tmpdir,)orthrowError::Simple("couldn't open new temp file");
@@ -1375,9 +1376,9 @@ sub temp_reset {truncate$temp_fd,0orthrowError::Simple("couldn't truncate file");-sysseek($temp_fd,0,SEEK_SET)andseek($temp_fd,0,SEEK_SET)+sysseek($temp_fd,0,Fcntl::SEEK_SET())andseek($temp_fd,0,Fcntl::SEEK_SET())orthrowError::Simple("couldn't seek to beginning of file");-sysseek($temp_fd,0,SEEK_CUR)==0andtell($temp_fd)==0+sysseek($temp_fd,0,Fcntl::SEEK_CUR())==0andtell($temp_fd)==0orthrowError::Simple("expected file position to be reset");}
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.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 71 +++++++++++++++++++++++++--------------------
1 file changed, 39 insertions(+), 32 deletions(-)
@@ -702,7 +686,8 @@ sub is_format_patch_arg {opendirmy$dh,$fordiesprintf(__("Failed to opendir %s: %s"),$f,$!);-push@files,grep{-f$_}map{catfile($f,$_)}+requireFile::Spec;+push@files,grep{-f$_}map{File::Spec->catfile($f,$_)}sortreaddir$dh;closedir$dh;}elsif((-f$for-p$f)and!is_format_patch_arg($f)){
@@ -715,7 +700,8 @@ sub is_format_patch_arg {if(@rev_list_opts){die__("Cannot run git format-patch from outside a repository\n")unless$repo;-push@files,$repo->command('format-patch','-o',tempdir(CLEANUP=>1),@rev_list_opts);+requireFile::Temp;+push@files,$repo->command('format-patch','-o',File::Temp::tempdir(CLEANUP=>1),@rev_list_opts);}@files=handle_backup_files(@files);
@@ -752,9 +738,10 @@ sub get_patch_subject {if($compose){# Note that this does not need to be secure, but we will make a small# effort to have it be unique+requireFile::Temp;$compose_filename=($repo?-tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):-tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];openmy$c,">",$compose_filenameordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);
@@ -861,6 +848,19 @@ sub get_patch_subject {do_edit(@files);}+subterm{+my$term=eval{+requireTerm::ReadLine;+$ENV{"GIT_SEND_EMAIL_NOTTY"}+?Term::ReadLine->new('git-send-email',\*STDIN,\*STDOUT)+:Term::ReadLine->new('git-send-email');+};+if($@){+$term=FakeTerm->new("$@: going non-interactive");+}+return$term;+}+subask{my($prompt,%arg)=@_;my$valid_re=$arg{valid_re};
@@ -868,6 +868,7 @@ sub ask {my$confirm_only=$arg{confirm_only};my$resp;my$i=0;+my$term=term();returndefined$default?$default:undefunlessdefined$term->INanddefinedfileno($term->IN)anddefined$term->OUTanddefinedfileno($term->OUT);
@@ -1048,6 +1049,7 @@ sub extract_valid_address {return$addressif($address=~ /^($local_part_regexp)$/);$address=~s/^\s*<(.*)>\s*$/$1/;+my$have_email_valid=eval{requireEmail::Valid;1};if($have_email_valid){returnscalarEmail::Valid->address($address);}
@@ -1107,7 +1109,8 @@ sub validate_address_list {submake_message_id{my$uniq;if(!defined$message_id_stamp){-$message_id_stamp=strftime("%Y%m%d%H%M%S.$$",gmtime(time));+requirePOSIX;+$message_id_stamp=POSIX::strftime("%Y%m%d%H%M%S.$$",gmtime(time));$message_id_serial=0;}$message_id_serial++;
@@ -1277,6 +1280,7 @@ sub valid_fqdn {submaildomain_net{my$maildomain;+requireNet::Domain;my$domain=Net::Domain::domainname();$maildomain=$domainifvalid_fqdn($domain);
@@ -1287,6 +1291,7 @@ sub maildomain_mta {my$maildomain;formy$host(qw(mailhost localhost)){+requireNet::SMTP;my$smtp=Net::SMTP->new($host);if(defined$smtp){my$domain=$smtp->domain;
@@ -1965,13 +1970,15 @@ sub validate_patch {my($fn,$xfer_encoding)=@_;if($repo){-my$validate_hook=catfile($repo->hooks_path(),+requireFile::Spec;+my$validate_hook=File::Spec->catfile($repo->hooks_path(),'sendemail-validate');my$hook_error;if(-x$validate_hook){-my$target=abs_path($fn);+requireCwd;+my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=cwd();+my$cwd_save=Cwd::cwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
It has been pointed out[1] that cwd() invokes "pwd(1)" while getcwd()
is a Perl-native XS function. For what we're using these for we can
use getcwd().
The performance difference is miniscule, we're saving on the order of
a millisecond or so, see [2] below for the benchmark. I don't think
this matters in practice for optimizing git-send-email or perl
execution (unlike the patches leading up to this one).
But let's do it regardless of that, if only so we don't have to think
about this as a low-hanging fruit anymore.
1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/
2.
$ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system "pwd >/dev/null" }})'
(warning: too few iterations for a reliable count)
Rate pwd cwd getcwd
pwd 982/s -- -48% -100%
cwd 1890/s 92% -- -100%
getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064% -
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 2 +-
perl/Git.pm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
@@ -1981,7 +1981,7 @@ sub validate_patch {requireCwd;my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=Cwd::cwd();+my$cwd_save=Cwd::getcwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
values we're interested in. See the earlier "send-email: lazily load
config for a big speedup" commit for why changing its interface is OK.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
other "--bool" cases to "git config". As noted in a preceding commit
we're free to change the config_regexp() function, it's only used by
"git send-email".
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We now run just one "git config"
invocation on startup instead of 8, the exact number will differ based
on the local sendemail.* config. I happen to have 8 of those set.
This brings the runtime of t9001-send-email.sh from ~13s down to ~12s
for me. The change there is less impressive as many of those tests set
various config values, and we're also getting to the point of
diminishing returns for optimizing "git send-email" itself.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 17 ++++++++++-------
perl/Git.pm | 10 +++++-----
2 files changed, 15 insertions(+), 12 deletions(-)
@@ -324,7 +324,10 @@ sub read_config {my$target=$config_bool_settings{$setting};my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};-my$v=Git::config_bool(@repo,$key);+my$v=(@{$known_keys->{$key}}==1&&+$known_keys->{$key}->[0]=~ /^(?:true|false)$/s)+?$known_keys->{$key}->[0]eq'true'+:Git::config_bool(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -353,14 +356,12 @@ sub read_config {my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,$key);-nextunless@values;+my@values=@{$known_keys->{$key}};nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,$key);-nextunlessdefined$v;+my$v=$known_keys->{$key}->[0];nextif$configured->{$setting}++;$$target=$v;}
@@ -371,8 +372,10 @@ sub read_config {# parses 'bool' etc.) by only doing so for config keys that exist.my%known_config_keys;{-my@known_config_keys=Git::config_regexp("^sende?mail[.]");-@known_config_keys{@known_config_keys}=();+my@kv=Git::config_regexp("^sende?mail[.]");+while(my($k,$v)=splice@kv,0,2){+push@{$known_config_keys{$k}}=>$v;+}}# sendemail.identity yields to --identity. We must parse this
From: Jeff King <hidden> Date: 2021-05-20 08:27:43
On Thu, May 20, 2021 at 10:18:57AM +0200, Ævar Arnfjörð Bjarmason wrote:
A re-roll of [1], the work I based v1 on top of has landed in master,
so this has been rebased on master.
The changes here are minor, just a typo fix / commit message
clarification, moving "require" closer to where it's used, and finally
a new 10/10 patch to s/cwd/getcwd/g.
I like all of this, except for the change in the interface of
Git::config_regexp(). You mention that it's new-ish, and probably not in
wide use. And I agree that's probably true. But it feels like violating
a principle of not breaking APIs, and we should stick to that principle
and not bend it for "well, it's not that old an API".
I'd find it more compelling if it the existing interface was broken or
hard to avoid changing. But couldn't we just add a new function with the
extra info (config_regexp_with_values() or something)?
-Peff
Add support for the "GIT_TEST_PERL_FATAL_WARNINGS=true" test mode to
"send-email". This was added to e.g. git-svn in 5338ed2b26 (perl:
check for perl warnings while running tests, 2020-10-21), but not
"send-email". Let's rectify that.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
The Git.pm code does its own Perl-ifying of boolean variables, let's
ensure that empty values = true for boolean variables, as in the C
code.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t9001-send-email.sh | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
Remove the already dead code to support "sendemail.smtpssl" by finally
removing the dead code supporting the configuration option.
In f6bebd121ac (git-send-email: add support for TLS via
Net::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was
documented as deprecated, later in 65180c66186 (List send-email config
options in config.txt., 2009-07-22) the "sendemail.smtpssl"
configuration option was also documented as such.
Then in in 3ff15040e22 (send-email: fix regression in
sendemail.identity parsing, 2019-05-17) I unintentionally removed
support for it by introducing a bug in read_config().
As can be seen from the diff context we've already returned unless
$enc i defined, so it's not possible for us to reach the "elsif"
branch here. This code was therefore already dead since Git v2.23.0.
So let's just remove it. We were already 11 years into a stated
deprecation period of this variable when 3ff15040e22 landed, now it's
around 13. Since it hasn't worked anyway for around 2 years it looks
like we can safely remove it.
The --smtp-ssl option is still deprecated, if someone cares they can
follow-up and remove that too, but unlike the config option that one
could still be in use in the wild. I'm just removing this code that's
provably unused already.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Documentation/config/sendemail.txt | 3 ---
git-send-email.perl | 6 +-----
2 files changed, 1 insertion(+), 8 deletions(-)
@@ -8,9 +8,6 @@ sendemail.smtpEncryption:: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism.-sendemail.smtpssl (deprecated)::- Deprecated alias for 'sendemail.smtpEncryption = ssl'.- sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification.
A re-roll of v2[1] which fixes issues pointed out with v2, plus some
others I noticed along the way.
There are now no longer any changes to the "public"
Git::config_regxp() API. Instead it's left bitrotting in our tree at
the end of this, having 0 in-tree users (instead of the 1 currently).
We also handle the -c foo.bar (NULL value) case correctly, per what
Jeff King pointed out. I also found a related issue with
GIT_TEST_PERL_FATAL_WARNINGS=true, which we can now turn on for
git-send-email.perl.
1. https://lore.kernel.org/git/cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com/
Ævar Arnfjörð Bjarmason (13):
send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true
send-email tests: test for boolean variables without a value
send-email: remove non-working support for "sendemail.smtpssl"
send-email: refactor sendemail.smtpencryption config parsing
send-email: copy "config_regxp" into git-send-email.perl
send-email: lazily load config for a big speedup
send-email: lazily shell out to "git var"
send-email: use function syntax instead of barewords
send-email: get rid of indirect object syntax
send-email: lazily load modules for a big speedup
perl: lazily load some common Git.pm setup code
send-email: move trivial config handling to Perl
perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()
Documentation/config/sendemail.txt | 3 -
git-send-email.perl | 174 +++++++++++++++++++----------
perl/Git.pm | 35 +++---
t/t9001-send-email.sh | 29 +++++
4 files changed, 160 insertions(+), 81 deletions(-)
Range-diff against v2:
-: ---------- > 1: 71f890dc60 send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true
-: ---------- > 2: 707c2ca556 send-email tests: test for boolean variables without a value
1: 8474acae68 = 3: 3bbd48dab2 send-email: remove non-working support for "sendemail.smtpssl"
2: b87f53adbe = 4: bed0f98d68 send-email: refactor sendemail.smtpencryption config parsing
-: ---------- > 5: c12f69a411 send-email: copy "config_regxp" into git-send-email.perl
3: 1b27a393ae ! 6: d1c233d251 send-email: lazily load config for a big speedup
@@ git-send-email.perl: sub read_config {
next unless defined $v;
next if $configured->{$setting}++;
$$target = $v;
-@@ git-send-email.perl: sub read_config {
- }
+@@ git-send-email.perl: sub config_regexp {
+ return @ret;
}
+# Save ourselves a lot of work of shelling out to 'git config' (it
+# parses 'bool' etc.) by only doing so for config keys that exist.
+my %known_config_keys;
+{
-+ my @known_config_keys = Git::config_regexp("^sende?mail[.]");
++ my @known_config_keys = config_regexp("^sende?mail[.]");
+ @known_config_keys{@known_config_keys} = ();
+}
+
@@ git-send-email.perl: sub read_config {
my $rc = GetOptions(
"identity=s" => \$identity,
"no-identity" => \$no_identity,
-@@ git-send-email.perl: sub read_config {
+@@ git-send-email.perl: sub config_regexp {
# Now we know enough to read the config
{
my %configured;
@@ git-send-email.perl: sub read_config {
}
# Begin by accumulating all the variables (defined above), that we will end up
-@@ git-send-email.perl: sub read_config {
+@@ git-send-email.perl: sub config_regexp {
usage();
}
--if ($forbid_sendmail_variables && (scalar Git::config_regexp("^sendmail[.]")) != 0) {
+-if ($forbid_sendmail_variables && (scalar config_regexp("^sendmail[.]")) != 0) {
+if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {
die __("fatal: found configuration options for 'sendmail'\n" .
"git-send-email is configured with the sendemail.* options - note the 'e'.\n" .
"Set sendemail.forbidSendmailVariables to false to disable this check.\n");
-
- ## perl/Git.pm ##
-@@ perl/Git.pm: sub config_regexp {
- } catch Git::Error::Command with {
- my $E = shift;
- if ($E->value() == 1) {
-- my @matches = ();
-- return @matches;
-+ # Key(s) not found.
-+ return;
- } else {
- throw $E;
- }
4: acee22b77d ! 7: 4326c2f99c send-email: lazily shell out to "git var"
@@ Commit message
Signed-off-by: Ævar Arnfjörð Bjarmason [off-list ref]
## git-send-email.perl ##
-@@ git-send-email.perl: sub read_config {
+@@ git-send-email.perl: sub config_regexp {
}
my ($repoauthor, $repocommitter);
5: f317cd1c01 = 8: e1fc71e3f9 send-email: use function syntax instead of barewords
6: fc27024f83 = 9: a806ce06f1 send-email: get rid of indirect object syntax
7: f86f5453d7 ! 10: aa11439789 send-email: lazily load modules for a big speedup
@@ git-send-email.perl
@@
use 5.008;
use strict;
- use warnings;
+ use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();
-use POSIX qw/strftime/;
-use Term::ReadLine;
use Getopt::Long;
@@ git-send-email.perl: sub do_edit {
# SMTP password masked
system "stty echo";
-@@ git-send-email.perl: sub read_config {
+@@ git-send-email.perl: sub config_regexp {
}
sub parse_address_line {
8: 86641377c0 = 11: b3b342b173 perl: lazily load some common Git.pm setup code
9: 895c9e29a9 ! 12: 950dc0f53d send-email: move trivial config handling to Perl
@@ Commit message
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
- values we're interested in. See the earlier "send-email: lazily load
- config for a big speedup" commit for why changing its interface is OK.
+ values we're interested in.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
- other "--bool" cases to "git config". As noted in a preceding commit
- we're free to change the config_regexp() function, it's only used by
- "git send-email".
+ other "--bool" cases to "git config". I'm not bothering with the
+ "undef" or "" case (true and false, respectively), let's just punt on
+ those and others and have "git config --type=bool" handle it.
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We now run just one "git config"
@@ Commit message
Signed-off-by: Ævar Arnfjörð Bjarmason [off-list ref]
+ diff --git a/git-send-email.perl b/git-send-email.perl
+ index 1e9273fd4f..1ea4d9589d 100755
+ --- a/git-send-email.perl
+ +++ b/git-send-email.perl
+ @@ -324,7 +324,11 @@ sub read_config {
+ my $target = $config_bool_settings{$setting};
+ my $key = "$prefix.$setting";
+ next unless exists $known_keys->{$key};
+ - my $v = Git::config_bool(@repo, $key);
+ + my $v = (@{$known_keys->{$key}} == 1 &&
+ + (defined $known_keys->{$key}->[0] &&
+ + $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))
+ + ? $known_keys->{$key}->[0] eq 'true'
+ + : Git::config_bool(@repo, $key);
+ next unless defined $v;
+ next if $configured->{$setting}++;
+ $$target = $v;
+ @@ -353,14 +357,12 @@ sub read_config {
+ my $key = "$prefix.$setting";
+ next unless exists $known_keys->{$key};
+ if (ref($target) eq "ARRAY") {
+ - my @values = Git::config(@repo, $key);
+ - next unless @values;
+ + my @values = @{$known_keys->{$key}};
+ next if $configured->{$setting}++;
+ @$target = @values;
+ }
+ else {
+ - my $v = Git::config(@repo, $key);
+ - next unless defined $v;
+ + my $v = $known_keys->{$key}->[0];
+ next if $configured->{$setting}++;
+ $$target = $v;
+ }
+ @@ -371,12 +373,19 @@ sub config_regexp {
+ my ($regex) = @_;
+ my @ret;
+ eval {
+ - @ret = Git::command(
+ + my $ret = Git::command(
+ 'config',
+ - '--name-only',
+ + '--null',
+ '--get-regexp',
+ $regex,
+ );
+ + @ret = map {
+ + # We must always return ($k, $v) here, since
+ + # empty config values will be just "key\0",
+ + # not "key\nvalue\0".
+ + my ($k, $v) = split /\n/, $_, 2;
+ + ($k, $v);
+ + } split /\0/, $ret;
+ 1;
+ } or do {
+ # If we have no keys we're OK, otherwise re-throw
+ @@ -389,8 +398,10 @@ sub config_regexp {
+ # parses 'bool' etc.) by only doing so for config keys that exist.
+ my %known_config_keys;
+ {
+ - my @known_config_keys = config_regexp("^sende?mail[.]");
+ - @known_config_keys{@known_config_keys} = ();
+ + my @kv = config_regexp("^sende?mail[.]");
+ + while (my ($k, $v) = splice @kv, 0, 2) {
+ + push @{$known_config_keys{$k}} => $v;
+ + }
+ }
+
+ # sendemail.identity yields to --identity. We must parse this
+
## git-send-email.perl ##
@@ git-send-email.perl: sub read_config {
my $target = $config_bool_settings{$setting};
@@ git-send-email.perl: sub read_config {
next unless exists $known_keys->{$key};
- my $v = Git::config_bool(@repo, $key);
+ my $v = (@{$known_keys->{$key}} == 1 &&
-+ $known_keys->{$key}->[0] =~ /^(?:true|false)$/s)
++ (defined $known_keys->{$key}->[0] &&
++ $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))
+ ? $known_keys->{$key}->[0] eq 'true'
+ : Git::config_bool(@repo, $key);
next unless defined $v;
@@ git-send-email.perl: sub read_config {
next if $configured->{$setting}++;
$$target = $v;
}
-@@ git-send-email.perl: sub read_config {
+@@ git-send-email.perl: sub config_regexp {
+ my ($regex) = @_;
+ my @ret;
+ eval {
+- @ret = Git::command(
++ my $ret = Git::command(
+ 'config',
+- '--name-only',
++ '--null',
+ '--get-regexp',
+ $regex,
+ );
++ @ret = map {
++ # We must always return ($k, $v) here, since
++ # empty config values will be just "key\0",
++ # not "key\nvalue\0".
++ my ($k, $v) = split /\n/, $_, 2;
++ ($k, $v);
++ } split /\0/, $ret;
+ 1;
+ } or do {
+ # If we have no keys we're OK, otherwise re-throw
+@@ git-send-email.perl: sub config_regexp {
# parses 'bool' etc.) by only doing so for config keys that exist.
my %known_config_keys;
{
-- my @known_config_keys = Git::config_regexp("^sende?mail[.]");
+- my @known_config_keys = config_regexp("^sende?mail[.]");
- @known_config_keys{@known_config_keys} = ();
-+ my @kv = Git::config_regexp("^sende?mail[.]");
++ my @kv = config_regexp("^sende?mail[.]");
+ while (my ($k, $v) = splice @kv, 0, 2) {
+ push @{$known_config_keys{$k}} => $v;
+ }
}
# sendemail.identity yields to --identity. We must parse this
-
- ## perl/Git.pm ##
-@@ perl/Git.pm: sub config_int {
- =item config_regexp ( RE )
-
- Retrieve the list of configuration key names matching the regular
--expression C<RE>. The return value is a list of strings matching
--this regex.
-+expression C<RE>. The return value is an ARRAY of key-value pairs.
-
- =cut
-
- sub config_regexp {
- my ($self, $regex) = _maybe_self(@_);
- try {
-- my @cmd = ('config', '--name-only', '--get-regexp', $regex);
-+ my @cmd = ('config', '--null', '--get-regexp', $regex);
- unshift @cmd, $self if $self;
-- my @matches = command(@cmd);
-- return @matches;
-+ my $data = command(@cmd);
-+ my (@kv) = map { split /\n/, $_, 2 } split /\0/, $data;
-+ return @kv;
- } catch Git::Error::Command with {
- my $E = shift;
- if ($E->value() == 1) {
10: 97455f993d = 13: c1d7ea664a perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()
--
2.32.0.rc0.406.g05cb3eebfc
With the removal of the support for sendemail.smtpssl in the preceding
commit the parsing of sendemail.smtpencryption is no longer special,
and can by moved to %config_settings.
This gets us rid of an unconditional call to Git::config(), which as
we'll see in subsequent commits matters for startup performance.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
@@ -277,6 +277,7 @@ sub do_edit {);my%config_settings=(+"smtpencryption"=>\$smtp_encryption,"smtpserver"=>\$smtp_server,"smtpserverport"=>\$smtp_server_port,"smtpserveroption"=>\@smtp_server_options,
@@ -377,14 +378,6 @@ sub read_config {$$target=$v;}}--if(!defined$smtp_encryption){-my$setting="$prefix.smtpencryption";-my$enc=Git::config(@repo,$setting);-returnunlessdefined$enc;-returnif$configured->{$setting}++;-$smtp_encryption=$enc;-}}# sendemail.identity yields to --identity. We must parse this
The config_regexp() function was added in dd84e528a3 (git-send-email:
die if sendmail.* config is set, 2020-07-23) for use in
git-send-email, and it's the only in-tree user of it.
However, the consensus is that Git.pm is a public interface, so even
though it's a recently added function we can't change it. So let's
copy over a minimal version of it to git-send-email.perl itself. In a
subsequent commit it'll be changed further for our own use.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
@@ -380,6 +380,24 @@ sub read_config {}}+subconfig_regexp{+my($regex)=@_;+my@ret;+eval{+@ret=Git::command(+'config',+'--name-only',+'--get-regexp',+$regex,+);+1;+}ordo{+# If we have no keys we're OK, otherwise re-throw+die$@if$@->value!=1;+};+return@ret;+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.$identity=Git::config(@repo,"sendemail.identity");
@@ -478,7 +496,7 @@ sub read_config {usage();}-if($forbid_sendmail_variables&&(scalarGit::config_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&(scalarconfig_regexp("^sendmail[.]"))!=0){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
Reduce the time it takes git-send-email to get to even the most
trivial of tasks (such as serving up its "-h" output) by first listing
config keys that exist, and only then only call e.g. "git config
--bool" on them if they do.
Over a lot of runs this speeds the time to "-h" up for me from ~250ms
to ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to
~20s.
This introduces a race condition where we'll do the "wrong" thing if a
config key were to be inserted between us discovering the list and
calling read_config(), i.e. we won't know about the racily added
key. In theory this is a change in behavior, in practice it doesn't
matter.
The config_regexp() function being changed here was added in
dd84e528a34 (git-send-email: die if sendmail.* config is set,
2020-07-23) for use by git-send-email. So we can change its odd return
value in the case where no values are found by "git config". The
difference in the *.pm code would matter if it was invoked in scalar
context, but now it no longer is.
Arguably this caching belongs in Git.pm itself, but in lieu of
modifying it for all its callers let's only do this for "git
send-email". The other big potential win would be "git svn", but
unlike "git send-email" it doesn't check tens of config variables one
at a time at startup (in my brief testing it doesn't check any).
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 35 ++++++++++++++++++++++++++---------
1 file changed, 26 insertions(+), 9 deletions(-)
@@ -349,8 +351,10 @@ sub read_config {foreachmy$setting(keys%config_path_settings){my$target=$config_path_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config_path(@repo,"$prefix.$setting");+my@values=Git::config_path(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;
@@ -365,14 +369,16 @@ sub read_config {foreachmy$setting(keys%config_settings){my$target=$config_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,"$prefix.$setting");+my@values=Git::config(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,"$prefix.$setting");+my$v=Git::config(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -398,9 +404,20 @@ sub config_regexp {return@ret;}+# Save ourselves a lot of work of shelling out to 'git config' (it+# parses 'bool' etc.) by only doing so for config keys that exist.+my%known_config_keys;+{+my@known_config_keys=config_regexp("^sende?mail[.]");+@known_config_keys{@known_config_keys}=();+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.-$identity=Git::config(@repo,"sendemail.identity");+{+my$key="sendemail.identity";+$identity=Git::config(@repo,$key)ifexists$known_config_keys{$key};+}my$rc=GetOptions("identity=s"=>\$identity,"no-identity"=>\$no_identity,
@@ -411,8 +428,8 @@ sub config_regexp {# Now we know enough to read the config{my%configured;-read_config(\%configured,"sendemail.$identity")ifdefined$identity;-read_config(\%configured,"sendemail");+read_config(\%known_config_keys,\%configured,"sendemail.$identity")ifdefined$identity;+read_config(\%known_config_keys,\%configured,"sendemail");}# Begin by accumulating all the variables (defined above), that we will end up
@@ -496,7 +513,7 @@ sub config_regexp {usage();}-if($forbid_sendmail_variables&&(scalarconfig_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&grep{/^sendmail/s}keys%known_config_keys){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
Optimize git-send-email by only shelling out to "git var" if we need
to. This is easily done by re-inventing our own small version of
perl's Memoize module.
I suppose I could just use Memoize itself, but in a subsequent patch
I'll be micro-optimizing send-email's use of dependencies. Using
Memoize is a measly extra 5-10 milliseconds, but as we'll see that'll
end up mattering for us in the end.
This brings the runtime of a plain "send-email" from around ~160-170ms
to ~140m-150ms. The runtime of the tests is around the same, or around
~20s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
@@ -578,8 +578,18 @@ sub config_regexp {}my($repoauthor,$repocommitter);-($repoauthor)=Git::ident_person(@repo,'author');-($repocommitter)=Git::ident_person(@repo,'committer');+{+my%cache;+my($author,$committer);+my$common=sub{+my($what)=@_;+return$cache{$what}ifexists$cache{$what};+($cache{$what})=Git::ident_person(@repo,$what);+return$cache{$what};+};+$repoauthor=sub{$common->('author')};+$repocommitter=sub{$common->('committer')};+}subparse_address_line{returnmap{$_->format}Mail::Address->parse($_[0]);
@@ -767,7 +777,7 @@ sub get_patch_subject {ordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);-my$tpl_sender=$sender||$repoauthor||$repocommitter||'';+my$tpl_sender=$sender||$repoauthor->()||$repocommitter->()||'';my$tpl_subject=$initial_subject||'';my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';
@@ -973,7 +983,7 @@ sub file_declares_8bit_cte {$sender=~s/^\s+|\s+$//g;($sender)=expand_aliases($sender);}else{-$sender=$repoauthor||$repocommitter||'';+$sender=$repoauthor->()||$repocommitter->()||'';}# $sender could be an already sanitized address
@@ -1122,7 +1132,7 @@ sub make_message_id {$uniq="$message_id_stamp-$message_id_serial";my$du_part;-for($sender,$repocommitter,$repoauthor){+for($sender,$repocommitter->(),$repoauthor->()){$du_part=extract_valid_address(sanitize_address($_));lastif(defined$du_partand$du_partne'');}
Change indirect object syntax such as "new X ARGS" to
"X->new(ARGS)". This allows perl to see what "new" is at compile-time
without having loaded Term::ReadLine. This doesn't matter now, but
will in a subsequent commit when we start lazily loading it.
Let's do the same for the adjacent "FakeTerm" package for consistency,
even though we're not going to conditionally load it.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
Change calls like "__ 'foo'" to "__('foo')" so the Perl compiler
doesn't have to guess that "__" is a function. This makes the code
more readable.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -696,7 +696,7 @@ sub is_format_patch_arg {if(defined($format_patch)){return$format_patch;}-diesprintf(__<<EOF,$f,$f);+diesprintf(__(<<EOF),$f,$f);File'%s'existsbutitcouldalsobetherangeofcommitstoproducepatchesfor.Pleasedisambiguateby...
@@ -782,7 +782,7 @@ sub get_patch_subject {my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';-print$c<<EOT1,Git::prefix_lines("GIT: ",__<<EOT2),<<EOT3;+print$c<<EOT1,Git::prefix_lines("GIT: ",__(<<EOT2)),<<EOT3;From$tpl_sender# This line is ignored.EOT1Linesbeginningin"GIT:"willberemoved.
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.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 71 +++++++++++++++++++++++++--------------------
1 file changed, 39 insertions(+), 32 deletions(-)
@@ -720,7 +704,8 @@ sub is_format_patch_arg {opendirmy$dh,$fordiesprintf(__("Failed to opendir %s: %s"),$f,$!);-push@files,grep{-f$_}map{catfile($f,$_)}+requireFile::Spec;+push@files,grep{-f$_}map{File::Spec->catfile($f,$_)}sortreaddir$dh;closedir$dh;}elsif((-f$for-p$f)and!is_format_patch_arg($f)){
@@ -733,7 +718,8 @@ sub is_format_patch_arg {if(@rev_list_opts){die__("Cannot run git format-patch from outside a repository\n")unless$repo;-push@files,$repo->command('format-patch','-o',tempdir(CLEANUP=>1),@rev_list_opts);+requireFile::Temp;+push@files,$repo->command('format-patch','-o',File::Temp::tempdir(CLEANUP=>1),@rev_list_opts);}@files=handle_backup_files(@files);
@@ -770,9 +756,10 @@ sub get_patch_subject {if($compose){# Note that this does not need to be secure, but we will make a small# effort to have it be unique+requireFile::Temp;$compose_filename=($repo?-tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):-tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];openmy$c,">",$compose_filenameordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);
@@ -879,6 +866,19 @@ sub get_patch_subject {do_edit(@files);}+subterm{+my$term=eval{+requireTerm::ReadLine;+$ENV{"GIT_SEND_EMAIL_NOTTY"}+?Term::ReadLine->new('git-send-email',\*STDIN,\*STDOUT)+:Term::ReadLine->new('git-send-email');+};+if($@){+$term=FakeTerm->new("$@: going non-interactive");+}+return$term;+}+subask{my($prompt,%arg)=@_;my$valid_re=$arg{valid_re};
@@ -886,6 +886,7 @@ sub ask {my$confirm_only=$arg{confirm_only};my$resp;my$i=0;+my$term=term();returndefined$default?$default:undefunlessdefined$term->INanddefinedfileno($term->IN)anddefined$term->OUTanddefinedfileno($term->OUT);
@@ -1066,6 +1067,7 @@ sub extract_valid_address {return$addressif($address=~ /^($local_part_regexp)$/);$address=~s/^\s*<(.*)>\s*$/$1/;+my$have_email_valid=eval{requireEmail::Valid;1};if($have_email_valid){returnscalarEmail::Valid->address($address);}
@@ -1125,7 +1127,8 @@ sub validate_address_list {submake_message_id{my$uniq;if(!defined$message_id_stamp){-$message_id_stamp=strftime("%Y%m%d%H%M%S.$$",gmtime(time));+requirePOSIX;+$message_id_stamp=POSIX::strftime("%Y%m%d%H%M%S.$$",gmtime(time));$message_id_serial=0;}$message_id_serial++;
@@ -1295,6 +1298,7 @@ sub valid_fqdn {submaildomain_net{my$maildomain;+requireNet::Domain;my$domain=Net::Domain::domainname();$maildomain=$domainifvalid_fqdn($domain);
@@ -1305,6 +1309,7 @@ sub maildomain_mta {my$maildomain;formy$host(qw(mailhost localhost)){+requireNet::SMTP;my$smtp=Net::SMTP->new($host);if(defined$smtp){my$domain=$smtp->domain;
@@ -1983,13 +1988,15 @@ sub validate_patch {my($fn,$xfer_encoding)=@_;if($repo){-my$validate_hook=catfile($repo->hooks_path(),+requireFile::Spec;+my$validate_hook=File::Spec->catfile($repo->hooks_path(),'sendemail-validate');my$hook_error;if(-x$validate_hook){-my$target=abs_path($fn);+requireCwd;+my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=cwd();+my$cwd_save=Cwd::cwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
Instead of unconditionally requiring modules such as File::Spec, let's
only load them when needed. This speeds up code that only needs a
subset of the features Git.pm provides.
This brings a plain invocation of "git send-email" down from 52/37
loaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes
~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh
test is down to ~13s from ~15s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
perl/Git.pm | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
@@ -191,13 +185,15 @@ sub repository {$dir=undef;};+requireCwd;if($dir){+requireFile::Spec;File::Spec->file_name_is_absolute($dir)or$dir=$opts{Directory}.'/'.$dir;-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);# If --git-dir went ok, this shouldn't die either.my$prefix=$search->command_oneline('rev-parse','--show-prefix');-$dir=abs_path($opts{Directory}).'/';+$dir=Cwd::abs_path($opts{Directory}).'/';if($prefix){if(substr($dir,-length($prefix))ne$prefix){throwError::Simple("rev-parse confused me - $dir does not have trailing $prefix");
@@ -223,7 +219,7 @@ sub repository {throwError::Simple("fatal: Not a git repository: $dir");}-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);}delete$opts{Directory};
@@ -408,10 +404,12 @@ sub command_bidi_pipe {my$cwd_save=undef;if($self){shift;-$cwd_save=cwd();+requireCwd;+$cwd_save=Cwd::cwd();_setup_git_cmd_env($self);}-$pid=open2($in,$out,'git',@_);+requireIPC::Open2;+$pid=IPC::Open2::open2($in,$out,'git',@_);chdir($cwd_save)if$cwd_save;return($pid,$in,$out,join(' ',@_));}
@@ -538,7 +536,8 @@ sub get_tz_offset {my$t=shift||time;my@t=localtime($t);$t[5]+=1900;-my$gm=timegm(@t);+requireTime::Local;+my$gm=Time::Local::timegm(@t);my$sign=qw(++-)[$gm<=>$t];returnsprintf("%s%02d%02d",$sign,(gmtime(abs($t-$gm)))[2,1]);}
@@ -629,7 +628,8 @@ sub hooks_path {my($self)=@_;my$dir=$self->command_oneline('rev-parse','--git-path','hooks');-my$abs=abs_path($dir);+requireCwd;+my$abs=Cwd::abs_path($dir);return$abs;}
@@ -1353,6 +1353,7 @@ sub _temp_cache {my$n=$name;$n=~s/\W/_/g;#nostrangechars+requireFile::Temp;($$temp_fd,$fname)=File::Temp::tempfile("Git_${n}_XXXXXX",UNLINK=>1,DIR=>$tmpdir,)orthrowError::Simple("couldn't open new temp file");
@@ -1375,9 +1376,9 @@ sub temp_reset {truncate$temp_fd,0orthrowError::Simple("couldn't truncate file");-sysseek($temp_fd,0,SEEK_SET)andseek($temp_fd,0,SEEK_SET)+sysseek($temp_fd,0,Fcntl::SEEK_SET())andseek($temp_fd,0,Fcntl::SEEK_SET())orthrowError::Simple("couldn't seek to beginning of file");-sysseek($temp_fd,0,SEEK_CUR)==0andtell($temp_fd)==0+sysseek($temp_fd,0,Fcntl::SEEK_CUR())==0andtell($temp_fd)==0orthrowError::Simple("expected file position to be reset");}
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
values we're interested in.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
other "--bool" cases to "git config". I'm not bothering with the
"undef" or "" case (true and false, respectively), let's just punt on
those and others and have "git config --type=bool" handle it.
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We now run just one "git config"
invocation on startup instead of 8, the exact number will differ based
on the local sendemail.* config. I happen to have 8 of those set.
This brings the runtime of t9001-send-email.sh from ~13s down to ~12s
for me. The change there is less impressive as many of those tests set
various config values, and we're also getting to the point of
diminishing returns for optimizing "git send-email" itself.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
@@ -324,7 +324,11 @@ sub read_config {my$target=$config_bool_settings{$setting};my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};-my$v=Git::config_bool(@repo,$key);+my$v=(@{$known_keys->{$key}}==1&&+(defined$known_keys->{$key}->[0]&&+$known_keys->{$key}->[0]=~ /^(?:true|false)$/s))+?$known_keys->{$key}->[0]eq'true'+:Git::config_bool(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -353,14 +357,12 @@ sub read_config {my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,$key);-nextunless@values;+my@values=@{$known_keys->{$key}};nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,$key);-nextunlessdefined$v;+my$v=$known_keys->{$key}->[0];nextif$configured->{$setting}++;$$target=$v;}
@@ -371,12 +373,19 @@ sub config_regexp {my($regex)=@_;my@ret;eval{-@ret=Git::command(+my$ret=Git::command('config',-'--name-only',+'--null','--get-regexp',$regex,);+@ret=map{+# We must always return ($k, $v) here, since+# empty config values will be just "key\0",+# not "key\nvalue\0".+my($k,$v)=split/\n/,$_,2;+($k,$v);+}split/\0/,$ret;1;}ordo{# If we have no keys we're OK, otherwise re-throw
@@ -389,8 +398,10 @@ sub config_regexp {# parses 'bool' etc.) by only doing so for config keys that exist.my%known_config_keys;{-my@known_config_keys=config_regexp("^sende?mail[.]");-@known_config_keys{@known_config_keys}=();+my@kv=config_regexp("^sende?mail[.]");+while(my($k,$v)=splice@kv,0,2){+push@{$known_config_keys{$k}}=>$v;+}}# sendemail.identity yields to --identity. We must parse this---git-send-email.perl|29++++++++++++++++++++---------1filechanged,20insertions(+),9deletions(-)
@@ -324,7 +324,11 @@ sub read_config {my$target=$config_bool_settings{$setting};my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};-my$v=Git::config_bool(@repo,$key);+my$v=(@{$known_keys->{$key}}==1&&+(defined$known_keys->{$key}->[0]&&+$known_keys->{$key}->[0]=~ /^(?:true|false)$/s))+?$known_keys->{$key}->[0]eq'true'+:Git::config_bool(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -353,14 +357,12 @@ sub read_config {my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,$key);-nextunless@values;+my@values=@{$known_keys->{$key}};nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,$key);-nextunlessdefined$v;+my$v=$known_keys->{$key}->[0];nextif$configured->{$setting}++;$$target=$v;}
@@ -371,12 +373,19 @@ sub config_regexp {my($regex)=@_;my@ret;eval{-@ret=Git::command(+my$ret=Git::command('config',-'--name-only',+'--null','--get-regexp',$regex,);+@ret=map{+# We must always return ($k, $v) here, since+# empty config values will be just "key\0",+# not "key\nvalue\0".+my($k,$v)=split/\n/,$_,2;+($k,$v);+}split/\0/,$ret;1;}ordo{# If we have no keys we're OK, otherwise re-throw
@@ -389,8 +398,10 @@ sub config_regexp {# parses 'bool' etc.) by only doing so for config keys that exist.my%known_config_keys;{-my@known_config_keys=config_regexp("^sende?mail[.]");-@known_config_keys{@known_config_keys}=();+my@kv=config_regexp("^sende?mail[.]");+while(my($k,$v)=splice@kv,0,2){+push@{$known_config_keys{$k}}=>$v;+}}# sendemail.identity yields to --identity. We must parse this
It has been pointed out[1] that cwd() invokes "pwd(1)" while getcwd()
is a Perl-native XS function. For what we're using these for we can
use getcwd().
The performance difference is miniscule, we're saving on the order of
a millisecond or so, see [2] below for the benchmark. I don't think
this matters in practice for optimizing git-send-email or perl
execution (unlike the patches leading up to this one).
But let's do it regardless of that, if only so we don't have to think
about this as a low-hanging fruit anymore.
1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/
2.
$ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system "pwd >/dev/null" }})'
(warning: too few iterations for a reliable count)
Rate pwd cwd getcwd
pwd 982/s -- -48% -100%
cwd 1890/s 92% -- -100%
getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064% -
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 2 +-
perl/Git.pm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
@@ -2007,7 +2007,7 @@ sub validate_patch {requireCwd;my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=Cwd::cwd();+my$cwd_save=Cwd::getcwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
Add support for the "GIT_TEST_PERL_FATAL_WARNINGS=true" test mode to
"send-email". This was added to e.g. git-svn in 5338ed2b26 (perl:
check for perl warnings while running tests, 2020-10-21), but not
"send-email". Let's rectify that.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
The Git.pm code does its own Perl-ifying of boolean variables, let's
ensure that empty values = true for boolean variables, as in the C
code.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t9001-send-email.sh | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
Remove the already dead code to support "sendemail.smtpssl" by finally
removing the dead code supporting the configuration option.
In f6bebd121ac (git-send-email: add support for TLS via
Net::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was
documented as deprecated, later in 65180c66186 (List send-email config
options in config.txt., 2009-07-22) the "sendemail.smtpssl"
configuration option was also documented as such.
Then in in 3ff15040e22 (send-email: fix regression in
sendemail.identity parsing, 2019-05-17) I unintentionally removed
support for it by introducing a bug in read_config().
As can be seen from the diff context we've already returned unless
$enc i defined, so it's not possible for us to reach the "elsif"
branch here. This code was therefore already dead since Git v2.23.0.
So let's just remove it. We were already 11 years into a stated
deprecation period of this variable when 3ff15040e22 landed, now it's
around 13. Since it hasn't worked anyway for around 2 years it looks
like we can safely remove it.
The --smtp-ssl option is still deprecated, if someone cares they can
follow-up and remove that too, but unlike the config option that one
could still be in use in the wild. I'm just removing this code that's
provably unused already.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Documentation/config/sendemail.txt | 3 ---
git-send-email.perl | 6 +-----
2 files changed, 1 insertion(+), 8 deletions(-)
@@ -8,9 +8,6 @@ sendemail.smtpEncryption:: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism.-sendemail.smtpssl (deprecated)::- Deprecated alias for 'sendemail.smtpEncryption = ssl'.- sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification.
With the removal of the support for sendemail.smtpssl in the preceding
commit the parsing of sendemail.smtpencryption is no longer special,
and can by moved to %config_settings.
This gets us rid of an unconditional call to Git::config(), which as
we'll see in subsequent commits matters for startup performance.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
@@ -277,6 +277,7 @@ sub do_edit {);my%config_settings=(+"smtpencryption"=>\$smtp_encryption,"smtpserver"=>\$smtp_server,"smtpserverport"=>\$smtp_server_port,"smtpserveroption"=>\@smtp_server_options,
@@ -377,14 +378,6 @@ sub read_config {$$target=$v;}}--if(!defined$smtp_encryption){-my$setting="$prefix.smtpencryption";-my$enc=Git::config(@repo,$setting);-returnunlessdefined$enc;-returnif$configured->{$setting}++;-$smtp_encryption=$enc;-}}# sendemail.identity yields to --identity. We must parse this
Optimize git-send-email by only shelling out to "git var" if we need
to. This is easily done by re-inventing our own small version of
perl's Memoize module.
I suppose I could just use Memoize itself, but in a subsequent patch
I'll be micro-optimizing send-email's use of dependencies. Using
Memoize is a measly extra 5-10 milliseconds, but as we'll see that'll
end up mattering for us in the end.
This brings the runtime of a plain "send-email" from around ~160-170ms
to ~140m-150ms. The runtime of the tests is around the same, or around
~20s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
@@ -578,8 +578,18 @@ sub config_regexp {}my($repoauthor,$repocommitter);-($repoauthor)=Git::ident_person(@repo,'author');-($repocommitter)=Git::ident_person(@repo,'committer');+{+my%cache;+my($author,$committer);+my$common=sub{+my($what)=@_;+return$cache{$what}ifexists$cache{$what};+($cache{$what})=Git::ident_person(@repo,$what);+return$cache{$what};+};+$repoauthor=sub{$common->('author')};+$repocommitter=sub{$common->('committer')};+}subparse_address_line{returnmap{$_->format}Mail::Address->parse($_[0]);
@@ -767,7 +777,7 @@ sub get_patch_subject {ordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);-my$tpl_sender=$sender||$repoauthor||$repocommitter||'';+my$tpl_sender=$sender||$repoauthor->()||$repocommitter->()||'';my$tpl_subject=$initial_subject||'';my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';
@@ -973,7 +983,7 @@ sub file_declares_8bit_cte {$sender=~s/^\s+|\s+$//g;($sender)=expand_aliases($sender);}else{-$sender=$repoauthor||$repocommitter||'';+$sender=$repoauthor->()||$repocommitter->()||'';}# $sender could be an already sanitized address
@@ -1122,7 +1132,7 @@ sub make_message_id {$uniq="$message_id_stamp-$message_id_serial";my$du_part;-for($sender,$repocommitter,$repoauthor){+for($sender,$repocommitter->(),$repoauthor->()){$du_part=extract_valid_address(sanitize_address($_));lastif(defined$du_partand$du_partne'');}
Reduce the time it takes git-send-email to get to even the most
trivial of tasks (such as serving up its "-h" output) by first listing
config keys that exist, and only then only call e.g. "git config
--bool" on them if they do.
Over a lot of runs this speeds the time to "-h" up for me from ~250ms
to ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to
~20s.
This introduces a race condition where we'll do the "wrong" thing if a
config key were to be inserted between us discovering the list and
calling read_config(), i.e. we won't know about the racily added
key. In theory this is a change in behavior, in practice it doesn't
matter.
The config_regexp() function being changed here was added in
dd84e528a34 (git-send-email: die if sendmail.* config is set,
2020-07-23) for use by git-send-email. So we can change its odd return
value in the case where no values are found by "git config". The
difference in the *.pm code would matter if it was invoked in scalar
context, but now it no longer is.
Arguably this caching belongs in Git.pm itself, but in lieu of
modifying it for all its callers let's only do this for "git
send-email". The other big potential win would be "git svn", but
unlike "git send-email" it doesn't check tens of config variables one
at a time at startup (in my brief testing it doesn't check any).
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 35 ++++++++++++++++++++++++++---------
1 file changed, 26 insertions(+), 9 deletions(-)
@@ -349,8 +351,10 @@ sub read_config {foreachmy$setting(keys%config_path_settings){my$target=$config_path_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config_path(@repo,"$prefix.$setting");+my@values=Git::config_path(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;
@@ -365,14 +369,16 @@ sub read_config {foreachmy$setting(keys%config_settings){my$target=$config_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,"$prefix.$setting");+my@values=Git::config(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,"$prefix.$setting");+my$v=Git::config(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -398,9 +404,20 @@ sub config_regexp {return@ret;}+# Save ourselves a lot of work of shelling out to 'git config' (it+# parses 'bool' etc.) by only doing so for config keys that exist.+my%known_config_keys;+{+my@known_config_keys=config_regexp("^sende?mail[.]");+@known_config_keys{@known_config_keys}=();+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.-$identity=Git::config(@repo,"sendemail.identity");+{+my$key="sendemail.identity";+$identity=Git::config(@repo,$key)ifexists$known_config_keys{$key};+}my$rc=GetOptions("identity=s"=>\$identity,"no-identity"=>\$no_identity,
@@ -411,8 +428,8 @@ sub config_regexp {# Now we know enough to read the config{my%configured;-read_config(\%configured,"sendemail.$identity")ifdefined$identity;-read_config(\%configured,"sendemail");+read_config(\%known_config_keys,\%configured,"sendemail.$identity")ifdefined$identity;+read_config(\%known_config_keys,\%configured,"sendemail");}# Begin by accumulating all the variables (defined above), that we will end up
@@ -496,7 +513,7 @@ sub config_regexp {usage();}-if($forbid_sendmail_variables&&(scalarconfig_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&grep{/^sendmail/s}keys%known_config_keys){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
The config_regexp() function was added in dd84e528a3 (git-send-email:
die if sendmail.* config is set, 2020-07-23) for use in
git-send-email, and it's the only in-tree user of it.
However, the consensus is that Git.pm is a public interface, so even
though it's a recently added function we can't change it. So let's
copy over a minimal version of it to git-send-email.perl itself. In a
subsequent commit it'll be changed further for our own use.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
@@ -380,6 +380,24 @@ sub read_config {}}+subconfig_regexp{+my($regex)=@_;+my@ret;+eval{+@ret=Git::command(+'config',+'--name-only',+'--get-regexp',+$regex,+);+1;+}ordo{+# If we have no keys we're OK, otherwise re-throw+die$@if$@->value!=1;+};+return@ret;+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.$identity=Git::config(@repo,"sendemail.identity");
@@ -478,7 +496,7 @@ sub read_config {usage();}-if($forbid_sendmail_variables&&(scalarGit::config_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&(scalarconfig_regexp("^sendmail[.]"))!=0){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
Change indirect object syntax such as "new X ARGS" to
"X->new(ARGS)". This allows perl to see what "new" is at compile-time
without having loaded Term::ReadLine. This doesn't matter now, but
will in a subsequent commit when we start lazily loading it.
Let's do the same for the adjacent "FakeTerm" package for consistency,
even though we're not going to conditionally load it.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
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.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 71 +++++++++++++++++++++++++--------------------
1 file changed, 39 insertions(+), 32 deletions(-)
@@ -720,7 +704,8 @@ sub is_format_patch_arg {opendirmy$dh,$fordiesprintf(__("Failed to opendir %s: %s"),$f,$!);-push@files,grep{-f$_}map{catfile($f,$_)}+requireFile::Spec;+push@files,grep{-f$_}map{File::Spec->catfile($f,$_)}sortreaddir$dh;closedir$dh;}elsif((-f$for-p$f)and!is_format_patch_arg($f)){
@@ -733,7 +718,8 @@ sub is_format_patch_arg {if(@rev_list_opts){die__("Cannot run git format-patch from outside a repository\n")unless$repo;-push@files,$repo->command('format-patch','-o',tempdir(CLEANUP=>1),@rev_list_opts);+requireFile::Temp;+push@files,$repo->command('format-patch','-o',File::Temp::tempdir(CLEANUP=>1),@rev_list_opts);}@files=handle_backup_files(@files);
@@ -770,9 +756,10 @@ sub get_patch_subject {if($compose){# Note that this does not need to be secure, but we will make a small# effort to have it be unique+requireFile::Temp;$compose_filename=($repo?-tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):-tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>$repo->repo_path()):+File::Temp::tempfile(".gitsendemail.msg.XXXXXX",DIR=>"."))[1];openmy$c,">",$compose_filenameordiesprintf(__("Failed to open for writing %s: %s"),$compose_filename,$!);
@@ -879,6 +866,19 @@ sub get_patch_subject {do_edit(@files);}+subterm{+my$term=eval{+requireTerm::ReadLine;+$ENV{"GIT_SEND_EMAIL_NOTTY"}+?Term::ReadLine->new('git-send-email',\*STDIN,\*STDOUT)+:Term::ReadLine->new('git-send-email');+};+if($@){+$term=FakeTerm->new("$@: going non-interactive");+}+return$term;+}+subask{my($prompt,%arg)=@_;my$valid_re=$arg{valid_re};
@@ -886,6 +886,7 @@ sub ask {my$confirm_only=$arg{confirm_only};my$resp;my$i=0;+my$term=term();returndefined$default?$default:undefunlessdefined$term->INanddefinedfileno($term->IN)anddefined$term->OUTanddefinedfileno($term->OUT);
@@ -1066,6 +1067,7 @@ sub extract_valid_address {return$addressif($address=~ /^($local_part_regexp)$/);$address=~s/^\s*<(.*)>\s*$/$1/;+my$have_email_valid=eval{requireEmail::Valid;1};if($have_email_valid){returnscalarEmail::Valid->address($address);}
@@ -1125,7 +1127,8 @@ sub validate_address_list {submake_message_id{my$uniq;if(!defined$message_id_stamp){-$message_id_stamp=strftime("%Y%m%d%H%M%S.$$",gmtime(time));+requirePOSIX;+$message_id_stamp=POSIX::strftime("%Y%m%d%H%M%S.$$",gmtime(time));$message_id_serial=0;}$message_id_serial++;
@@ -1295,6 +1298,7 @@ sub valid_fqdn {submaildomain_net{my$maildomain;+requireNet::Domain;my$domain=Net::Domain::domainname();$maildomain=$domainifvalid_fqdn($domain);
@@ -1305,6 +1309,7 @@ sub maildomain_mta {my$maildomain;formy$host(qw(mailhost localhost)){+requireNet::SMTP;my$smtp=Net::SMTP->new($host);if(defined$smtp){my$domain=$smtp->domain;
@@ -1983,13 +1988,15 @@ sub validate_patch {my($fn,$xfer_encoding)=@_;if($repo){-my$validate_hook=catfile($repo->hooks_path(),+requireFile::Spec;+my$validate_hook=File::Spec->catfile($repo->hooks_path(),'sendemail-validate');my$hook_error;if(-x$validate_hook){-my$target=abs_path($fn);+requireCwd;+my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=cwd();+my$cwd_save=Cwd::cwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
Instead of unconditionally requiring modules such as File::Spec, let's
only load them when needed. This speeds up code that only needs a
subset of the features Git.pm provides.
This brings a plain invocation of "git send-email" down from 52/37
loaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes
~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh
test is down to ~13s from ~15s.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
perl/Git.pm | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
@@ -191,13 +185,15 @@ sub repository {$dir=undef;};+requireCwd;if($dir){+requireFile::Spec;File::Spec->file_name_is_absolute($dir)or$dir=$opts{Directory}.'/'.$dir;-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);# If --git-dir went ok, this shouldn't die either.my$prefix=$search->command_oneline('rev-parse','--show-prefix');-$dir=abs_path($opts{Directory}).'/';+$dir=Cwd::abs_path($opts{Directory}).'/';if($prefix){if(substr($dir,-length($prefix))ne$prefix){throwError::Simple("rev-parse confused me - $dir does not have trailing $prefix");
@@ -223,7 +219,7 @@ sub repository {throwError::Simple("fatal: Not a git repository: $dir");}-$opts{Repository}=abs_path($dir);+$opts{Repository}=Cwd::abs_path($dir);}delete$opts{Directory};
@@ -408,10 +404,12 @@ sub command_bidi_pipe {my$cwd_save=undef;if($self){shift;-$cwd_save=cwd();+requireCwd;+$cwd_save=Cwd::cwd();_setup_git_cmd_env($self);}-$pid=open2($in,$out,'git',@_);+requireIPC::Open2;+$pid=IPC::Open2::open2($in,$out,'git',@_);chdir($cwd_save)if$cwd_save;return($pid,$in,$out,join(' ',@_));}
@@ -538,7 +536,8 @@ sub get_tz_offset {my$t=shift||time;my@t=localtime($t);$t[5]+=1900;-my$gm=timegm(@t);+requireTime::Local;+my$gm=Time::Local::timegm(@t);my$sign=qw(++-)[$gm<=>$t];returnsprintf("%s%02d%02d",$sign,(gmtime(abs($t-$gm)))[2,1]);}
@@ -629,7 +628,8 @@ sub hooks_path {my($self)=@_;my$dir=$self->command_oneline('rev-parse','--git-path','hooks');-my$abs=abs_path($dir);+requireCwd;+my$abs=Cwd::abs_path($dir);return$abs;}
@@ -1353,6 +1353,7 @@ sub _temp_cache {my$n=$name;$n=~s/\W/_/g;#nostrangechars+requireFile::Temp;($$temp_fd,$fname)=File::Temp::tempfile("Git_${n}_XXXXXX",UNLINK=>1,DIR=>$tmpdir,)orthrowError::Simple("couldn't open new temp file");
@@ -1375,9 +1376,9 @@ sub temp_reset {truncate$temp_fd,0orthrowError::Simple("couldn't truncate file");-sysseek($temp_fd,0,SEEK_SET)andseek($temp_fd,0,SEEK_SET)+sysseek($temp_fd,0,Fcntl::SEEK_SET())andseek($temp_fd,0,Fcntl::SEEK_SET())orthrowError::Simple("couldn't seek to beginning of file");-sysseek($temp_fd,0,SEEK_CUR)==0andtell($temp_fd)==0+sysseek($temp_fd,0,Fcntl::SEEK_CUR())==0andtell($temp_fd)==0orthrowError::Simple("expected file position to be reset");}
Optimize the startup time of git-send-email by using an amended
config_regexp() function to retrieve the list of config keys and
values we're interested in.
For boolean keys we can handle the [true|false] case ourselves, and
the "--get" case didn't need any parsing. Let's leave "--path" and
other "--bool" cases to "git config". I'm not bothering with the
"undef" or "" case (true and false, respectively), let's just punt on
those and others and have "git config --type=bool" handle it.
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We now run just one "git config"
invocation on startup instead of 8, the exact number will differ based
on the local sendemail.* config. I happen to have 8 of those set.
This brings the runtime of t9001-send-email.sh from ~13s down to ~12s
for me. The change there is less impressive as many of those tests set
various config values, and we're also getting to the point of
diminishing returns for optimizing "git send-email" itself.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
@@ -324,7 +324,11 @@ sub read_config {my$target=$config_bool_settings{$setting};my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};-my$v=Git::config_bool(@repo,$key);+my$v=(@{$known_keys->{$key}}==1&&+(defined$known_keys->{$key}->[0]&&+$known_keys->{$key}->[0]=~ /^(?:true|false)$/s))+?$known_keys->{$key}->[0]eq'true'+:Git::config_bool(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -353,14 +357,12 @@ sub read_config {my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,$key);-nextunless@values;+my@values=@{$known_keys->{$key}};nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,$key);-nextunlessdefined$v;+my$v=$known_keys->{$key}->[0];nextif$configured->{$setting}++;$$target=$v;}
@@ -371,12 +373,19 @@ sub config_regexp {my($regex)=@_;my@ret;eval{-@ret=Git::command(+my$ret=Git::command('config',-'--name-only',+'--null','--get-regexp',$regex,);+@ret=map{+# We must always return ($k, $v) here, since+# empty config values will be just "key\0",+# not "key\nvalue\0".+my($k,$v)=split/\n/,$_,2;+($k,$v);+}split/\0/,$ret;1;}ordo{# If we have no keys we're OK, otherwise re-throw
@@ -389,8 +398,10 @@ sub config_regexp {# parses 'bool' etc.) by only doing so for config keys that exist.my%known_config_keys;{-my@known_config_keys=config_regexp("^sende?mail[.]");-@known_config_keys{@known_config_keys}=();+my@kv=config_regexp("^sende?mail[.]");+while(my($k,$v)=splice@kv,0,2){+push@{$known_config_keys{$k}}=>$v;+}}# sendemail.identity yields to --identity. We must parse this
Change calls like "__ 'foo'" to "__('foo')" so the Perl compiler
doesn't have to guess that "__" is a function. This makes the code
more readable.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
@@ -696,7 +696,7 @@ sub is_format_patch_arg {if(defined($format_patch)){return$format_patch;}-diesprintf(__<<EOF,$f,$f);+diesprintf(__(<<EOF),$f,$f);File'%s'existsbutitcouldalsobetherangeofcommitstoproducepatchesfor.Pleasedisambiguateby...
@@ -782,7 +782,7 @@ sub get_patch_subject {my$tpl_in_reply_to=$initial_in_reply_to||'';my$tpl_reply_to=$reply_to||'';-print$c<<EOT1,Git::prefix_lines("GIT: ",__<<EOT2),<<EOT3;+print$c<<EOT1,Git::prefix_lines("GIT: ",__(<<EOT2)),<<EOT3;From$tpl_sender# This line is ignored.EOT1Linesbeginningin"GIT:"willberemoved.
It has been pointed out[1] that cwd() invokes "pwd(1)" while getcwd()
is a Perl-native XS function. For what we're using these for we can
use getcwd().
The performance difference is miniscule, we're saving on the order of
a millisecond or so, see [2] below for the benchmark. I don't think
this matters in practice for optimizing git-send-email or perl
execution (unlike the patches leading up to this one).
But let's do it regardless of that, if only so we don't have to think
about this as a low-hanging fruit anymore.
1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/
2.
$ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system "pwd >/dev/null" }})'
(warning: too few iterations for a reliable count)
Rate pwd cwd getcwd
pwd 982/s -- -48% -100%
cwd 1890/s 92% -- -100%
getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064% -
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 2 +-
perl/Git.pm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
@@ -2007,7 +2007,7 @@ sub validate_patch {requireCwd;my$target=Cwd::abs_path($fn);# The hook needs a correct cwd and GIT_DIR.-my$cwd_save=Cwd::cwd();+my$cwd_save=Cwd::getcwd();chdir($repo->wc_path()or$repo->repo_path())ordie("chdir: $!");local$ENV{"GIT_DIR"}=$repo->repo_path();
Hi,
On Wed, May 12, 2021 at 6:50 AM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
As noted in the subject this speeds up git-send-email invocations by
~2x or more, and brings the very slow t9001 test from running in ~26s
on my box to ~12s. It's no longer consistently the slowest test I run.
This is basically done in two ways: We lazily invoke "git config" to
get config, before it's very eager, and deferring Perl compilation
with s/use/require/g.
I know I'm very late to the party, but I just wanted to comment that
this is super cool. Thanks for speeding this up; some really good
finds here.
@@ -324,7 +324,11 @@ sub read_config {my$target=$config_bool_settings{$setting};my$key="$prefix.$setting";nextunlessexists$known_keys->{$key};-my$v=Git::config_bool(@repo,$key);+my$v=(@{$known_keys->{$key}}==1&&+(defined$known_keys->{$key}->[0]&&+$known_keys->{$key}->[0]=~ /^(?:true|false)$/s))+?$known_keys->{$key}->[0]eq'true'+:Git::config_bool(@repo,$key);
Thanks for addressing this. It looks like an undefined value will kick
back to the Git::config_bool() case. That's probably fine, as I'd think
it's relatively rare (and this is all just optimization anyway).
quoted hunk
@@ -353,14 +357,12 @@ sub read_config { my $key = "$prefix.$setting"; next unless exists $known_keys->{$key}; if (ref($target) eq "ARRAY") {- my @values = Git::config(@repo, $key);- next unless @values;+ my @values = @{$known_keys->{$key}}; next if $configured->{$setting}++; @$target = @values; }
I do wonder what happens for non-bool values here. I.e., would we return
a list that contains undef? Before your change, the value comes from
Git::config(), and now it comes from our pre-read $known_keys.
It seems fine either way:
$ git -c sendemail.smtpsslcertpath send-email -1
error: missing value for 'sendemail.smtpsslcertpath'
fatal: unable to parse command-line config
config --path --get sendemail.smtpsslcertpath: command returned error: 128
but I didn't carefully follow all the paths that config can take. So I
raise it only as a potential issue. Your response is hopefully "yes, I
thought of that, and it is fine." :)
-Peff
From: Jeff King <hidden> Date: 2021-05-27 16:00:32
On Mon, May 24, 2021 at 09:52:49AM +0200, Ævar Arnfjörð Bjarmason wrote:
This v4 fixes an issue in v3 where 12/13 had a "diff --git" as part of
the commit message (mistake during rebase/squash), which confused "git
am" in trying to apply a diff twice. See <xmqqwnrplyns.fsf@gitster.g>.
I raised a probably-not-a-problem question in patch 12. Assuming it's
indeed not-a-problem, this all looks good to me.
Thanks for addressing my concerns about Git.pm stability.
-Peff
PS I hit some mild conflicts applying this on top of master because
7cbc0455cc (send-email: move "hooks_path" invocation to
git-send-email.perl, 2021-05-26) graduated in the meantime. I expect
Junio prefers it as you have it here, since he tends to use the
original base from previous rounds, but just a hint to any other
reviewers.
Hopefully the final iteration. Updates a commit message to explain why
I moved away from File::Spec::Functions, rebases on master, and
explains and deals with the "undef in config" issue Jeff King noted.
Ævar Arnfjörð Bjarmason (13):
send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true
send-email tests: test for boolean variables without a value
send-email: remove non-working support for "sendemail.smtpssl"
send-email: refactor sendemail.smtpencryption config parsing
send-email: copy "config_regxp" into git-send-email.perl
send-email: lazily load config for a big speedup
send-email: lazily shell out to "git var"
send-email: use function syntax instead of barewords
send-email: get rid of indirect object syntax
send-email: lazily load modules for a big speedup
perl: lazily load some common Git.pm setup code
send-email: move trivial config handling to Perl
perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()
Documentation/config/sendemail.txt | 3 -
git-send-email.perl | 174 +++++++++++++++++++----------
perl/Git.pm | 32 +++---
t/t9001-send-email.sh | 29 +++++
4 files changed, 159 insertions(+), 79 deletions(-)
Range-diff against v4:
1: 7140847367c = 1: 81025b48f1c send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true
2: d27f3b48f85 = 2: 16277bd1082 send-email tests: test for boolean variables without a value
3: a7a21b75f2e = 3: e3e3e6415d2 send-email: remove non-working support for "sendemail.smtpssl"
4: 7356a528589 = 4: 961ca4c2b2a send-email: refactor sendemail.smtpencryption config parsing
5: cce0f89143b = 5: f2bd12728a1 send-email: copy "config_regxp" into git-send-email.perl
6: 8afe8661761 = 6: 4cf70c6f97e send-email: lazily load config for a big speedup
7: 491eefde6a2 = 7: bd0d9535718 send-email: lazily shell out to "git var"
8: 860156013f8 = 8: f1a879a8ae9 send-email: use function syntax instead of barewords
9: dd24f1249f5 = 9: 881b1093409 send-email: get rid of indirect object syntax
10: 61e3e3c93c5 ! 10: 9f21bc6e6f2 send-email: lazily load modules for a big speedup
@@ Commit message
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 [off-list ref]
## git-send-email.perl ##
@@ git-send-email.perl: sub maildomain_mta {
if (defined $smtp) {
my $domain = $smtp->domain;
@@ git-send-email.perl: sub validate_patch {
- my ($fn, $xfer_encoding) = @_;
if ($repo) {
-- my $validate_hook = catfile($repo->hooks_path(),
+ my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
+- my $validate_hook = catfile($hooks_path,
+ require File::Spec;
-+ my $validate_hook = File::Spec->catfile($repo->hooks_path(),
++ my $validate_hook = File::Spec->catfile($hooks_path,
'sendemail-validate');
my $hook_error;
if (-x $validate_hook) {
11: ada34374286 ! 11: 66f68e38c16 perl: lazily load some common Git.pm setup code
@@ perl/Git.pm: sub get_tz_offset {
my $sign = qw( + + - )[ $gm <=> $t ];
return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
}
-@@ perl/Git.pm: sub hooks_path {
- my ($self) = @_;
-
- my $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks');
-- my $abs = abs_path($dir);
-+ require Cwd;
-+ my $abs = Cwd::abs_path($dir);
- return $abs;
- }
-
@@ perl/Git.pm: sub _temp_cache {
my $n = $name;
$n =~ s/\W/_/g; # no strange chars
12: 3818000bfba ! 12: f605b5ae49f send-email: move trivial config handling to Perl
@@ Commit message
"undef" or "" case (true and false, respectively), let's just punt on
those and others and have "git config --type=bool" handle it.
+ The "grep { defined } @values" here covers a rather subtle case. For
+ list values such as sendemail.to it is possible as with any other
+ config key to provide a plain "-c sendemail.to", i.e. to set the key
+ as a boolean true. In that case the Git::config() API will return an
+ empty string, but this new parser will correctly return "undef".
+
+ However, that means we can end up with "undef" in the middle of a
+ list. E.g. for sendemail.smtpserveroption in conjuction with
+ sendemail.smtpserver as a path this would have produce a warning. For
+ most of the other keys we'd behave the same despite the subtle change
+ in the value, e.g. sendemail.to would behave the same because
+ Mail::Address->parse() happens to return an empty list if fed
+ "undef". For the boolean values we were already prepared to handle
+ these variables being initialized as undef anyway.
+
This brings the runtime of "git send-email" from ~60-~70ms to a very
steady ~40ms on my test box. We now run just one "git config"
invocation on startup instead of 8, the exact number will differ based
@@ git-send-email.perl: sub read_config {
- my @values = Git::config(@repo, $key);
- next unless @values;
+ my @values = @{$known_keys->{$key}};
++ @values = grep { defined } @values;
next if $configured->{$setting}++;
@$target = @values;
}
else {
- my $v = Git::config(@repo, $key);
-- next unless defined $v;
+ my $v = $known_keys->{$key}->[0];
+ next unless defined $v;
next if $configured->{$setting}++;
$$target = $v;
- }
@@ git-send-email.perl: sub config_regexp {
my ($regex) = @_;
my @ret;
13: d36b57e429f = 13: aa3a2de7047 perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()
--
2.32.0.rc1.458.gd885d4f985c
Add support for the "GIT_TEST_PERL_FATAL_WARNINGS=true" test mode to
"send-email". This was added to e.g. git-svn in 5338ed2b26 (perl:
check for perl warnings while running tests, 2020-10-21), but not
"send-email". Let's rectify that.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Remove the already dead code to support "sendemail.smtpssl" by finally
removing the dead code supporting the configuration option.
In f6bebd121ac (git-send-email: add support for TLS via
Net::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was
documented as deprecated, later in 65180c66186 (List send-email config
options in config.txt., 2009-07-22) the "sendemail.smtpssl"
configuration option was also documented as such.
Then in in 3ff15040e22 (send-email: fix regression in
sendemail.identity parsing, 2019-05-17) I unintentionally removed
support for it by introducing a bug in read_config().
As can be seen from the diff context we've already returned unless
$enc i defined, so it's not possible for us to reach the "elsif"
branch here. This code was therefore already dead since Git v2.23.0.
So let's just remove it. We were already 11 years into a stated
deprecation period of this variable when 3ff15040e22 landed, now it's
around 13. Since it hasn't worked anyway for around 2 years it looks
like we can safely remove it.
The --smtp-ssl option is still deprecated, if someone cares they can
follow-up and remove that too, but unlike the config option that one
could still be in use in the wild. I'm just removing this code that's
provably unused already.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Documentation/config/sendemail.txt | 3 ---
git-send-email.perl | 6 +-----
2 files changed, 1 insertion(+), 8 deletions(-)
@@ -8,9 +8,6 @@ sendemail.smtpEncryption:: See linkgit:git-send-email[1] for description. Note that this setting is not subject to the 'identity' mechanism.-sendemail.smtpssl (deprecated)::- Deprecated alias for 'sendemail.smtpEncryption = ssl'.- sendemail.smtpsslcertpath:: Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification.
The Git.pm code does its own Perl-ifying of boolean variables, let's
ensure that empty values = true for boolean variables, as in the C
code.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
t/t9001-send-email.sh | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
With the removal of the support for sendemail.smtpssl in the preceding
commit the parsing of sendemail.smtpencryption is no longer special,
and can by moved to %config_settings.
This gets us rid of an unconditional call to Git::config(), which as
we'll see in subsequent commits matters for startup performance.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
@@ -287,6 +287,7 @@ sub do_edit {);my%config_settings=(+"smtpencryption"=>\$smtp_encryption,"smtpserver"=>\$smtp_server,"smtpserverport"=>\$smtp_server_port,"smtpserveroption"=>\@smtp_server_options,
@@ -387,14 +388,6 @@ sub read_config {$$target=$v;}}--if(!defined$smtp_encryption){-my$setting="$prefix.smtpencryption";-my$enc=Git::config(@repo,$setting);-returnunlessdefined$enc;-returnif$configured->{$setting}++;-$smtp_encryption=$enc;-}}# sendemail.identity yields to --identity. We must parse this
Reduce the time it takes git-send-email to get to even the most
trivial of tasks (such as serving up its "-h" output) by first listing
config keys that exist, and only then only call e.g. "git config
--bool" on them if they do.
Over a lot of runs this speeds the time to "-h" up for me from ~250ms
to ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to
~20s.
This introduces a race condition where we'll do the "wrong" thing if a
config key were to be inserted between us discovering the list and
calling read_config(), i.e. we won't know about the racily added
key. In theory this is a change in behavior, in practice it doesn't
matter.
The config_regexp() function being changed here was added in
dd84e528a34 (git-send-email: die if sendmail.* config is set,
2020-07-23) for use by git-send-email. So we can change its odd return
value in the case where no values are found by "git config". The
difference in the *.pm code would matter if it was invoked in scalar
context, but now it no longer is.
Arguably this caching belongs in Git.pm itself, but in lieu of
modifying it for all its callers let's only do this for "git
send-email". The other big potential win would be "git svn", but
unlike "git send-email" it doesn't check tens of config variables one
at a time at startup (in my brief testing it doesn't check any).
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 35 ++++++++++++++++++++++++++---------
1 file changed, 26 insertions(+), 9 deletions(-)
@@ -359,8 +361,10 @@ sub read_config {foreachmy$setting(keys%config_path_settings){my$target=$config_path_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config_path(@repo,"$prefix.$setting");+my@values=Git::config_path(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;
@@ -375,14 +379,16 @@ sub read_config {foreachmy$setting(keys%config_settings){my$target=$config_settings{$setting};+my$key="$prefix.$setting";+nextunlessexists$known_keys->{$key};if(ref($target)eq"ARRAY"){-my@values=Git::config(@repo,"$prefix.$setting");+my@values=Git::config(@repo,$key);nextunless@values;nextif$configured->{$setting}++;@$target=@values;}else{-my$v=Git::config(@repo,"$prefix.$setting");+my$v=Git::config(@repo,$key);nextunlessdefined$v;nextif$configured->{$setting}++;$$target=$v;
@@ -408,9 +414,20 @@ sub config_regexp {return@ret;}+# Save ourselves a lot of work of shelling out to 'git config' (it+# parses 'bool' etc.) by only doing so for config keys that exist.+my%known_config_keys;+{+my@known_config_keys=config_regexp("^sende?mail[.]");+@known_config_keys{@known_config_keys}=();+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.-$identity=Git::config(@repo,"sendemail.identity");+{+my$key="sendemail.identity";+$identity=Git::config(@repo,$key)ifexists$known_config_keys{$key};+}my$rc=GetOptions("identity=s"=>\$identity,"no-identity"=>\$no_identity,
@@ -421,8 +438,8 @@ sub config_regexp {# Now we know enough to read the config{my%configured;-read_config(\%configured,"sendemail.$identity")ifdefined$identity;-read_config(\%configured,"sendemail");+read_config(\%known_config_keys,\%configured,"sendemail.$identity")ifdefined$identity;+read_config(\%known_config_keys,\%configured,"sendemail");}# Begin by accumulating all the variables (defined above), that we will end up
@@ -506,7 +523,7 @@ sub config_regexp {usage();}-if($forbid_sendmail_variables&&(scalarconfig_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&grep{/^sendmail/s}keys%known_config_keys){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");
The config_regexp() function was added in dd84e528a3 (git-send-email:
die if sendmail.* config is set, 2020-07-23) for use in
git-send-email, and it's the only in-tree user of it.
However, the consensus is that Git.pm is a public interface, so even
though it's a recently added function we can't change it. So let's
copy over a minimal version of it to git-send-email.perl itself. In a
subsequent commit it'll be changed further for our own use.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
git-send-email.perl | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
@@ -390,6 +390,24 @@ sub read_config {}}+subconfig_regexp{+my($regex)=@_;+my@ret;+eval{+@ret=Git::command(+'config',+'--name-only',+'--get-regexp',+$regex,+);+1;+}ordo{+# If we have no keys we're OK, otherwise re-throw+die$@if$@->value!=1;+};+return@ret;+}+# sendemail.identity yields to --identity. We must parse this# special-case first before the rest of the config is read.$identity=Git::config(@repo,"sendemail.identity");
@@ -488,7 +506,7 @@ sub read_config {usage();}-if($forbid_sendmail_variables&&(scalarGit::config_regexp("^sendmail[.]"))!=0){+if($forbid_sendmail_variables&&(scalarconfig_regexp("^sendmail[.]"))!=0){die__("fatal: found configuration options for 'sendmail'\n"."git-send-email is configured with the sendemail.* options - note the 'e'.\n"."Set sendemail.forbidSendmailVariables to false to disable this check.\n");