unofficial mirror of meta@public-inbox.org
 help / color / mirror / Atom feed
* [PATCH 0/6] lei config fixes and improvements
@ 2023-09-24  5:42 Eric Wong
  2023-09-24  5:42 ` [PATCH 1/6] lei: check git-config(1) failures Eric Wong
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

Fixing `-c NAME=VALUE' was something I noticed working on
improving *BSD support the other week.  Everything else
here was noticed while fixing -c.  And 6/6 is because I'm
annoyed at seeing test-only code in Config.pm

Eric Wong (6):
  lei: check git-config(1) failures
  lei view_text: used tied ProcessPipe for `git config'
  config: handle key-only entries as booleans
  lei config: send `git config' errors to pager
  lei: fix `-c NAME=VALUE' config support
  config: drop scalar ref support from internal API

 lib/PublicInbox/Config.pm            |  78 ++++++++++------
 lib/PublicInbox/LEI.pm               | 102 +++++++++++----------
 lib/PublicInbox/LeiAddWatch.pm       |   7 +-
 lib/PublicInbox/LeiConfig.pm         |  35 +++++---
 lib/PublicInbox/LeiForgetExternal.pm |   3 +-
 lib/PublicInbox/LeiInit.pm           |   4 +-
 lib/PublicInbox/LeiMirror.pm         |   5 +-
 lib/PublicInbox/LeiRmWatch.pm        |   2 +-
 lib/PublicInbox/LeiViewText.pm       |   9 +-
 lib/PublicInbox/TestCommon.pm        |  20 +++--
 t/config.t                           | 128 +++++++++++++++------------
 t/config_limiter.t                   |  31 +++----
 t/inbox_idle.t                       |  15 ++--
 t/lei.t                              |  17 +++-
 t/psgi_bad_mids.t                    |  18 ++--
 t/psgi_mount.t                       |  14 ++-
 t/psgi_multipart_not.t               |  16 ++--
 t/psgi_scan_all.t                    |  18 ++--
 t/psgi_search.t                      |  12 ++-
 t/psgi_text.t                        |  21 ++---
 t/watch_filter_rubylang.t            |  30 +++----
 t/watch_imap.t                       |  20 +++--
 t/watch_maildir.t                    |  24 ++---
 t/watch_maildir_v2.t                 |  44 ++++-----
 t/watch_multiple_headers.t           |  21 +++--
 25 files changed, 381 insertions(+), 313 deletions(-)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/6] lei: check git-config(1) failures
  2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
@ 2023-09-24  5:42 ` Eric Wong
  2023-09-24  5:42 ` [PATCH 2/6] lei view_text: used tied ProcessPipe for `git config' Eric Wong
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

