send-email: lazily load config for a big speedup

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 <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed May 28, 2021 at 11:23 UTC 9264d29bf6da9e1789d71807dd72bbd4fb447887
1 file changed +26 -9
git-send-email.perl
+26 -9
@@ -347,11 +347,13 @@ $SIG{INT} = \&signal_handler;
347
348 # Read our sendemail.* config
349 sub read_config {
350 - my ($configured, $prefix) = @_;
350 + my ($known_keys, $configured, $prefix) = @_;
351
352 foreach my $setting (keys %config_bool_settings) {
353 my $target = $config_bool_settings{$setting};
354 - my $v = Git::config_bool(@repo, "$prefix.$setting");
354 + my $key = "$prefix.$setting";
355 + next unless exists $known_keys->{$key};
356 + my $v = Git::config_bool(@repo, $key);
357 next unless defined $v;
358 next if $configured->{$setting}++;
359 $$target = $v;
@@ -359,8 +361,10 @@ sub read_config {
361
362 foreach my $setting (keys %config_path_settings) {
363 my $target = $config_path_settings{$setting};
364 + my $key = "$prefix.$setting";
365 + next unless exists $known_keys->{$key};
366 if (ref($target) eq "ARRAY") {
363 - my @values = Git::config_path(@repo, "$prefix.$setting");
367 + my @values = Git::config_path(@repo, $key);
368 next unless @values;
369 next if $configured->{$setting}++;
370 @$target = @values;
@@ -375,14 +379,16 @@ sub read_config {
379
380 foreach my $setting (keys %config_settings) {
381 my $target = $config_settings{$setting};
382 + my $key = "$prefix.$setting";
383 + next unless exists $known_keys->{$key};
384 if (ref($target) eq "ARRAY") {
379 - my @values = Git::config(@repo, "$prefix.$setting");
385 + my @values = Git::config(@repo, $key);
386 next unless @values;
387 next if $configured->{$setting}++;
388 @$target = @values;
389 }
390 else {
385 - my $v = Git::config(@repo, "$prefix.$setting");
391 + my $v = Git::config(@repo, $key);
392 next unless defined $v;
393 next if $configured->{$setting}++;
394 $$target = $v;
@@ -408,9 +414,20 @@ sub config_regexp {
414 return @ret;
415 }
416
417 +# Save ourselves a lot of work of shelling out to 'git config' (it
418 +# parses 'bool' etc.) by only doing so for config keys that exist.
419 +my %known_config_keys;
420 +{
421 + my @known_config_keys = config_regexp("^sende?mail[.]");
422 + @known_config_keys{@known_config_keys} = ();
423 +}
424 +
425 # sendemail.identity yields to --identity. We must parse this
426 # special-case first before the rest of the config is read.
413 -$identity = Git::config(@repo, "sendemail.identity");
427 +{
428 + my $key = "sendemail.identity";
429 + $identity = Git::config(@repo, $key) if exists $known_config_keys{$key};
430 +}
431 my $rc = GetOptions(
432 "identity=s" => \$identity,
433 "no-identity" => \$no_identity,
@@ -421,8 +438,8 @@ undef $identity if $no_identity;
438 # Now we know enough to read the config
439 {
440 my %configured;
424 - read_config(\%configured, "sendemail.$identity") if defined $identity;
425 - read_config(\%configured, "sendemail");
441 + read_config(\%known_config_keys, \%configured, "sendemail.$identity") if defined $identity;
442 + read_config(\%known_config_keys, \%configured, "sendemail");
443 }
444
445 # Begin by accumulating all the variables (defined above), that we will end up
@@ -506,7 +523,7 @@ unless ($rc) {
523 usage();
524 }
525
509 -if ($forbid_sendmail_variables && (scalar config_regexp("^sendmail[.]")) != 0) {
526 +if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {
527 die __("fatal: found configuration options for 'sendmail'\n" .
528 "git-send-email is configured with the sendemail.* options - note the 'e'.\n" .
529 "Set sendemail.forbidSendmailVariables to false to disable this check.\n");