2020-2021 were bad times and I somehow got deluded into
believing git-config(1) would always succeed :x
---
 lib/PublicInbox/LEI.pm               | 9 ++++++---
 lib/PublicInbox/LeiAddWatch.pm       | 7 ++++---
 lib/PublicInbox/LeiForgetExternal.pm | 3 +--
 lib/PublicInbox/LeiInit.pm           | 4 ++--
 lib/PublicInbox/LeiRmWatch.pm        | 2 +-
 5 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index 368f9357..a6d92eec 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -776,9 +776,8 @@ EOM
 		/\A([^=\.]+\.[^=]+)(?:=(.*))?\z/ or return fail($self, <<EOM);
 `-c $_' is not of the form -c <name>=<value>'
 EOM
-		my $name = $1;
-		my $value = $2 // 1;
-		_config($self, '--add', $name, $value);
+		my ($name, $value) = ($1, $2 // 1);
+		_config($self, '--add', $name, $value) or return;
 		if (defined(my $v = $tmp->{$name})) {
 			if (ref($v) eq 'ARRAY') {
 				push @$v, $value;
@@ -894,13 +893,17 @@ sub _lei_store ($;$) {
 	};
 }
 
+# returns true on success, undef
+# argv[0] eq `+e' means errors do not ->fail # (like `sh +e')
 sub _config {
 	my ($self, @argv) = @_;
+	my $err_ok = ($argv[0] // '') eq '+e' ? shift(@argv) : undef;
 	my %env = (%{$self->{env}}, GIT_CONFIG => undef);
 	my $cfg = _lei_cfg($self, 1);
 	my $cmd = [ qw(git config -f), $cfg->{'-f'}, @argv ];
 	my %rdr = map { $_ => $self->{$_} } (0..2);
 	waitpid(spawn($cmd, \%env, \%rdr), 0);
+	$? == 0 ? 1 : ($err_ok ? undef : fail($self, $?));
 }
 
 sub lei_daemon_pid { puts shift, $$ }
diff --git a/lib/PublicInbox/LeiAddWatch.pm b/lib/PublicInbox/LeiAddWatch.pm
index 97e7a342..f61e2de4 100644
--- a/lib/PublicInbox/LeiAddWatch.pm
+++ b/lib/PublicInbox/LeiAddWatch.pm
@@ -26,13 +26,14 @@ sub lei_add_watch {
 	for my $w (@{$self->{inputs}}) {
 		# clobber existing, allow multiple
 		if (defined($vmd0)) {
-			$lei->_config("watch.$w.vmd", '--replace-all', $vmd0);
+			$lei->_config("watch.$w.vmd", '--replace-all', $vmd0)
+				or return;
 			for my $v (@vmd) {
-				$lei->_config("watch.$w.vmd", $v);
+				$lei->_config("watch.$w.vmd", $v) or return;
 			}
 		}
 		next if defined $cfg->{"watch.$w.state"};
-		$lei->_config("watch.$w.state", $state);
+		$lei->_config("watch.$w.state", $state) or return;
 	}
 	$lei->_lei_store(1); # create
 	$lei->lms(1)->lms_write_prepare->add_folders(@{$self->{inputs}});
diff --git a/lib/PublicInbox/LeiForgetExternal.pm b/lib/PublicInbox/LeiForgetExternal.pm
index 39bfc60b..c8d1df38 100644
--- a/lib/PublicInbox/LeiForgetExternal.pm
+++ b/lib/PublicInbox/LeiForgetExternal.pm
@@ -16,8 +16,7 @@ sub lei_forget_external {
 			next if $seen{$l}++;
 			my $key = "external.$l.boost";
 			delete($cfg->{$key});
-			$lei->_config('--unset', $key);
-			if ($? == 0) {
+			if ($lei->_config('+e', '--unset', $key)) {
 				$lei->qerr("# $l forgotten ");
 			} elsif (($? >> 8) == 5) {
 				warn("# $l not found\n");
diff --git a/lib/PublicInbox/LeiInit.pm b/lib/PublicInbox/LeiInit.pm
index 27ce8169..94897e61 100644
--- a/lib/PublicInbox/LeiInit.pm
+++ b/lib/PublicInbox/LeiInit.pm
@@ -23,7 +23,7 @@ sub lei_init {
 
 		# some folks like symlinks and bind mounts :P
 		if (@dir && "@cur[1,0]" eq "@dir[1,0]") {
-			$self->_config('leistore.dir', $dir);
+			$self->_config('leistore.dir', $dir) or return;
 			$self->_lei_store(1)->done;
 			return $self->qerr("$exists (as $cur)");
 		}
@@ -31,7 +31,7 @@ sub lei_init {
 E: leistore.dir=$cur already initialized and it is not $dir
 
 	}
-	$self->_config('leistore.dir', $dir);
+	$self->_config('leistore.dir', $dir) or return;
 	$self->_lei_store(1)->done;
 	$exists //= "# leistore.dir=$dir newly initialized";
 	$self->qerr($exists);
diff --git a/lib/PublicInbox/LeiRmWatch.pm b/lib/PublicInbox/LeiRmWatch.pm
index c0f336f0..19bee3ab 100644
--- a/lib/PublicInbox/LeiRmWatch.pm
+++ b/lib/PublicInbox/LeiRmWatch.pm
@@ -14,7 +14,7 @@ sub lei_rm_watch {
 	my $self = bless { missing_ok => 1 }, __PACKAGE__;
 	$self->prepare_inputs($lei, \@argv) or return;
 	for my $w (@{$self->{inputs}}) {
-		$lei->_config('--remove-section', "watch.$w");
+		$lei->_config('--remove-section', "watch.$w") or return;
 	}
 	delete $lei->{cfg}; # force reload
 	$lei->refresh_watches;

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/6] lei view_text: used tied ProcessPipe for `git config'
  2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
  2023-09-24  5:42 ` [PATCH 1/6] lei: check git-config(1) failures Eric Wong
@ 2023-09-24  5:42 ` Eric Wong
  2023-09-24  5:42 ` [PATCH 3/6] config: handle key-only entries as booleans Eric Wong
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

The code exists and is loaded anyways, so we might as well
save an explicit call to waitpid.  Noticed while checking
over our uses of `git config'
---
 lib/PublicInbox/LeiViewText.pm | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/lib/PublicInbox/LeiViewText.pm b/lib/PublicInbox/LeiViewText.pm
index 53555467..70441867 100644
--- a/lib/PublicInbox/LeiViewText.pm
+++ b/lib/PublicInbox/LeiViewText.pm
@@ -72,12 +72,11 @@ sub new {
 	my $self = bless { %{$lei->{opt}}, -colored => \&uncolored }, $cls;
 	$self->{-quote_reply} = 1 if $fmt eq 'reply';
 	return $self unless $self->{color} //= -t $lei->{1};
-	my $cmd = [ qw(git config -z --includes -l) ];
-	my ($r, $pid) = popen_rd($cmd, undef, { 2 => $lei->{2} });
+	my @cmd = qw(git config -z --includes -l); # reuse normal git config
+	my $r = popen_rd(\@cmd, undef, { 2 => $lei->{2} });
 	my $cfg = PublicInbox::Config::config_fh_parse($r, "\0", "\n");
-	waitpid($pid, 0);
-	if ($?) {
-		warn "# git-config failed, no color (non-fatal)\n";
+	if (!close($r)) {
+		warn "# @cmd failed, no color (non-fatal \$?=$?)\n";
 		return $self;
 	}
 	$self->{-colored} = \&my_colored;

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/6] config: handle key-only entries as booleans
  2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
  2023-09-24  5:42 ` [PATCH 1/6] lei: check git-config(1) failures Eric Wong
  2023-09-24  5:42 ` [PATCH 2/6] lei view_text: used tied ProcessPipe for `git config' Eric Wong
@ 2023-09-24  5:42 ` Eric Wong
  2023-09-24  5:42 ` [PATCH 4/6] lei config: send `git config' errors to pager Eric Wong
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

It's how git-config works, so our `git config --list' parser
must be able to handle it.  Fortunately this doesn't seem to
incur a measurable overhead when parsing a config with 50k
inboxes.
---
 lib/PublicInbox/Config.pm |  5 ++++-
 t/config.t                | 19 +++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm
index 0a6b210f..f6236d84 100644
--- a/lib/PublicInbox/Config.pm
+++ b/lib/PublicInbox/Config.pm
@@ -151,8 +151,11 @@ sub config_fh_parse ($$$) {
 	local $/ = $rs;
 	while (defined($line = <$fh>)) { # perf critical with giant configs
 		$i = index($line, $fs);
+		# $i may be -1 if $fs not found and it's a key-only entry
+		# (meaning boolean true).  Either way the -1 will drop the
+		# $rs either from $k or $v.
 		$k = substr($line, 0, $i);
-		$v = substr($line, $i + 1, -1); # chop off $fs
+		$v = $i >= 0 ? substr($line, $i + 1, -1) : 1;
 		$section = substr($k, 0, rindex($k, '.'));
 		$seen{$section} //= push(@section_order, $section);
 
diff --git a/t/config.t b/t/config.t
index 80f214cd..8a27a920 100644
--- a/t/config.t
+++ b/t/config.t
@@ -7,6 +7,25 @@ use_ok 'PublicInbox';
 ok(defined(eval('$PublicInbox::VERSION')), 'VERSION defined');
 use_ok 'PublicInbox::Config';
 my ($tmpdir, $for_destroy) = tmpdir();
+use autodie qw(open close);
+my $validate_git_behavior = $ENV{TEST_VALIDATE_GIT_BEHAVIOR};
+
+{
+	my $f = "$tmpdir/bool_config";
+	open my $fh, '>', $f;
+	print $fh <<EOM;
+[imap]
+	debug
+	port = 2
+EOM
+	close $fh;
+	my $cfg = PublicInbox::Config->git_config_dump($f);
+	$validate_git_behavior and
+		is(xqx([qw(git config -f), $f, qw(--bool imap.debug)]),
+			"true\n", 'git handles key-only as truth');
+	ok($cfg->git_bool($cfg->{'imap.debug'}), 'key-only value handled');
+	is($cfg->{'imap.port'}, 2, 'normal k=v read after key-only');
+}
 
 {
 	PublicInbox::Import::init_bare($tmpdir);

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 4/6] lei config: send `git config' errors to pager
  2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
                   ` (2 preceding siblings ...)
  2023-09-24  5:42 ` [PATCH 3/6] config: handle key-only entries as booleans Eric Wong
@ 2023-09-24  5:42 ` Eric Wong
  2023-09-24  5:42 ` [PATCH 5/6] lei: fix `-c NAME=VALUE' config support Eric Wong
  2023-09-24  5:42 ` [PATCH 6/6] config: drop scalar ref support from internal API Eric Wong
  5 siblings, 0 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

Our previous use of lei->cfg_dump was wrong as the extra arg was
never supported.  Instead, we need to capture the output of
`git config' and send it to the pager if ->cfg_dump fails.  We'll
also add a note to the user to quit the pager to continue.
---
 lib/PublicInbox/LEI.pm       |  2 +-
 lib/PublicInbox/LeiConfig.pm | 12 ++++++++++--
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index a6d92eec..488006e0 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -1098,7 +1098,7 @@ sub pgr_err {
 	my ($self, @msg) = @_;
 	return warn(@msg) unless $self->{sock} && -t $self->{2};
 	start_pager($self, { LESS => 'RX' }); # no 'F' so we prompt
-	print { $self->{2} } @msg;
+	say { $self->{2} } @msg, '# -quit pager to continue-';
 	$self->{2}->autoflush(1);
 	stop_pager($self);
 	send($self->{sock}, 'wait', 0); # wait for user to quit pager
diff --git a/lib/PublicInbox/LeiConfig.pm b/lib/PublicInbox/LeiConfig.pm
index 23be9aaf..fd4b0eca 100644
--- a/lib/PublicInbox/LeiConfig.pm
+++ b/lib/PublicInbox/LeiConfig.pm
@@ -4,6 +4,8 @@ package PublicInbox::LeiConfig;
 use strict;
 use v5.10.1;
 use PublicInbox::PktOp;
+use Fcntl qw(SEEK_SET);
+use autodie qw(open seek);
 
 sub cfg_do_edit ($;$) {
 	my ($self, $reason) = @_;
@@ -22,8 +24,14 @@ sub cfg_do_edit ($;$) {
 sub cfg_edit_done { # PktOp
 	my ($self) = @_;
 	eval {
-		my $cfg = $self->{lei}->cfg_dump($self->{-f}, $self->{lei}->{2})
-			// return cfg_do_edit($self, "\n");
+		open my $fh, '+>', undef or die "open($!)";
+		my $cfg = do {
+			local $self->{lei}->{2} = $fh;
+			$self->{lei}->cfg_dump($self->{-f});
+		} or do {
+			seek($fh, 0, SEEK_SET);
+			return cfg_do_edit($self, do { local $/; <$fh> });
+		};
 		$self->cfg_verify($cfg) if $self->can('cfg_verify');
 	};
 	$self->{lei}->fail($@) if $@;

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 5/6] lei: fix `-c NAME=VALUE' config support
  2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
                   ` (3 preceding siblings ...)
  2023-09-24  5:42 ` [PATCH 4/6] lei config: send `git config' errors to pager Eric Wong
@ 2023-09-24  5:42 ` Eric Wong
  2023-09-24  5:42 ` [PATCH 6/6] config: drop scalar ref support from internal API Eric Wong
  5 siblings, 0 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

We can pass `-c NAME=VALUE' args directly to git-config without
needing a temporary directory nor file.  Furthermore, this opens
the door to us being able to correctly handle `-c NAME=VALUE'
after `delete $lei->{cfg}' if we need to reload the config
during a command.

This tightens up error-checking for `lei config' and ensures we
can make config settings changes while using `-c NAME=VALUE'
instead of editing the temporary file.

The non-obvious part was avoiding the use of the -f/--file arg for
`git config' for read-only operations and include relying on
`-c include.path=$ABS_PATH'.  This is done by parsing the
switches to be passed to `git config' to determine if it's a
read-only operation or not.
---
 lib/PublicInbox/Config.pm    | 52 +++++++++++++++-----
 lib/PublicInbox/LEI.pm       | 95 +++++++++++++++++++-----------------
 lib/PublicInbox/LeiConfig.pm | 23 ++++++---
 lib/PublicInbox/LeiMirror.pm |  5 +-
 t/lei.t                      | 17 ++++++-
 5 files changed, 123 insertions(+), 69 deletions(-)

diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm
index f6236d84..533f4a52 100644
--- a/lib/PublicInbox/Config.pm
+++ b/lib/PublicInbox/Config.pm
@@ -22,7 +22,7 @@ sub _array ($) { ref($_[0]) eq 'ARRAY' ? $_[0] : [ $_[0] ] }
 # returns key-value pairs of config directives in a hash
 # if keys may be multi-value, the value is an array ref containing all values
 sub new {
-	my ($class, $file, $errfh) = @_;
+	my ($class, $file, $lei) = @_;
 	$file //= default_file();
 	my $self;
 	my $set_dedupe;
@@ -36,7 +36,7 @@ sub new {
 			$self = $DEDUPE->{$file} and return $self;
 			$set_dedupe = 1;
 		}
-		$self = git_config_dump($class, $file, $errfh);
+		$self = git_config_dump($class, $file, $lei);
 		$self->{'-f'} = $file;
 	}
 	# caches
@@ -174,13 +174,34 @@ sub config_fh_parse ($$$) {
 	\%rv;
 }
 
+sub tmp_cmd_opt ($$) {
+	my ($env, $opt) = @_;
+	# quiet global and system gitconfig if supported by installed git,
+	# but normally harmless if too noisy (NOGLOBAL no longer exists)
+	$env->{GIT_CONFIG_NOSYSTEM} = 1;
+	$env->{GIT_CONFIG_GLOBAL} = '/dev/null'; # git v2.32+
+	$opt->{-C} = '/'; # avoid $worktree/.git/config on MOST systems :P
+}
+
 sub git_config_dump {
-	my ($class, $file, $errfh) = @_;
-	return bless {}, $class unless -e $file;
-	my $cmd = [ qw(git config -z -l --includes), "--file=$file" ];
-	my $fh = popen_rd($cmd, undef, { 2 => $errfh // 2 });
+	my ($class, $file, $lei) = @_;
+	my @opt_c = map { ('-c', $_) } @{$lei->{opt}->{c} // []};
+	$file = undef if !-e $file;
+	# XXX should we set {-f} if !-e $file?
+	return bless {}, $class if (!@opt_c && !defined($file));
+	my %env;
+	my $opt = { 2 => $lei->{2} // 2 };
+	if (@opt_c) {
+		unshift(@opt_c, '-c', "include.path=$file") if defined($file);
+		tmp_cmd_opt(\%env, $opt);
+	}
+	my @cmd = ('git', @opt_c, qw(config -z -l --includes));
+	push(@cmd, '-f', $file) if !@opt_c && defined($file);
+	my $fh = popen_rd(\@cmd, \%env, $opt);
 	my $rv = config_fh_parse($fh, "\0", "\n");
-	close $fh or die "@$cmd failed: \$?=$?\n";
+	close $fh or die "@cmd failed: \$?=$?\n";
+	$rv->{-opt_c} = \@opt_c if @opt_c; # for ->urlmatch
+	$rv->{-f} = $file;
 	bless $rv, $class;
 }
 
@@ -544,14 +565,23 @@ sub _fill_ei ($$) {
 	$es;
 }
 
+sub config_cmd {
+	my ($self, $env, $opt) = @_;
+	my $f = $self->{-f} // default_file();
+	my @opt_c = @{$self->{-opt_c} // []};
+	my @cmd = ('git', @opt_c, 'config');
+	@opt_c ? tmp_cmd_opt($env, $opt) : push(@cmd, '-f', $f);
+	\@cmd;
+}
+
 sub urlmatch {
 	my ($self, $key, $url, $try_git) = @_;
 	state $urlmatch_broken; # requires git 1.8.5
 	return if $urlmatch_broken;
-	my $file = $self->{'-f'} // default_file();
-	my $cmd = [qw/git config -z --includes --get-urlmatch/,
-		"--file=$file", $key, $url ];
-	my $fh = popen_rd($cmd);
+	my (%env, %opt);
+	my $cmd = $self->config_cmd(\%env, \%opt);
+	push @$cmd, qw(-z --includes --get-urlmatch), $key, $url;
+	my $fh = popen_rd($cmd, \%env, \%opt);
 	local $/ = "\0";
 	my $val = <$fh>;
 	if (!close($fh)) {
diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index 488006e0..8b62def2 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -281,7 +281,8 @@ our %CMD = ( # sorted in order of importance/use:
 	"use a patch to generate a query for `lei q --stdin'",
 	qw(stdin| in-format|F=s want|w=s@ uri debug), @net_opt, @c_opt ],
 'config' => [ '[...]', sub {
-		'git-config(1) wrapper for '._config_path($_[0]);
+		'git-config(1) wrapper for '._config_path($_[0]). "\n" .
+	'-l/--list and other common git-config uses are supported'
 	}, qw(config-file|system|global|file|f=s), # for conflict detection
 	 qw(edit|e c=s@ C=s@), pass_through('git config') ],
 'inspect' => [ 'ITEMS...|--stdin', 'inspect lei/store and/or local external',
@@ -456,6 +457,7 @@ my %OPTDESC = (
 'z|0' => 'use NUL \\0 instead of newline (CR) to delimit lines',
 
 'signal|s=s' => [ 'SIG', 'signal to send lei-daemon (default: TERM)' ],
+'edit|e	config' => 'open an editor to modify the lei config file',
 ); # %OPTDESC
 
 my %CONFIG_KEYS = (
@@ -760,36 +762,6 @@ sub optparse ($$$) {
 	$err ? fail($self, "usage: lei $cmd $proto\nE: $err") : 1;
 }
 
-sub _tmp_cfg { # for lei -c <name>=<value> ...
-	my ($self) = @_;
-	my $cfg = _lei_cfg($self, 1);
-	require File::Temp;
-	my $ft = File::Temp->new(TEMPLATE => 'lei_cfg-XXXX', TMPDIR => 1);
-	my $tmp = { '-f' => $ft->filename, -tmp => $ft };
-	$ft->autoflush(1);
-	print $ft <<EOM or return fail($self, "$tmp->{-f}: $!");
-[include]
-	path = $cfg->{-f}
-EOM
-	$tmp = $self->{cfg} = bless { %$cfg, %$tmp }, ref($cfg);
-	for (@{$self->{opt}->{c}}) {
-		/\A([^=\.]+\.[^=]+)(?:=(.*))?\z/ or return fail($self, <<EOM);
-`-c $_' is not of the form -c <name>=<value>'
-EOM
-		my ($name, $value) = ($1, $2 // 1);
-		_config($self, '--add', $name, $value) or return;
-		if (defined(my $v = $tmp->{$name})) {
-			if (ref($v) eq 'ARRAY') {
-				push @$v, $value;
-			} else {
-				$tmp->{$name} = [ $v, $value ];
-			}
-		} else {
-			$tmp->{$name} = $value;
-		}
-	}
-}
-
 sub lazy_cb ($$$) { # $pfx is _complete_ or lei_
 	my ($self, $cmd, $pfx) = @_;
 	my $ucmd = $cmd;
@@ -819,7 +791,6 @@ sub dispatch {
 	}
 	if (my $cb = lazy_cb(__PACKAGE__, $cmd, 'lei_')) {
 		optparse($self, $cmd, \@argv) or return;
-		$self->{opt}->{c} and (_tmp_cfg($self) // return);
 		if (my $chdir = $self->{opt}->{C}) {
 			for my $d (@$chdir) {
 				next if $d eq ''; # same as git(1)
@@ -844,17 +815,20 @@ sub _lei_cfg ($;$) {
 	my $f = _config_path($self);
 	my @st = stat($f);
 	my $cur_st = @st ? pack('dd', $st[10], $st[7]) : ''; # 10:ctime, 7:size
-	my ($sto, $sto_dir, $watches, $lne);
-	if (my $cfg = $PATH2CFG{$f}) { # reuse existing object in common case
-		return ($self->{cfg} = $cfg) if $cur_st eq $cfg->{-st};
+	my ($sto, $sto_dir, $watches, $lne, $cfg);
+	if ($cfg = $PATH2CFG{$f}) { # reuse existing object in common case
+		($cur_st eq $cfg->{-st} && !$self->{opt}->{c}) and
+			return ($self->{cfg} = $cfg);
+		# reuse some fields below if they match:
 		($sto, $sto_dir, $watches, $lne) =
 				@$cfg{qw(-lei_store leistore.dir -watches
 					-lei_note_event)};
 	}
 	if (!@st) {
-		unless ($creat) {
-			delete $self->{cfg};
-			return bless {}, 'PublicInbox::Config';
+		unless ($creat) { # any commands which write to cfg must creat
+			$cfg = PublicInbox::Config->git_config_dump(
+							'/dev/null', $self);
+			return ($self->{cfg} = $cfg);
 		}
 		my ($cfg_dir) = ($f =~ m!(.*?/)[^/]+\z!);
 		File::Path::mkpath($cfg_dir);
@@ -863,9 +837,8 @@ sub _lei_cfg ($;$) {
 		$cur_st = pack('dd', $st[10], $st[7]);
 		qerr($self, "# $f created") if $self->{cmd} ne 'config';
 	}
-	my $cfg = PublicInbox::Config->git_config_dump($f, $self->{2});
+	$cfg = PublicInbox::Config->git_config_dump($f, $self);
 	$cfg->{-st} = $cur_st;
-	$cfg->{'-f'} = $f;
 	if ($sto && canonpath_harder($sto_dir // store_path($self))
 			eq canonpath_harder($cfg->{'leistore.dir'} //
 						store_path($self))) {
@@ -877,7 +850,7 @@ sub _lei_cfg ($;$) {
 		# FIXME: use inotify/EVFILT_VNODE to detect unlinked configs
 		delete(@PATH2CFG{grep(!-f, keys %PATH2CFG)});
 	}
-	$self->{cfg} = $PATH2CFG{$f} = $cfg;
+	$self->{cfg} = $self->{opt}->{c} ? $cfg : ($PATH2CFG{$f} = $cfg);
 	refresh_watches($self);
 	$cfg;
 }
@@ -898,11 +871,41 @@ sub _lei_store ($;$) {
 sub _config {
 	my ($self, @argv) = @_;
 	my $err_ok = ($argv[0] // '') eq '+e' ? shift(@argv) : undef;
-	my %env = (%{$self->{env}}, GIT_CONFIG => undef);
+	my %env;
+	my %opt = map { $_ => $self->{$_} } (0..2);
 	my $cfg = _lei_cfg($self, 1);
-	my $cmd = [ qw(git config -f), $cfg->{'-f'}, @argv ];
-	my %rdr = map { $_ => $self->{$_} } (0..2);
-	waitpid(spawn($cmd, \%env, \%rdr), 0);
+	my $opt_c = delete local $cfg->{-opt_c};
+	my @file_arg;
+	if ($opt_c) {
+		my ($set, $get, $nondash);
+		for (@argv) { # order matters for git-config
+			if (!$nondash) {
+				if (/\A--(?:add|rename-section|remove-section|
+						replace-all|
+						unset-all|unset)\z/x) {
+					++$set;
+				} elsif ($_ eq '-l' || $_ eq '--list' ||
+						/\A--get/) {
+					++$get;
+				} elsif (/\A-/) { # -z and such
+				} else {
+					++$nondash;
+				}
+			} else {
+				++$nondash;
+			}
+		}
+		if ($set || ($nondash//0) > 1 && !$get) {
+			@file_arg = ('-f', $cfg->{-f});
+			$env{GIT_CONFIG} = $file_arg[1];
+		} else { # OK, we can use `-c n=v' for read-only
+			$cfg->{-opt_c} = $opt_c;
+			$env{GIT_CONFIG} = undef;
+		}
+	}
+	my $cmd = $cfg->config_cmd(\%env, \%opt);
+	push @$cmd, @file_arg, @argv;
+	waitpid(spawn($cmd, \%env, \%opt), 0);
 	$? == 0 ? 1 : ($err_ok ? undef : fail($self, $?));
 }
 
@@ -1545,7 +1548,7 @@ sub sto_done_request {
 
 sub cfg_dump ($$) {
 	my ($lei, $f) = @_;
-	my $ret = eval { PublicInbox::Config->git_config_dump($f, $lei->{2}) };
+	my $ret = eval { PublicInbox::Config->git_config_dump($f, $lei) };
 	return $ret if !$@;
 	warn($@);
 	undef;
diff --git a/lib/PublicInbox/LeiConfig.pm b/lib/PublicInbox/LeiConfig.pm
index fd4b0eca..76fc43e7 100644
--- a/lib/PublicInbox/LeiConfig.pm
+++ b/lib/PublicInbox/LeiConfig.pm
@@ -1,8 +1,7 @@
-# Copyright (C) 2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-package PublicInbox::LeiConfig;
-use strict;
-use v5.10.1;
+package PublicInbox::LeiConfig; # subclassed by LeiEditSearch
+use v5.12;
 use PublicInbox::PktOp;
 use Fcntl qw(SEEK_SET);
 use autodie qw(open seek);
@@ -41,10 +40,18 @@ sub lei_config {
 	my ($lei, @argv) = @_;
 	$lei->{opt}->{'config-file'} and return $lei->fail(
 		"config file switches not supported by `lei config'");
-	return $lei->_config(@argv) unless $lei->{opt}->{edit};
-	my $f = $lei->_lei_cfg(1)->{-f};
-	my $self = bless { lei => $lei, -f => $f }, __PACKAGE__;
-	cfg_do_edit($self);
+	if ($lei->{opt}->{edit}) {
+		@argv and return $lei->fail(
+'--edit must be used without other arguments');
+		$lei->{opt}->{c} and return $lei->fail(
+"`-c $lei->{opt}->{c}->[0]' not allowed with --edit");
+		my $f = $lei->_lei_cfg(1)->{-f};
+		cfg_do_edit(bless { lei => $lei, -f => $f }, __PACKAGE__);
+	} elsif (@argv) { # let git-config do error-checking
+		$lei->_config(@argv);
+	} else {
+		$lei->_help('no options given');
+	}
 }
 
 1;
diff --git a/lib/PublicInbox/LeiMirror.pm b/lib/PublicInbox/LeiMirror.pm
index bed034f1..fed6b668 100644
--- a/lib/PublicInbox/LeiMirror.pm
+++ b/lib/PublicInbox/LeiMirror.pm
@@ -193,7 +193,8 @@ sub _write_inbox_config {
 	} elsif (!$!{EEXIST}) {
 		die "open($f): $!";
 	}
-	my $cfg = PublicInbox::Config->git_config_dump($f, $self->{lei}->{2});
+	my $cfg = PublicInbox::Config->git_config_dump($f,
+						{ 2 => $self->{lei}->{2} });
 	my $ibx = $self->{ibx} = {}; # for indexing
 	for my $sec (grep(/\Apublicinbox\./, @{$cfg->{-section_order}})) {
 		for (qw(address newsgroup nntpmirror)) {
@@ -238,7 +239,7 @@ sub index_cloned_inbox {
 		}
 		# force synchronous awaitpid for v2:
 		local $PublicInbox::DS::in_loop = 0;
-		my $cfg = PublicInbox::Config->new(undef, $lei->{2});
+		my $cfg = PublicInbox::Config->new(undef, { 2 => $lei->{2} });
 		my $env = PublicInbox::Admin::index_prepare($opt, $cfg);
 		local %ENV = (%ENV, %$env) if $env;
 		PublicInbox::Admin::progress_prepare($opt, $lei->{2});
diff --git a/t/lei.t b/t/lei.t
index 1199ca75..3ac804a8 100644
--- a/t/lei.t
+++ b/t/lei.t
@@ -40,10 +40,21 @@ my $test_help = sub {
 	lei_ok(qw(config -h));
 	like($lei_out, qr! \Q$home\E/\.config/lei/config\b!,
 		'actual path shown in config -h');
+	my $exp_help = qr/\Q$lei_out\E/s;
+	ok(!lei('config'), 'config w/o args fails');
+	like($lei_err, $exp_help, 'config w/o args shows our help in stderr');
 	lei_ok(qw(config -h), { XDG_CONFIG_HOME => '/XDC' },
 		\'config with XDG_CONFIG_HOME');
 	like($lei_out, qr! /XDC/lei/config\b!, 'XDG_CONFIG_HOME in config -h');
 	is($lei_err, '', 'no errors from config -h');
+
+	lei_ok(qw(-c foo.bar config dash.c works));
+	lei_ok(qw(config dash.c));
+	is($lei_out, "works\n", 'config set w/ -c');
+
+	lei_ok(qw(-c foo.bar config --add dash.c add-works));
+	lei_ok(qw(config --get-all dash.c));
+	is($lei_out, "works\nadd-works\n", 'config --add w/ -c');
 };
 
 my $ok_err_info = sub {
@@ -101,9 +112,11 @@ my $test_config = sub {
 	is($lei_out, "tr00\n", "-c string value passed as-is");
 	lei_ok(qw(-c imap.debug=a -c imap.debug=b config --get-all imap.debug));
 	is($lei_out, "a\nb\n", '-c and --get-all work together');
-
-	lei_ok([qw(config -e)], { VISUAL => 'cat', EDITOR => 'cat' });
+	my $env = { VISUAL => 'cat', EDITOR => 'cat' };
+	lei_ok([qw(config -e)], $env);
 	is($lei_out, "[a]\n\tb = c\n", '--edit works');
+	ok(!lei([qw(-c a.b=c config -e)], $env), '-c conflicts with -e');
+	like($lei_err, qr/not allowed/, 'error message shown');
 };
 
 my $test_completion = sub {

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 6/6] config: drop scalar ref support from internal API
  2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
                   ` (4 preceding siblings ...)
  2023-09-24  5:42 ` [PATCH 5/6] lei: fix `-c NAME=VALUE' config support Eric Wong
@ 2023-09-24  5:42 ` Eric Wong
  2023-09-24  9:50   ` [SQUASH 7/6] t/config: fix missing coderepo.<PROJECT>.dir entry Eric Wong
  5 siblings, 1 reply; 8+ messages in thread
From: Eric Wong @ 2023-09-24  5:42 UTC (permalink / raw)
  To: meta

It's a needless branch to maintain exclusively for our tests.
The `git config -l' output isn't pleasant to write in tests,
anyways.  So just use heredocs to write git configs in their
native format rather than emulate the output of `git config -l'.

This does make the test suite do more work with temporary files
and process invocations, but it doesn't seem very measurable
when testing on tmpfs (TMPDIR=/dev/shm).

We'll make a minor improvement to TestCommon::tmpdir by allowing
it to return a single value (which I suspect we can rely on in
more places since File::Temp::Dir overloads stringification).
---
 lib/PublicInbox/Config.pm     |  23 +++----
 lib/PublicInbox/TestCommon.pm |  20 +++++--
 t/config.t                    | 109 +++++++++++++++++-----------------
 t/config_limiter.t            |  31 +++++-----
 t/inbox_idle.t                |  15 +++--
 t/psgi_bad_mids.t             |  18 +++---
 t/psgi_mount.t                |  14 ++---
 t/psgi_multipart_not.t        |  16 +++--
 t/psgi_scan_all.t             |  18 +++---
 t/psgi_search.t               |  12 ++--
 t/psgi_text.t                 |  21 +++----
 t/watch_filter_rubylang.t     |  30 ++++------
 t/watch_imap.t                |  20 ++++---
 t/watch_maildir.t             |  24 +++-----
 t/watch_maildir_v2.t          |  44 +++++++-------
 t/watch_multiple_headers.t    |  21 ++++---
 16 files changed, 209 insertions(+), 227 deletions(-)

diff --git a/lib/PublicInbox/Config.pm b/lib/PublicInbox/Config.pm
index 533f4a52..9f764c32 100644
--- a/lib/PublicInbox/Config.pm
+++ b/lib/PublicInbox/Config.pm
@@ -24,21 +24,14 @@ sub _array ($) { ref($_[0]) eq 'ARRAY' ? $_[0] : [ $_[0] ] }
 sub new {
 	my ($class, $file, $lei) = @_;
 	$file //= default_file();
-	my $self;
-	my $set_dedupe;
-	if (ref($file) eq 'SCALAR') { # used by some tests
-		open my $fh, '<', $file or die;  # PerlIO::scalar
-		$self = config_fh_parse($fh, "\n", '=');
-		bless $self, $class;
-	} else {
-		if (-f $file && $DEDUPE) {
-			$file = rel2abs_collapsed($file);
-			$self = $DEDUPE->{$file} and return $self;
-			$set_dedupe = 1;
-		}
-		$self = git_config_dump($class, $file, $lei);
-		$self->{'-f'} = $file;
-	}
+	my ($self, $set_dedupe);
+	if (-f $file && $DEDUPE) {
+		$file = rel2abs_collapsed($file);
+		$self = $DEDUPE->{$file} and return $self;
+		$set_dedupe = 1;
+	}
+	$self = git_config_dump($class, $file, $lei);
+	$self->{-f} = $file;
 	# caches
 	$self->{-by_addr} = {};
 	$self->{-by_list_id} = {};
diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm
index ae67a0ae..9ad181e2 100644
--- a/lib/PublicInbox/TestCommon.pm
+++ b/lib/PublicInbox/TestCommon.pm
@@ -26,7 +26,7 @@ BEGIN {
 		create_coderepo no_scm_rights
 		tcp_host_port test_lei lei lei_ok $lei_out $lei_err $lei_opt
 		test_httpd xbail require_cmd is_xdeeply tail_f
-		ignore_inline_c_missing no_pollerfd no_coredump);
+		ignore_inline_c_missing no_pollerfd no_coredump cfg_new);
 	require Test::More;
 	my @methods = grep(!/\W/, @Test::More::EXPORT);
 	eval(join('', map { "*$_=\\&Test::More::$_;" } @methods));
@@ -46,11 +46,9 @@ sub eml_load ($) {
 sub tmpdir (;$) {
 	my ($base) = @_;
 	require File::Temp;
-	unless (defined $base) {
-		($base) = ($0 =~ m!\b([^/]+)\.[^\.]+\z!);
-	}
+	($base) = ($0 =~ m!\b([^/]+)\.[^\.]+\z!) unless defined $base;
 	my $tmpdir = File::Temp->newdir("pi-$base-$$-XXXX", TMPDIR => 1);
-	($tmpdir->dirname, $tmpdir);
+	wantarray ? ($tmpdir->dirname, $tmpdir) : $tmpdir;
 }
 
 sub tcp_server () {
@@ -894,6 +892,18 @@ sub no_pollerfd ($) {
 		is(grep(/$re/, @of), 0, "no $re FDs") or diag explain(\@of);
 	}
 }
+
+sub cfg_new ($;@) {
+	my ($tmpdir, @body) = @_;
+	use autodie;
+	require PublicInbox::Config;
+	my $f = "$tmpdir/tmp_cfg";
+	open my $fh, '>', $f;
+	print $fh @body;
+	close $fh;
+	PublicInbox::Config->new($f);
+}
+
 package PublicInbox::TestCommon::InboxWakeup;
 use strict;
 sub on_inbox_unlock { ${$_[0]}->($_[1]) }
diff --git a/t/config.t b/t/config.t
index 8a27a920..d567abd9 100644
--- a/t/config.t
+++ b/t/config.t
@@ -82,28 +82,30 @@ EOM
 
 
 {
-	my $cfgpfx = "publicinbox.test";
 	my @altid = qw(serial:gmane:file=a serial:enamg:file=b);
-	my $config = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=test\@example.com
-$cfgpfx.mainrepo=/path/to/non/existent
-$cfgpfx.altid=serial:gmane:file=a
-$cfgpfx.altid=serial:enamg:file=b
+	my $config = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	altid=serial:gmane:file=a
+	altid=serial:enamg:file=b
 EOF
 	my $ibx = $config->lookup_name('test');
 	is_deeply($ibx->{altid}, [ @altid ]);
 
-	$config = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=test\@example.com
-$cfgpfx.mainrepo=/path/to/non/existent
+	$config = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
 EOF
 	$ibx = $config->lookup_name('test');
 	is($ibx->{inboxdir}, '/path/to/non/existent', 'mainrepo still works');
 
-	$config = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=test\@example.com
-$cfgpfx.inboxdir=/path/to/non/existent
-$cfgpfx.mainrepo=/path/to/deprecated
+	$config = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	mainrepo = /path/to/deprecated
 EOF
 	$ibx = $config->lookup_name('test');
 	is($ibx->{inboxdir}, '/path/to/non/existent',
@@ -111,28 +113,29 @@ EOF
 }
 
 {
-	my $pfx = "publicinbox.test";
-	my $str = <<EOF;
-$pfx.address=test\@example.com
-$pfx.inboxdir=/path/to/non/existent
-$pfx.newsgroup=inbox.test
-publicinbox.nntpserver=news.example.com
+	my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	newsgroup = inbox.test
+[publicinbox]
+	nntpserver = news.example.com
 EOF
-	my $cfg = PublicInbox::Config->new(\$str);
 	my $ibx = $cfg->lookup_name('test');
 	is_deeply($ibx->nntp_url({ www => { pi_cfg => $cfg }}),
 		[ 'nntp://news.example.com/inbox.test' ],
 		'nntp_url uses global NNTP server');
 
-	$str = <<EOF;
-$pfx.address=test\@example.com
-$pfx.inboxdir=/path/to/non/existent
-$pfx.newsgroup=inbox.test
-$pfx.nntpserver=news.alt.example.com
-publicinbox.nntpserver=news.example.com
-publicinbox.imapserver=imaps://mail.example.com
+	$cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	newsgroup = inbox.test
+	nntpserver = news.alt.example.com
+[publicinbox]
+	nntpserver = news.example.com
+	imapserver = imaps://mail.example.com
 EOF
-	$cfg = PublicInbox::Config->new(\$str);
 	$ibx = $cfg->lookup_name('test');
 	is_deeply($ibx->nntp_url({ www => { pi_cfg => $cfg }}),
 		[ 'nntp://news.alt.example.com/inbox.test' ],
@@ -144,17 +147,18 @@ EOF
 
 # no obfuscate domains
 {
-	my $pfx = "publicinbox.test";
-	my $pfx2 = "publicinbox.foo";
-	my $str = <<EOF;
-$pfx.address=test\@example.com
-$pfx.inboxdir=/path/to/non/existent
-$pfx2.address=foo\@example.com
-$pfx2.inboxdir=/path/to/foo
-publicinbox.noobfuscate=public-inbox.org \@example.com z\@EXAMPLE.com
-$pfx.obfuscate=true
+	my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+[publicinbox "foo"]
+	address = foo\@example.com
+	inboxdir = /path/to/foo
+[publicinbox]
+	noobfuscate = public-inbox.org \@example.com z\@EXAMPLE.com
+[publicinbox "test"]
+	obfuscate = true
 EOF
-	my $cfg = PublicInbox::Config->new(\$str);
 	my $ibx = $cfg->lookup_name('test');
 	my $re = $ibx->{-no_obfuscate_re};
 	like('meta@public-inbox.org', $re,
@@ -226,18 +230,16 @@ for my $s (@valid) {
 }
 
 {
-	my $pfx1 = "publicinbox.test1";
-	my $pfx2 = "publicinbox.test2";
-	my $str = <<EOF;
-$pfx1.address=test\@example.com
-$pfx1.inboxdir=/path/to/non/existent
-$pfx2.address=foo\@example.com
-$pfx2.inboxdir=/path/to/foo
-$pfx1.coderepo=project
-$pfx2.coderepo=project
-coderepo.project.dir=/path/to/project.git
+	my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test1"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	coderepo = project
+[publicinbox "test2"]
+	address = foo\@example.com
+	inboxdir = /path/to/foo
+	coderepo = project
 EOF
-	my $cfg = PublicInbox::Config->new(\$str);
 	my $t1 = $cfg->lookup_name('test1');
 	my $t2 = $cfg->lookup_name('test2');
 	is($cfg->repo_objs($t1)->[0], $cfg->repo_objs($t2)->[0],
@@ -263,16 +265,11 @@ EOF
 
 SKIP: {
 	# XXX wildcard match requires git 2.26+
-	require_git('1.8.5', 2);
-	my $f = "$tmpdir/urlmatch";
-	open my $fh, '>', $f or BAIL_OUT $!;
-	print $fh <<EOF or BAIL_OUT $!;
+	require_git v1.8.5, 2;
+	my $cfg = cfg_new $tmpdir, <<EOF;
 [imap "imap://mail.example.com"]
 	pollInterval = 9
 EOF
-	close $fh or BAIL_OUT;
-	local $ENV{PI_CONFIG} = $f;
-	my $cfg = PublicInbox::Config->new;
 	my $url = 'imap://mail.example.com/INBOX';
 	is($cfg->urlmatch('imap.pollInterval', $url), 9, 'urlmatch hit');
 	is($cfg->urlmatch('imap.idleInterval', $url), undef, 'urlmatch miss');
diff --git a/t/config_limiter.t b/t/config_limiter.t
index 8c83aca8..f4d99080 100644
--- a/t/config_limiter.t
+++ b/t/config_limiter.t
@@ -1,15 +1,14 @@
-# Copyright (C) 2016-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use warnings;
-use Test::More;
-use PublicInbox::Config;
-my $cfgpfx = "publicinbox.test";
+use v5.12;
+use PublicInbox::TestCommon;
+my $tmpdir = tmpdir;
 {
-	my $config = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=test\@example.com
-$cfgpfx.inboxdir=/path/to/non/existent
-$cfgpfx.httpbackendmax=12
+	my $config = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	httpbackendmax = 12
 EOF
 	my $ibx = $config->lookup_name('test');
 	my $git = $ibx->git;
@@ -24,11 +23,13 @@ EOF
 }
 
 {
-	my $config = PublicInbox::Config->new(\<<EOF);
-publicinboxlimiter.named.max=3
-$cfgpfx.address=test\@example.com
-$cfgpfx.inboxdir=/path/to/non/existent
-$cfgpfx.httpbackendmax=named
+	my $config = cfg_new $tmpdir, <<EOF;
+[publicinboxlimiter "named"]
+	max = 3
+[publicinbox "test"]
+	address = test\@example.com
+	inboxdir = /path/to/non/existent
+	httpbackendmax = named
 EOF
 	my $ibx = $config->lookup_name('test');
 	my $git = $ibx->git;
diff --git a/t/inbox_idle.t b/t/inbox_idle.t
index 51379764..0ccffab7 100644
--- a/t/inbox_idle.t
+++ b/t/inbox_idle.t
@@ -1,10 +1,8 @@
 #!perl -w
-# Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use v5.10.1;
+use v5.12;
 use PublicInbox::TestCommon;
-use PublicInbox::Config;
 require_git 2.6;
 require_mods(qw(DBD::SQLite));
 require PublicInbox::SearchIdx;
@@ -26,10 +24,11 @@ for my $V (1, 2) {
 		$sidx->idx_release; # allow watching on lockfile
 	};
 	my $obj = InboxIdleTestObj->new;
-	my $pi_cfg = PublicInbox::Config->new(\<<EOF);
-publicinbox.inbox-idle.inboxdir=$inboxdir
-publicinbox.inbox-idle.indexlevel=basic
-publicinbox.inbox-idle.address=$ibx->{-primary_address}
+	my $pi_cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "inbox-idle"]
+	inboxdir = $inboxdir
+	indexlevel = basic
+	address = $ibx->{-primary_address}
 EOF
 	my $ident = 'whatever';
 	$pi_cfg->each_inbox(sub { shift->subscribe_unlock($ident, $obj) });
diff --git a/t/psgi_bad_mids.t b/t/psgi_bad_mids.t
index 8e531b54..ac0eb3c3 100644
--- a/t/psgi_bad_mids.t
+++ b/t/psgi_bad_mids.t
@@ -1,11 +1,9 @@
 #!perl -w
-# Copyright (C) 2018-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use v5.10.1;
+use v5.12;
 use PublicInbox::TestCommon;
 use PublicInbox::Eml;
-use PublicInbox::Config;
 my @mods = qw(DBD::SQLite HTTP::Request::Common Plack::Test
 		URI::Escape Plack::Builder);
 require_git 2.6;
@@ -37,12 +35,12 @@ Date: Fri, 02 Oct 1993 00:00:0$i +0000
 	}
 };
 
-my $cfgpfx = "publicinbox.bad-mids";
-my $cfg = <<EOF;
-$cfgpfx.address=$ibx->{-primary_address}
-$cfgpfx.inboxdir=$ibx->{inboxdir}
-EOF
-my $config = PublicInbox::Config->new(\$cfg);
+my $tmpdir = tmpdir;
+my $config = cfg_new $tmpdir, <<EOM;
+[publicinbox "bad-mids"]
+	address = $ibx->{-primary_address}
+	inboxdir = $ibx->{inboxdir}
+EOM
 my $www = PublicInbox::WWW->new($config);
 test_psgi(sub { $www->call(@_) }, sub {
 	my ($cb) = @_;
diff --git a/t/psgi_mount.t b/t/psgi_mount.t
index 28689f11..e43b9f2d 100644
--- a/t/psgi_mount.t
+++ b/t/psgi_mount.t
@@ -1,14 +1,11 @@
 #!perl -w
-# Copyright (C) 2016-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use v5.10.1;
+use v5.12;
 use PublicInbox::Eml;
 use PublicInbox::TestCommon;
-use PublicInbox::Config;
 my ($tmpdir, $for_destroy) = tmpdir();
 my $v1dir = "$tmpdir/v1.git";
-my $cfgpfx = "publicinbox.test";
 my @mods = qw(HTTP::Request::Common Plack::Test URI::Escape
 	Plack::Builder Plack::App::URLMap);
 require_mods(@mods);
@@ -27,9 +24,10 @@ Date: Thu, 01 Jan 1970 00:00:00 +0000
 zzzzzz
 EOF
 };
-my $cfg = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=$ibx->{-primary_address}
-$cfgpfx.inboxdir=$v1dir
+my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = $ibx->{-primary_address}
+	inboxdir = $v1dir
 EOF
 my $www = PublicInbox::WWW->new($cfg);
 my $app = builder(sub {
diff --git a/t/psgi_multipart_not.t b/t/psgi_multipart_not.t
index 96be1716..e7c43abf 100644
--- a/t/psgi_multipart_not.t
+++ b/t/psgi_multipart_not.t
@@ -1,11 +1,9 @@
 #!perl -w
-# Copyright (C) 2018-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use v5.10.1;
+use v5.12;
 use PublicInbox::TestCommon;
 use PublicInbox::Eml;
-use PublicInbox::Config;
 require_git 2.6;
 my @mods = qw(DBD::SQLite Xapian HTTP::Request::Common
               Plack::Test URI::Escape Plack::Builder Plack::Test);
@@ -28,12 +26,12 @@ Freed^Wmultipart ain't what it used to be
 EOF
 
 };
-my $cfgpfx = "publicinbox.v2test";
-my $cfg = <<EOF;
-$cfgpfx.address=$ibx->{-primary_address}
-$cfgpfx.inboxdir=$ibx->{inboxdir}
+my $tmpdir = tmpdir;
+my $www = PublicInbox::WWW->new(cfg_new($tmpdir, <<EOF));
+[publicinbox "v2test"]
+	address = $ibx->{-primary_address}
+	inboxdir = $ibx->{inboxdir}
 EOF
-my $www = PublicInbox::WWW->new(PublicInbox::Config->new(\$cfg));
 my ($res, $raw);
 test_psgi(sub { $www->call(@_) }, sub {
 	my ($cb) = @_;
diff --git a/t/psgi_scan_all.t b/t/psgi_scan_all.t
index 20b93b78..4c28b553 100644
--- a/t/psgi_scan_all.t
+++ b/t/psgi_scan_all.t
@@ -7,9 +7,9 @@ use PublicInbox::Eml;
 my @use = qw(HTTP::Request::Common Plack::Test);
 my @req = qw(URI::Escape DBD::SQLite);
 require_git v2.6;
-require_mods(@use, @req, qw(PublicInbox::WWW PublicInbox::Config));
+require_mods(@use, @req, qw(PublicInbox::WWW));
 $_->import for @use;
-my $cfg = '';
+my $cfgtxt = '';
 foreach my $i (1..2) {
 	my $ibx = create_inbox "test-$i", version => 2, indexlevel => 'basic',
 	sub {
@@ -24,13 +24,15 @@ Date: Fri, 02 Oct 1993 00:00:00 +0000
 hello world
 EOF
 	};
-	my $cfgpfx = "publicinbox.test-$i";
-	$cfg .= "$cfgpfx.address=$ibx->{-primary_address}\n";
-	$cfg .= "$cfgpfx.inboxdir=$ibx->{inboxdir}\n";
-	$cfg .= "$cfgpfx.url=http://example.com/$i\n";
-
+	$cfgtxt .= <<EOM;
+[publicinbox "test-$i"]
+	address = $ibx->{-primary_address}
+	inboxdir = $ibx->{inboxdir}
+	url = http://example.com/$i
+EOM
 }
-my $www = PublicInbox::WWW->new(PublicInbox::Config->new(\$cfg));
+my $tmpdir = tmpdir;
+my $www = PublicInbox::WWW->new(cfg_new($tmpdir, $cfgtxt));
 
 test_psgi(sub { $www->call(@_) }, sub {
 	my ($cb) = @_;
diff --git a/t/psgi_search.t b/t/psgi_search.t
index d7c9f183..289c34e7 100644
--- a/t/psgi_search.t
+++ b/t/psgi_search.t
@@ -1,12 +1,10 @@
 #!perl -w
 # Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use v5.10.1;
+use v5.12;
 use PublicInbox::TestCommon;
 use IO::Uncompress::Gunzip qw(gunzip);
 use PublicInbox::Eml;
-use PublicInbox::Config;
 use PublicInbox::Inbox;
 my @mods = qw(DBD::SQLite Xapian HTTP::Request::Common Plack::Test
 		URI::Escape Plack::Builder);
@@ -53,10 +51,10 @@ To: git@vger.kernel.org
 EOF
 };
 
-my $cfgpfx = "publicinbox.test";
-my $cfg = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=git\@vger.kernel.org
-$cfgpfx.inboxdir=$ibx->{inboxdir}
+my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = git\@vger.kernel.org
+	inboxdir = $ibx->{inboxdir}
 EOF
 my $www = PublicInbox::WWW->new($cfg);
 test_psgi(sub { $www->call(@_) }, sub {
diff --git a/t/psgi_text.t b/t/psgi_text.t
index e4613945..25599dd9 100644
--- a/t/psgi_text.t
+++ b/t/psgi_text.t
@@ -1,8 +1,6 @@
-# Copyright (C) 2016-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use warnings;
-use Test::More;
+use v5.12;
 use PublicInbox::Eml;
 use PublicInbox::TestCommon;
 my ($tmpdir, $for_destroy) = tmpdir();
@@ -13,13 +11,12 @@ my @mods = qw(HTTP::Request::Common Plack::Test URI::Escape Plack::Builder);
 require_mods(@mods, 'IO::Uncompress::Gunzip');
 use_ok $_ foreach @mods;
 use PublicInbox::Import;
-use PublicInbox::Git;
-use PublicInbox::Config;
 use_ok 'PublicInbox::WWW';
 use_ok 'PublicInbox::WwwText';
-my $config = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=$addr
-$cfgpfx.inboxdir=$maindir
+my $config = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = $addr
+	inboxdir = $maindir
 EOF
 PublicInbox::Import::init_bare($maindir);
 my $www = PublicInbox::WWW->new($config);
@@ -43,11 +40,7 @@ test_psgi(sub { $www->call(@_) }, sub {
 	$res = $cb->($req);
 	$content = $res->content;
 	my $olen = $res->header('Content-Length');
-	my $f = "$tmpdir/cfg";
-	open my $fh, '>', $f or die;
-	print $fh $content or die;
-	close $fh or die;
-	my $cfg = PublicInbox::Config->new($f);
+	my $cfg = cfg_new $tmpdir, $content;
 	is($cfg->{"$cfgpfx.address"}, $addr, 'got expected address in config');
 
 	$req->header('Accept-Encoding' => 'gzip');
diff --git a/t/watch_filter_rubylang.t b/t/watch_filter_rubylang.t
index a6153e46..f72feb9f 100644
--- a/t/watch_filter_rubylang.t
+++ b/t/watch_filter_rubylang.t
@@ -1,11 +1,8 @@
-# Copyright (C) 2019-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use warnings;
+use v5.12;
 use PublicInbox::TestCommon;
-use Test::More;
 use PublicInbox::Eml;
-use PublicInbox::Config;
 require_mods(qw(DBD::SQLite Xapian));
 use_ok 'PublicInbox::Watch';
 use_ok 'PublicInbox::Emergency';
@@ -25,7 +22,6 @@ SKIP: {
 for my $v (@v) {
 	my @warn;
 	local $SIG{__WARN__} = sub { push @warn, @_ };
-	my $cfgpfx = "publicinbox.$v";
 	my $inboxdir = "$tmpdir/$v";
 	my $maildir = "$tmpdir/md-$v";
 	my $spamdir = "$tmpdir/spam-$v";
@@ -60,16 +56,16 @@ Date: Sat, 05 Jan 2019 04:19:17 +0000
 spam
 EOF
 	PublicInbox::Emergency->new($maildir)->prepare(\"$spam");
-
-	my $orig = <<EOF;
-$cfgpfx.address=$addr
-$cfgpfx.inboxdir=$inboxdir
-$cfgpfx.watch=maildir:$maildir
-$cfgpfx.filter=PublicInbox::Filter::RubyLang
-$cfgpfx.altid=serial:alerts:file=msgmap.sqlite3
-publicinboxwatch.watchspam=maildir:$spamdir
-EOF
-	my $cfg = PublicInbox::Config->new(\$orig);
+	my $cfg = cfg_new $tmpdir, <<EOM;
+[publicinbox "$v"]
+	address = $addr
+	inboxdir = $inboxdir
+	watch = maildir:$maildir
+	filter = PublicInbox::Filter::RubyLang
+	altid = serial:alerts:file=msgmap.sqlite3
+[publicinboxwatch]
+	watchspam = maildir:$spamdir
+EOM
 	my $ibx = $cfg->lookup_name($v);
 	$ibx->{-no_fsync} = 1;
 	ok($ibx, 'found inbox by name');
@@ -102,7 +98,7 @@ EOF
 	# ensure orderly destruction to avoid SQLite segfault:
 	PublicInbox::DS->Reset;
 
-	$cfg = PublicInbox::Config->new(\$orig);
+	$cfg = PublicInbox::Config->new($cfg->{-f});
 	$ibx = $cfg->lookup_name($v);
 	$ibx->{-no_fsync} = 1;
 	is($ibx->search->reopen->mset('b:spam')->size, 0, 'spam removed');
diff --git a/t/watch_imap.t b/t/watch_imap.t
index eeda29eb..26fd5330 100644
--- a/t/watch_imap.t
+++ b/t/watch_imap.t
@@ -1,16 +1,18 @@
-# Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use Test::More;
-use PublicInbox::Config;
+use v5.12;
+use PublicInbox::TestCommon;
 # see t/imapd*.t for tests against a live IMAP server
 
 use_ok 'PublicInbox::Watch';
-my $cfg = PublicInbox::Config->new(\<<EOF);
-publicinbox.i.address=i\@example.com
-publicinbox.i.inboxdir=/nonexistent
-publicinbox.i.watch=imap://example.com/INBOX.a
-publicinboxlearn.watchspam=imap://example.com/INBOX.spam
+my $tmpdir = tmpdir;
+my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "i"]
+	address = i\@example.com
+	inboxdir = /nonexistent
+	watch = imap://example.com/INBOX.a
+[publicinboxlearn]
+	watchspam = imap://example.com/INBOX.spam
 EOF
 my $watch = PublicInbox::Watch->new($cfg);
 is($watch->{imap}->{'imap://example.com/INBOX.a'}->[0]->{name}, 'i',
diff --git a/t/watch_maildir.t b/t/watch_maildir.t
index d0df1c1e..29e9bdc5 100644
--- a/t/watch_maildir.t
+++ b/t/watch_maildir.t
@@ -4,7 +4,6 @@
 use v5.12;
 use PublicInbox::Eml;
 use Cwd;
-use PublicInbox::Config;
 use PublicInbox::TestCommon;
 use PublicInbox::Import;
 my ($tmpdir, $for_destroy) = tmpdir();
@@ -13,7 +12,6 @@ my $maildir = "$tmpdir/md";
 my $spamdir = "$tmpdir/spam";
 use_ok 'PublicInbox::Watch';
 use_ok 'PublicInbox::Emergency';
-my $cfgpfx = "publicinbox.test";
 my $addr = 'test-public@example.com';
 my $default_branch = PublicInbox::Import::default_branch;
 PublicInbox::Import::init_bare($git_dir);
@@ -35,11 +33,13 @@ my $sem = PublicInbox::Emergency->new($spamdir); # create dirs
 {
 	my @w;
 	local $SIG{__WARN__} = sub { push @w, @_ };
-	my $cfg = PublicInbox::Config->new(\<<EOF);
-$cfgpfx.address=$addr
-$cfgpfx.inboxdir=$git_dir
-$cfgpfx.watch=maildir:$spamdir
-publicinboxlearn.watchspam=maildir:$spamdir
+	my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = $addr
+	inboxdir = $git_dir
+	watch = maildir:$spamdir
+[publicinboxlearn]
+	watchspam = maildir:$spamdir
 EOF
 	my $wm = PublicInbox::Watch->new($cfg);
 	is(scalar grep(/is a spam folder/, @w), 1, 'got warning about spam');
@@ -47,10 +47,7 @@ EOF
 		'only got the spam folder to watch');
 }
 
-my $cfg_path = "$tmpdir/config";
-{
-	open my $fh, '>', $cfg_path or BAIL_OUT $!;
-	print $fh <<EOF or BAIL_OUT $!;
+my $cfg = cfg_new $tmpdir, <<EOF;
 [publicinbox "test"]
 	address = $addr
 	inboxdir = $git_dir
@@ -59,10 +56,7 @@ my $cfg_path = "$tmpdir/config";
 [publicinboxlearn]
 	watchspam = maildir:$spamdir
 EOF
-	close $fh or BAIL_OUT $!;
-}
-
-my $cfg = PublicInbox::Config->new($cfg_path);
+my $cfg_path = $cfg->{-f};
 PublicInbox::Watch->new($cfg)->scan('full');
 my $git = PublicInbox::Git->new($git_dir);
 my @list = $git->qx('rev-list', $default_branch);
diff --git a/t/watch_maildir_v2.t b/t/watch_maildir_v2.t
index 38679836..fa86f7bf 100644
--- a/t/watch_maildir_v2.t
+++ b/t/watch_maildir_v2.t
@@ -1,10 +1,8 @@
-# Copyright (C) 2018-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use Test::More;
+use v5.12;
 use PublicInbox::Eml;
 use Cwd;
-use PublicInbox::Config;
 use PublicInbox::TestCommon;
 use PublicInbox::Import;
 require_git(2.6);
@@ -38,13 +36,15 @@ ok(POSIX::mkfifo("$maildir/cur/fifo", 0777),
 my $sem = PublicInbox::Emergency->new($spamdir); # create dirs
 
 my $orig = <<EOF;
-$cfgpfx.address=$addr
-$cfgpfx.inboxdir=$inboxdir
-$cfgpfx.watch=maildir:$maildir
-$cfgpfx.filter=PublicInbox::Filter::Vger
-publicinboxlearn.watchspam=maildir:$spamdir
+[publicinbox "test"]
+	address = $addr
+	inboxdir = $inboxdir
+	watch = maildir:$maildir
+	filter = PublicInbox::Filter::Vger
+[publicinboxlearn]
+	watchspam = maildir:$spamdir
 EOF
-my $cfg = PublicInbox::Config->new(\$orig);
+my $cfg = cfg_new $tmpdir, $orig;
 my $ibx = $cfg->lookup_name('test');
 ok($ibx, 'found inbox by name');
 $ibx->{-no_fsync} = 1;
@@ -147,12 +147,13 @@ More majordomo info at  http://vger.kernel.org/majordomo-info.html\n);
 	my $v1pfx = "publicinbox.v1";
 	my $v1addr = 'v1-public@example.com';
 	PublicInbox::Import::init_bare($v1repo);
-	my $raw = <<EOF;
-$orig$v1pfx.address=$v1addr
-$v1pfx.inboxdir=$v1repo
-$v1pfx.watch=maildir:$maildir
+	my $cfg = cfg_new $tmpdir, <<EOF;
+$orig
+[publicinbox "v1"]
+	address = $v1addr
+	inboxdir = $v1repo
+	watch = maildir:$maildir
 EOF
-	my $cfg = PublicInbox::Config->new(\$raw);
 	my $both = <<EOF;
 From: user\@example.com
 To: $addr, $v1addr
@@ -185,19 +186,22 @@ List-Id: <do.not.want>
 X-Mailing-List: no@example.com
 Message-ID: <do.not.want@example.com>
 EOF
-	my $raw = $orig."$cfgpfx.listid=i.want.you.to.want.me\n";
 	PublicInbox::Emergency->new($maildir)->prepare(\$want);
 	PublicInbox::Emergency->new($maildir)->prepare(\$do_not_want);
-	my $cfg = PublicInbox::Config->new(\$raw);
+	my $raw = <<EOM;
+$orig
+[publicinbox "test"]
+	listid = i.want.you.to.want.me
+EOM
+	my $cfg = cfg_new $tmpdir, $raw;
 	PublicInbox::Watch->new($cfg)->scan('full');
 	$ibx = $cfg->lookup_name('test');
 	my $num = $ibx->mm->num_for('do.want@example.com');
 	ok(defined $num, 'List-ID matched for watch');
 	$num = $ibx->mm->num_for('do.not.want@example.com');
 	is($num, undef, 'unaccepted List-ID matched for watch');
-
-	$raw = $orig."$cfgpfx.watchheader=X-Mailing-List:no\@example.com\n";
-	$cfg = PublicInbox::Config->new(\$raw);
+	$raw .= "\twatchheader = X-Mailing-List:no\@example.com\n";
+	$cfg = cfg_new $tmpdir, $raw;
 	PublicInbox::Watch->new($cfg)->scan('full');
 	$ibx = $cfg->lookup_name('test');
 	$num = $ibx->mm->num_for('do.not.want@example.com');
diff --git a/t/watch_multiple_headers.t b/t/watch_multiple_headers.t
index 13dd3452..9585da2b 100644
--- a/t/watch_multiple_headers.t
+++ b/t/watch_multiple_headers.t
@@ -1,8 +1,6 @@
-# Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
+# Copyright (C)  all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-use strict;
-use Test::More;
-use PublicInbox::Config;
+use v5.12;
 use PublicInbox::TestCommon;
 require_git(2.6);
 require_mods(qw(Xapian DBD::SQLite));
@@ -54,14 +52,15 @@ PublicInbox::Emergency->new($maildir)->prepare(\$msg_to);
 PublicInbox::Emergency->new($maildir)->prepare(\$msg_cc);
 PublicInbox::Emergency->new($maildir)->prepare(\$msg_none);
 
-my $raw = <<EOF;
-$cfgpfx.address=$addr
-$cfgpfx.inboxdir=$inboxdir
-$cfgpfx.watch=maildir:$maildir
-$cfgpfx.watchheader=To:$addr
-$cfgpfx.watchheader=Cc:$addr
+my $cfg = cfg_new $tmpdir, <<EOF;
+[publicinbox "test"]
+	address = $addr
+	inboxdir = $inboxdir
+	watch = maildir:$maildir
+	watchheader = To:$addr
+	watchheader = Cc:$addr
 EOF
-my $cfg = PublicInbox::Config->new(\$raw);
+
 PublicInbox::Watch->new($cfg)->scan('full');
 my $ibx = $cfg->lookup_name('test');
 ok($ibx, 'found inbox by name');

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [SQUASH 7/6] t/config: fix missing coderepo.<PROJECT>.dir entry
  2023-09-24  5:42 ` [PATCH 6/6] config: drop scalar ref support from internal API Eric Wong
@ 2023-09-24  9:50   ` Eric Wong
  0 siblings, 0 replies; 8+ messages in thread
From: Eric Wong @ 2023-09-24  9:50 UTC (permalink / raw)
  To: meta

Eric Wong <e@80x24.org> wrote:
> diff --git a/t/config.t b/t/config.t
> index 8a27a920..d567abd9 100644

> @@ -226,18 +230,16 @@ for my $s (@valid) {
>  }
>  
>  {
> -	my $pfx1 = "publicinbox.test1";
> -	my $pfx2 = "publicinbox.test2";
> -	my $str = <<EOF;
> -$pfx1.address=test\@example.com
> -$pfx1.inboxdir=/path/to/non/existent
> -$pfx2.address=foo\@example.com
> -$pfx2.inboxdir=/path/to/foo
> -$pfx1.coderepo=project
> -$pfx2.coderepo=project
> -coderepo.project.dir=/path/to/project.git

Oops, I forgot to translate the coderepo.project.dir line :x

> +	my $cfg = cfg_new $tmpdir, <<EOF;
> +[publicinbox "test1"]
> +	address = test\@example.com
> +	inboxdir = /path/to/non/existent
> +	coderepo = project
> +[publicinbox "test2"]
> +	address = foo\@example.com
> +	inboxdir = /path/to/foo
> +	coderepo = project
>  EOF
> -	my $cfg = PublicInbox::Config->new(\$str);
>  	my $t1 = $cfg->lookup_name('test1');
>  	my $t2 = $cfg->lookup_name('test2');
>  	is($cfg->repo_objs($t1)->[0], $cfg->repo_objs($t2)->[0],

-----8<----
Subject: [SQUASH] t/config: fix missing coderepo.<PROJECT>.dir entry

Also added an extra check to ensure the coderepo's parsed
properly.
---
 t/config.t | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/t/config.t b/t/config.t
index d567abd9..9b6684b7 100644
--- a/t/config.t
+++ b/t/config.t
@@ -239,9 +239,12 @@ for my $s (@valid) {
 	address = foo\@example.com
 	inboxdir = /path/to/foo
 	coderepo = project
+[coderepo "project"]
+	dir = /path/to/project.git
 EOF
 	my $t1 = $cfg->lookup_name('test1');
 	my $t2 = $cfg->lookup_name('test2');
+	ok $cfg->repo_objs($t1)->[0], 'coderepo parsed';
 	is($cfg->repo_objs($t1)->[0], $cfg->repo_objs($t2)->[0],
 		'inboxes share ::Git object');
 }

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2023-09-24  9:50 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-09-24  5:42 [PATCH 0/6] lei config fixes and improvements Eric Wong
2023-09-24  5:42 ` [PATCH 1/6] lei: check git-config(1) failures Eric Wong
2023-09-24  5:42 ` [PATCH 2/6] lei view_text: used tied ProcessPipe for `git config' Eric Wong
2023-09-24  5:42 ` [PATCH 3/6] config: handle key-only entries as booleans Eric Wong
2023-09-24  5:42 ` [PATCH 4/6] lei config: send `git config' errors to pager Eric Wong
2023-09-24  5:42 ` [PATCH 5/6] lei: fix `-c NAME=VALUE' config support Eric Wong
2023-09-24  5:42 ` [PATCH 6/6] config: drop scalar ref support from internal API Eric Wong
2023-09-24  9:50   ` [SQUASH 7/6] t/config: fix missing coderepo.<PROJECT>.dir entry Eric Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).