* [PATCH 0/8] use libgit2 for writing configs
@ 2025-02-15 11:10 Eric Wong
2025-02-15 11:10 ` [PATCH 1/8] favor run_wait() over CORE::system() Eric Wong
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
1/8 was something I noticed while hunting for
`git config' invocations, and 2/8 was noticed when
working on 1/8.
3-5 came about while testing 8/8
Currently there's no noticeable speedup in the entire test suite
from 8/8 since we've been avoiding using `git config' too much
to setup configs, but that will probably change in the future.
The t/lg2_cfg.t test does show a ~%4 speedup compared to
setting TEST_VALIDATE_GIT_BEHAVIOR to force git(1) use instead
of the new libgit2 $cfgrw_commit subroutine.
Eric Wong (8):
favor run_wait() over CORE::system()
edit: use autodie and favor flush over autoflush
import: clobber $? if global init.defaultBranch fails
test_common: key2sub: detect syntax errors early
t/watch_v1_v2_mix_no_modify: load PublicInbox::Config
cfgwrite: new module to batch commit writes
rename Gcf2 -> Lg2 for general libgit2 use
lg2: use cfgwr_commit to write to configs using libgit2
Documentation/public-inbox-tuning.pod | 2 +-
MANIFEST | 6 ++-
lib/PublicInbox/CfgWr.pm | 60 +++++++++++++++++++++++
lib/PublicInbox/ExtSearchIdx.pm | 5 +-
lib/PublicInbox/Gcf2Client.pm | 10 ++--
lib/PublicInbox/Import.pm | 1 +
lib/PublicInbox/LEI.pm | 1 +
lib/PublicInbox/LeiMirror.pm | 23 ++++-----
lib/PublicInbox/LeiSavedSearch.pm | 5 +-
lib/PublicInbox/{Gcf2.pm => Lg2.pm} | 13 +++--
lib/PublicInbox/MultiGit.pm | 4 +-
lib/PublicInbox/SearchView.pm | 2 +-
lib/PublicInbox/Spawn.pm | 2 +-
lib/PublicInbox/TestCommon.pm | 3 +-
lib/PublicInbox/ViewVCS.pm | 2 +-
lib/PublicInbox/{gcf2_libgit2.h => lg2.h} | 60 ++++++++++++++++++++++-
script/public-inbox-edit | 17 +++----
script/public-inbox-init | 16 +++---
t/gcf2.t | 12 ++---
t/gcf2_client.t | 2 +-
t/lg2_cfg.t | 60 +++++++++++++++++++++++
t/watch_v1_v2_mix_no_modify.t | 1 +
xt/check-run.t | 1 +
23 files changed, 245 insertions(+), 63 deletions(-)
create mode 100644 lib/PublicInbox/CfgWr.pm
rename lib/PublicInbox/{Gcf2.pm => Lg2.pm} (92%)
rename lib/PublicInbox/{gcf2_libgit2.h => lg2.h} (64%)
create mode 100644 t/lg2_cfg.t
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 1/8] favor run_wait() over CORE::system()
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 2/8] edit: use autodie and favor flush over autoflush Eric Wong
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
run_wait() can use vfork(2) which saves memory in large
processes, such as public-inbox-extindex when dealing with giant
messages. While vfork is unlikely to matter for real-world uses
of public-inbox-edit, PublicInbox::Spawn is a sunk cost treewide
our `make check-run' test target avoids spawning new Perl
processes for most things in script/*, so there can be a small
savings for testing.
---
lib/PublicInbox/ExtSearchIdx.pm | 5 +++--
script/public-inbox-edit | 3 ++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/lib/PublicInbox/ExtSearchIdx.pm b/lib/PublicInbox/ExtSearchIdx.pm
index d1a16c84..7cf600c1 100644
--- a/lib/PublicInbox/ExtSearchIdx.pm
+++ b/lib/PublicInbox/ExtSearchIdx.pm
@@ -24,7 +24,7 @@ use File::Glob qw(bsd_glob GLOB_NOSORT);
use PublicInbox::SQLiteUtil;
use PublicInbox::Isearch;
use PublicInbox::MultiGit;
-use PublicInbox::Spawn ();
+use PublicInbox::Spawn qw(run_wait);
use PublicInbox::Search;
use PublicInbox::SearchIdx qw(prepare_stack is_ancestor is_bad_blob
update_checkpoint);
@@ -1266,8 +1266,9 @@ sub idx_init { # similar to V2Writable
if ($git_midx && ($opt->{'multi-pack-index'} // 1)) {
my $cmd = $self->git->cmd('multi-pack-index');
push @$cmd, '--no-progress' if ($opt->{quiet}//0) > 1;
+ push @$cmd, 'write';
my $lk = $self->lock_for_scope;
- system(@$cmd, 'write');
+ run_wait $cmd;
# ignore errors, fairly new command, may not exist
}
$self->parallel_init($self->{indexlevel});
diff --git a/script/public-inbox-edit b/script/public-inbox-edit
index 88115d7c..c76579e4 100755
--- a/script/public-inbox-edit
+++ b/script/public-inbox-edit
@@ -15,6 +15,7 @@ PublicInbox::Admin::check_require('-index');
use PublicInbox::Eml;
use PublicInbox::InboxWritable qw(eml_from_path);
use PublicInbox::Import;
+use PublicInbox::Spawn qw(run_wait);
my $help = <<'EOF';
usage: public-inbox-edit -m MESSAGE-ID [--all] [INBOX_DIRS]
@@ -159,7 +160,7 @@ foreach my $to_edit (values %$found) {
# run the editor, respecting spaces/quote
retry_edit:
- if (system(qw(sh -c), $editor.' "$@"', $editor, $edit_fn)) {
+ if (run_wait [qw(sh -c), $editor.' "$@"', $editor, $edit_fn]) {
if (!(-t STDIN) && !$opt->{force}) {
die "E: $editor failed: $?\n";
}
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 2/8] edit: use autodie and favor flush over autoflush
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
2025-02-15 11:10 ` [PATCH 1/8] favor run_wait() over CORE::system() Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 3/8] import: clobber $? if global init.defaultBranch fails Eric Wong
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
Using a single ->flush reduces iops for the (default) non-raw
case and autodie for `open' makes error messages more
consistent.
---
script/public-inbox-edit | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/script/public-inbox-edit b/script/public-inbox-edit
index c76579e4..a363f0c8 100755
--- a/script/public-inbox-edit
+++ b/script/public-inbox-edit
@@ -5,7 +5,8 @@
# Used for editing messages in a public-inbox.
# Supports v2 inboxes only, for now.
use strict;
-use warnings;
+use v5.10.1; # TODO: check unicode_strings compat
+use autodie qw(open);
use Getopt::Long qw(:config gnu_getopt no_ignore_case auto_abbrev);
use PublicInbox::AdminEdit;
use File::Temp 0.19 (); # 0.19 for TMPDIR
@@ -142,7 +143,6 @@ my %tmpopt = (
foreach my $to_edit (values %$found) {
my $edit_fh = File::Temp->new(%tmpopt);
- $edit_fh->autoflush(1);
my $edit_fn = $edit_fh->filename;
my ($ibx, $smsg) = @{$to_edit->[0]};
my $old_raw = $ibx->msg_by_smsg($smsg);
@@ -151,12 +151,11 @@ foreach my $to_edit (values %$found) {
my $tmp = $$old_raw;
if (!$opt->{raw}) {
my $oid = $smsg->{blob};
- print $edit_fh "From mboxrd\@$oid Thu Jan 1 00:00:00 1970\n"
- or die "failed to write From_ line: $!";
+ print $edit_fh "From mboxrd\@$oid Thu Jan 1 00:00:00 1970\n";
$tmp =~ s/^(>*From )/>$1/gm;
}
- print $edit_fh $tmp or
- die "failed to write tempfile for editing: $!";
+ print $edit_fh $tmp;
+ $edit_fh->flush or die "E: flush($edit_fn): $!";
# run the editor, respecting spaces/quote
retry_edit:
@@ -183,8 +182,7 @@ retry_edit:
# reread the edited file, not using $edit_fh since $EDITOR may
# rename/relink $edit_fn
- open my $new_fh, '<', $edit_fn or
- die "can't read edited file ($edit_fn): $!\n";
+ open my $new_fh, '<', $edit_fn;
my $new_raw = PublicInbox::IO::read_all $new_fh;
if (!$opt->{raw}) {
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 3/8] import: clobber $? if global init.defaultBranch fails
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
2025-02-15 11:10 ` [PATCH 1/8] favor run_wait() over CORE::system() Eric Wong
2025-02-15 11:10 ` [PATCH 2/8] edit: use autodie and favor flush over autoflush Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 4/8] test_common: key2sub: detect syntax errors early Eric Wong
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
We don't want $? influencing further error checking downstream,
such as thw _wq_done_wait call in public-inbox-clone when
git-config(1) invocations which set $?=0 are replaced with
in-process libgit2 config function calls.
---
lib/PublicInbox/Import.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/PublicInbox/Import.pm b/lib/PublicInbox/Import.pm
index e6d725fc..35985f74 100644
--- a/lib/PublicInbox/Import.pm
+++ b/lib/PublicInbox/Import.pm
@@ -29,6 +29,7 @@ sub default_branch () {
my $h = run_qx([git_exe,qw(config --global init.defaultBranch)],
{ GIT_CONFIG => undef });
chomp $h;
+ $? = 0;
$h eq '' ? 'refs/heads/master' : "refs/heads/$h";
}
}
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 4/8] test_common: key2sub: detect syntax errors early
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
` (2 preceding siblings ...)
2025-02-15 11:10 ` [PATCH 3/8] import: clobber $? if global init.defaultBranch fails Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 5/8] t/watch_v1_v2_mix_no_modify: load PublicInbox::Config Eric Wong
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
Instead of attempting to use an `undef' $sub as a coderef;
detect compile errors early and die immediately if `eval'
raises an exception.
---
lib/PublicInbox/TestCommon.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm
index cbcfc008..e1178149 100644
--- a/lib/PublicInbox/TestCommon.pm
+++ b/lib/PublicInbox/TestCommon.pm
@@ -371,6 +371,7 @@ sub main {
}
1;
EOF
+ die "E: $f failed: $@" if $@;
$pkg->can('main');
}
}
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 5/8] t/watch_v1_v2_mix_no_modify: load PublicInbox::Config
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
` (3 preceding siblings ...)
2025-02-15 11:10 ` [PATCH 4/8] test_common: key2sub: detect syntax errors early Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 6/8] cfgwrite: new module to batch commit writes Eric Wong
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
For testing with TEST_RUN_MODE=0 (which spawns new processes
instead of reusing them), we can't rely on packages being loaded
by subprocesses influencing the main Perl process. Thus we must
load PublicInbox::Config explicitly to prevent failures with
this run mode.
---
t/watch_v1_v2_mix_no_modify.t | 1 +
1 file changed, 1 insertion(+)
diff --git a/t/watch_v1_v2_mix_no_modify.t b/t/watch_v1_v2_mix_no_modify.t
index 759fd0bb..d0e2a78c 100644
--- a/t/watch_v1_v2_mix_no_modify.t
+++ b/t/watch_v1_v2_mix_no_modify.t
@@ -9,6 +9,7 @@ use PublicInbox::InboxIdle;
use PublicInbox::DS qw(now);
use PublicInbox::IO qw(write_file);
use PublicInbox::Eml;
+use PublicInbox::Config;
my $tmpdir = tmpdir;
local $ENV{HOME} = "$tmpdir";
local $ENV{PI_CONFIG} = "$tmpdir/.public-inbox/config";
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 6/8] cfgwrite: new module to batch commit writes
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
` (4 preceding siblings ...)
2025-02-15 11:10 ` [PATCH 5/8] t/watch_v1_v2_mix_no_modify: load PublicInbox::Config Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 7/8] rename Gcf2 -> Lg2 for general libgit2 use Eric Wong
2025-02-15 11:10 ` [PATCH 8/8] lg2: use cfgwr_commit to write to configs using libgit2 Eric Wong
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
Hoisting git config writing code into a separate module will
make it easier to use libgit2 to avoid process spawning overhead
in a future change.
---
MANIFEST | 1 +
lib/PublicInbox/CfgWr.pm | 54 +++++++++++++++++++++++++++++++
lib/PublicInbox/LeiMirror.pm | 23 ++++++-------
lib/PublicInbox/LeiSavedSearch.pm | 5 +--
lib/PublicInbox/MultiGit.pm | 3 +-
script/public-inbox-init | 16 +++++----
6 files changed, 78 insertions(+), 24 deletions(-)
create mode 100644 lib/PublicInbox/CfgWr.pm
diff --git a/MANIFEST b/MANIFEST
index 2ca7755c..f0b42826 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -164,6 +164,7 @@ lib/PublicInbox/AdminEdit.pm
lib/PublicInbox/AltId.pm
lib/PublicInbox/Aspawn.pm
lib/PublicInbox/AutoReap.pm
+lib/PublicInbox/CfgWr.pm
lib/PublicInbox/Cgit.pm
lib/PublicInbox/CidxComm.pm
lib/PublicInbox/CidxLogP.pm
diff --git a/lib/PublicInbox/CfgWr.pm b/lib/PublicInbox/CfgWr.pm
new file mode 100644
index 00000000..2b3fd507
--- /dev/null
+++ b/lib/PublicInbox/CfgWr.pm
@@ -0,0 +1,54 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# config writer, may use libgit2 in the future
+package PublicInbox::CfgWr;
+use v5.12;
+use PublicInbox::Git qw(git_exe);
+use PublicInbox::Spawn qw(run_die run_wait);
+
+sub new {
+ my ($cls, $f) = @_;
+ bless { -f => $f }, $cls;
+}
+
+sub set {
+ my ($self, $k, $v) = @_;
+ push @{$self->{todo}}, [ $k, $v ];
+ $self;
+}
+
+sub add {
+ my ($self, $k, $v) = @_;
+ push @{$self->{todo}}, [ '--add', $k, $v ];
+ $self;
+}
+
+sub replace_all {
+ my ($self, $k, $v, $re) = @_;
+ push @{$self->{todo}}, [ '--replace-all', $k, $v, $re ];
+ $self;
+}
+
+sub unset_all {
+ my ($self, $k) = @_;
+ push @{$self->{todo}}, [ '--unset-all', $k ];
+ $self;
+}
+
+sub commit {
+ my ($self, $opt) = @_;
+ my @x = (git_exe, 'config', '-f', $self->{-f});
+ for my $c (@{delete $self->{todo} // []}) {
+ unshift @$c, @x;
+ if ($c->[scalar(@x)] eq '--unset-all') {
+ run_wait $c, undef, $opt;
+ # ignore ret=5 if no matches (see git-config(1))
+ die "E: @$c \$?=$?" if ($? && ($? >> 8) != 5);
+ } else {
+ run_die $c, undef, $opt;
+ }
+ }
+}
+
+1;
diff --git a/lib/PublicInbox/LeiMirror.pm b/lib/PublicInbox/LeiMirror.pm
index 0f14ceba..51d0d9ac 100644
--- a/lib/PublicInbox/LeiMirror.pm
+++ b/lib/PublicInbox/LeiMirror.pm
@@ -19,6 +19,7 @@ use PublicInbox::Config qw(glob2re);
use PublicInbox::Inbox;
use PublicInbox::LeiCurl;
use PublicInbox::OnDestroy;
+use PublicInbox::CfgWr;
use PublicInbox::SHA qw(sha256_hex sha_all);
use POSIX qw(strftime);
use PublicInbox::Admin qw(fmt_localtime);
@@ -415,16 +416,13 @@ sub fgrp_fetch_all {
my ($old, $new) = @$fgrp_old_new;
@$old = sort { $b->{-sort} <=> $a->{-sort} } @$old;
# $new is ordered by {references}
- my $cmd = [ git_exe, "--git-dir=$osdir", qw(config -f), $f ];
+ my $cfgwr = PublicInbox::CfgWr->new($f);
# clobber settings from previous run atomically
for ("remotes.$grp", 'fetch.hideRefs') {
- my $c = [ @$cmd, '--unset-all', $_ ];
- $self->{lei}->qerr("# @$c");
- next if $self->{dry_run};
- run_wait($c, undef, $opt);
- die "E: @$c \$?=$?" if ($? && ($? >> 8) != 5);
+ $cfgwr->unset_all($_) if !$self->{dry_run};
}
+ $cfgwr->commit($opt);
# permanent configs:
my $cfg = PublicInbox::Config->git_config_dump($f);
@@ -436,12 +434,10 @@ sub fgrp_fetch_all {
my ($k, $v) = split(/=/, $_, 2);
$k = "remote.$rn.$k";
next if ($cfg->{$k} // '') eq $v;
- my $c = [@$cmd, $k, $v];
- $fgrp->{lei}->qerr("# @$c");
- next if $fgrp->{dry_run};
- run_die($c, undef, $opt);
+ $cfgwr->set($k, $v) if !$fgrp->{dry_run};
}
}
+ $cfgwr->commit($opt);
if (!$self->{dry_run}) {
# update the config atomically via O_APPEND while
@@ -460,7 +456,7 @@ sub fgrp_fetch_all {
write_file '>>', $f, @buf;
unlink("$f.lock");
}
- $cmd = [ @git, "--git-dir=$osdir", @fetch, $grp ];
+ my $cmd = [ @git, "--git-dir=$osdir", @fetch, $grp ];
push @$old, @$new;
my $end = on_destroy \&fgrpv_done, $old;
start_cmd($self, $cmd, $opt, $end);
@@ -809,9 +805,8 @@ sub update_ent {
$cur = $self->{-local_manifest}->{$key}->{owner} // "\0";
return if $cur eq $new;
utf8::encode($new); # to octets
- my $cmd = [ git_exe, qw(config -f), "$dst/config",
- 'gitweb.owner', $new ];
- start_cmd($self, $cmd, { 2 => $self->{lei}->{2} });
+ PublicInbox::CfgWr->new(
+ "$dst/config")->set('gitweb.owner', $new)->commit;
}
sub v1_done { # called via OnDestroy
diff --git a/lib/PublicInbox/LeiSavedSearch.pm b/lib/PublicInbox/LeiSavedSearch.pm
index 612d1f43..574cf9aa 100644
--- a/lib/PublicInbox/LeiSavedSearch.pm
+++ b/lib/PublicInbox/LeiSavedSearch.pm
@@ -10,6 +10,7 @@ use PublicInbox::Git qw(git_exe);
use PublicInbox::OverIdx;
use PublicInbox::LeiSearch;
use PublicInbox::Config;
+use PublicInbox::CfgWr;
use PublicInbox::Spawn qw(run_die);
use PublicInbox::ContentHash qw(git_sha);
use PublicInbox::MID qw(mids_for_index);
@@ -179,9 +180,9 @@ EOM
sub description { $_[0]->{qstr} } # for WWW
sub cfg_set { # called by LeiXSearch
- my ($self, @args) = @_;
+ my ($self, $k, $v) = @_;
my $lk = $self->lock_for_scope; # git-config doesn't wait
- run_die([git_exe, qw(config -f), $self->{'-f'}, @args]);
+ PublicInbox::CfgWr->new($self->{-f})->set($k, $v)->commit;
}
# drop-in for LeiDedupe API
diff --git a/lib/PublicInbox/MultiGit.pm b/lib/PublicInbox/MultiGit.pm
index 9be3a876..c99a11cb 100644
--- a/lib/PublicInbox/MultiGit.pm
+++ b/lib/PublicInbox/MultiGit.pm
@@ -7,6 +7,7 @@ use strict;
use v5.10.1;
use PublicInbox::Spawn qw(run_die run_qx);
use PublicInbox::Import;
+use PublicInbox::CfgWr;
use PublicInbox::Git qw(git_exe);
use File::Temp 0.19;
use List::Util qw(max);
@@ -116,7 +117,7 @@ sub epoch_cfg_set {
chomp(my $x = run_qx(\@cmd));
return if $x eq $v;
}
- run_die [@cmd, $v];
+ PublicInbox::CfgWr->new($f)->set('include.path', $v)->commit;
}
sub add_epoch {
diff --git a/script/public-inbox-init b/script/public-inbox-init
index b9e234e3..b959fd91 100755
--- a/script/public-inbox-init
+++ b/script/public-inbox-init
@@ -163,7 +163,8 @@ my $pi_config_tmp = $fh->filename;
close($fh);
my $pfx = "publicinbox.$name";
-my @x = (qw/git config/, "--file=$pi_config_tmp");
+require PublicInbox::CfgWr;
+my $cfgwr = PublicInbox::CfgWr->new($pi_config_tmp);
$inboxdir = PublicInbox::Config::rel2abs_collapsed($inboxdir);
die "`\\n' not allowed in `$inboxdir'\n" if index($inboxdir, "\n") >= 0;
@@ -225,15 +226,15 @@ require PublicInbox::Spawn;
PublicInbox::Spawn->import(qw(run_die));
for my $addr (grep { !$seen{lc $_} } @address) {
- run_die([@x, "--add", "$pfx.address", $addr]);
+ $cfgwr->add("$pfx.address", $addr);
}
-run_die([@x, "$pfx.url", $http_url]) if
+$cfgwr->set("$pfx.url", $http_url) if
(!$old_ibx || !grep(/\Q$http_url\E/, @{$old_ibx->{url} // []}));
-run_die([@x, "$pfx.inboxdir", $inboxdir]) if
+$cfgwr->set("$pfx.inboxdir", $inboxdir) if
(!$old_ibx || ($old_ibx->{inboxdir} ne $inboxdir));
-run_die([@x, "$pfx.indexlevel", $indexlevel]) if defined($indexlevel) &&
+$cfgwr->set("$pfx.indexlevel", $indexlevel) if defined($indexlevel) &&
(!$old_ibx || (($old_ibx->{indexlevel} // '') ne $indexlevel));
-run_die([@x, "$pfx.newsgroup", $ng]) if $ng ne '' &&
+$cfgwr->set("$pfx.newsgroup", $ng) if $ng ne '' &&
(!$old_ibx || (($old_ibx->{newsgroup} // '') ne $ng));
for my $kv (@c_extra) {
@@ -242,8 +243,9 @@ for my $kv (@c_extra) {
# but that's too new to depend on in 2021. Perl quotemeta
# seems compatible enough for POSIX ERE which git uses
my $re = '^'.quotemeta($v).'$';
- run_die([@x, qw(--replace-all), "$pfx.$k", $v, $re]);
+ $cfgwr->replace_all("$pfx.$k", $v, $re);
}
+$cfgwr->commit;
# needed for git prior to v2.1.0
chmod($perm & 07777, $pi_config_tmp);
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 7/8] rename Gcf2 -> Lg2 for general libgit2 use
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
` (5 preceding siblings ...)
2025-02-15 11:10 ` [PATCH 6/8] cfgwrite: new module to batch commit writes Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
2025-02-15 11:10 ` [PATCH 8/8] lg2: use cfgwr_commit to write to configs using libgit2 Eric Wong
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
We'll be exploring libgit2 for writing (and possibly reading) config
files, so the `gcf2' (git-cat-file-2) name isn't appropriate
anymore for the package name. We'll still keep `gcf2_' for
subroutine names related to git-cat-file-like behavior.
---
Documentation/public-inbox-tuning.pod | 2 +-
MANIFEST | 4 ++--
lib/PublicInbox/Gcf2Client.pm | 10 +++++-----
lib/PublicInbox/{Gcf2.pm => Lg2.pm} | 13 ++++++-------
lib/PublicInbox/SearchView.pm | 2 +-
lib/PublicInbox/Spawn.pm | 2 +-
lib/PublicInbox/TestCommon.pm | 2 +-
lib/PublicInbox/ViewVCS.pm | 2 +-
lib/PublicInbox/{gcf2_libgit2.h => lg2.h} | 2 +-
t/gcf2.t | 12 ++++++------
t/gcf2_client.t | 2 +-
11 files changed, 26 insertions(+), 27 deletions(-)
rename lib/PublicInbox/{Gcf2.pm => Lg2.pm} (92%)
rename lib/PublicInbox/{gcf2_libgit2.h => lg2.h} (98%)
diff --git a/Documentation/public-inbox-tuning.pod b/Documentation/public-inbox-tuning.pod
index b56c2b10..3f2c0861 100644
--- a/Documentation/public-inbox-tuning.pod
+++ b/Documentation/public-inbox-tuning.pod
@@ -71,7 +71,7 @@ public-inbox processes.
If libgit2 development files are installed and L<Inline::C>
is enabled (described above), per-inbox C<git cat-file --batch>
processes are replaced with a single L<perl(1)> process running
-C<PublicInbox::Gcf2::loop> in read-only daemons. libgit2 use
+C<PublicInbox::Lg2::gcf2_loop> in read-only daemons. libgit2 use
will be available in public-inbox 1.7.0+
More (optional) L<Inline::C> use will be introduced in the future
diff --git a/MANIFEST b/MANIFEST
index f0b42826..8f35c1ad 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -203,7 +203,6 @@ lib/PublicInbox/Filter/Mirror.pm
lib/PublicInbox/Filter/RubyLang.pm
lib/PublicInbox/Filter/SubjectTag.pm
lib/PublicInbox/Filter/Vger.pm
-lib/PublicInbox/Gcf2.pm
lib/PublicInbox/Gcf2Client.pm
lib/PublicInbox/GetlineResponse.pm
lib/PublicInbox/Git.pm
@@ -294,6 +293,7 @@ lib/PublicInbox/LeiUp.pm
lib/PublicInbox/LeiViewText.pm
lib/PublicInbox/LeiWatch.pm
lib/PublicInbox/LeiXSearch.pm
+lib/PublicInbox/Lg2.pm
lib/PublicInbox/Limiter.pm
lib/PublicInbox/Linkify.pm
lib/PublicInbox/Listener.pm
@@ -388,8 +388,8 @@ lib/PublicInbox/XapHelperCxx.pm
lib/PublicInbox/Xapcmd.pm
lib/PublicInbox/XhcMset.pm
lib/PublicInbox/XhcMsetIterator.pm
-lib/PublicInbox/gcf2_libgit2.h
lib/PublicInbox/khashl.h
+lib/PublicInbox/lg2.h
lib/PublicInbox/xap_helper.h
lib/PublicInbox/xh_cidx.h
lib/PublicInbox/xh_mset.h
diff --git a/lib/PublicInbox/Gcf2Client.pm b/lib/PublicInbox/Gcf2Client.pm
index 07ff7dcb..17bf34c7 100644
--- a/lib/PublicInbox/Gcf2Client.pm
+++ b/lib/PublicInbox/Gcf2Client.pm
@@ -1,12 +1,12 @@
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-# connects public-inbox processes to PublicInbox::Gcf2::loop()
+# connects public-inbox processes to PublicInbox::Lg2::gcf2_loop()
package PublicInbox::Gcf2Client;
use v5.12;
use parent qw(PublicInbox::DS);
use PublicInbox::Git;
-use PublicInbox::Gcf2; # fails if Inline::C or libgit2-dev isn't available
+use PublicInbox::Lg2; # fails if Inline::C or libgit2-dev isn't available
use PublicInbox::Spawn qw(spawn);
use Socket qw(AF_UNIX SOCK_STREAM);
use PublicInbox::Syscall qw(EPOLLIN);
@@ -14,10 +14,10 @@ use PublicInbox::IO;
use autodie qw(socketpair);
# fields:
-# sock => socket to Gcf2::loop
+# sock => socket to Lg2::gcf2_loop
# The rest of these fields are compatible with what PublicInbox::Git
# uses code-sharing
-# pid => PID of Gcf2::loop process
+# pid => PID of Lg2::gcf2_loop process
# pid.owner => process which spawned {pid}
# in => same as {sock}, for compatibility with PublicInbox::Git
# inflight => array (see PublicInbox::Git)
@@ -30,7 +30,7 @@ sub new {
$s1->blocking(0);
$opt->{0} = $opt->{1} = $s2;
my $cmd = [$^X, $^W ? ('-w') : (),
- qw[-MPublicInbox::Gcf2 -e PublicInbox::Gcf2::loop]];
+ qw[-MPublicInbox::Lg2 -e PublicInbox::Lg2::gcf2_loop]];
$self->{inflight} = [];
PublicInbox::IO::attach_pid($s1, spawn($cmd, $env, $opt),
\&PublicInbox::Git::gcf_drain, $self->{inflight});
diff --git a/lib/PublicInbox/Gcf2.pm b/lib/PublicInbox/Lg2.pm
similarity index 92%
rename from lib/PublicInbox/Gcf2.pm
rename to lib/PublicInbox/Lg2.pm
index acc2091c..0ee9b354 100644
--- a/lib/PublicInbox/Gcf2.pm
+++ b/lib/PublicInbox/Lg2.pm
@@ -1,9 +1,8 @@
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-# backend for a git-cat-file-workalike based on libgit2,
-# other libgit2 stuff may go here, too.
-package PublicInbox::Gcf2;
+# including backend for a git-cat-file-workalike based on libgit2,
+package PublicInbox::Lg2;
use v5.12;
use PublicInbox::Spawn qw(which run_qx); # may set PERL_INLINE_DIRECTORY
use Fcntl qw(SEEK_SET);
@@ -35,7 +34,7 @@ BEGIN {
die "E: libgit2 not installed: $err\n" if $?;
$vals->{$k} = $val;
}
- my $f = "$dir/gcf2_libgit2.h";
+ my $f = "$dir/lg2.h";
$c_src = PublicInbox::IO::try_cat $f or die "cat $f: $!";
# append pkg-config results to the source to ensure Inline::C
# can rebuild if there's changes (it doesn't seem to detect
@@ -62,7 +61,7 @@ EOM
seek($fh, 0, SEEK_SET);
my @msg = <$fh>;
truncate($fh, 0);
- die "Inline::C Gcf2 build failed:\n", $err, "\n", @msg;
+ die "Inline::C Lg2 build failed:\n", $err, "\n", @msg;
}
}
@@ -86,9 +85,9 @@ sub add_alt ($$) {
1;
}
-# Usage: $^X -MPublicInbox::Gcf2 -e PublicInbox::Gcf2::loop [EXPIRE-TIMEOUT]
+# Usage: $^X -MPublicInbox::Lg2 -e PublicInbox::Lg2::gcf2_loop [EXPIRE-TIMEOUT]
# (see lib/PublicInbox/Gcf2Client.pm)
-sub loop (;$) {
+sub gcf2_loop (;$) {
my $exp = $_[0] || $ARGV[0] || 60; # seconds
my $gcf2 = new();
my (%seen, $check_at);
diff --git a/lib/PublicInbox/SearchView.pm b/lib/PublicInbox/SearchView.pm
index 8ae422f9..8464ae1b 100644
--- a/lib/PublicInbox/SearchView.pm
+++ b/lib/PublicInbox/SearchView.pm
@@ -169,7 +169,7 @@ sub path2inc ($) {
if (my $short = $rmap_inc{$full}) {
return $short;
} elsif (!scalar(keys %rmap_inc) && -e $full) {
- # n.b. $INC{'PublicInbox::Gcf2'} is undef if libgit2-dev
+ # n.b. $INC{'PublicInbox::Lg2'} is undef if libgit2-dev
# doesn't exist
my $f;
%rmap_inc = map {;
diff --git a/lib/PublicInbox/Spawn.pm b/lib/PublicInbox/Spawn.pm
index d818af0f..f79623b7 100644
--- a/lib/PublicInbox/Spawn.pm
+++ b/lib/PublicInbox/Spawn.pm
@@ -330,7 +330,7 @@ EOM
$all_libc = undef;
}
}
- if (defined $all_libc) { # set for Gcf2
+ if (defined $all_libc) { # set for Lg2
$ENV{PERL_INLINE_DIRECTORY} = $inline_dir;
%RLIMITS = rlimit_map();
*send_cmd4 = sub ($$$$;$) {
diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm
index e1178149..a9967735 100644
--- a/lib/PublicInbox/TestCommon.pm
+++ b/lib/PublicInbox/TestCommon.pm
@@ -266,7 +266,7 @@ sub require_mods (@) {
eval "require $mod";
}
if ($@) {
- diag "require $mod: $@" if $mod =~ /Gcf2/;
+ diag "require $mod: $@" if $mod =~ /Lg2/;
push @need, $mod;
} elsif ($mod eq 'IO::Socket::SSL' &&
# old versions of IO::Socket::SSL aren't supported
diff --git a/lib/PublicInbox/ViewVCS.pm b/lib/PublicInbox/ViewVCS.pm
index e1e129b1..552e3241 100644
--- a/lib/PublicInbox/ViewVCS.pm
+++ b/lib/PublicInbox/ViewVCS.pm
@@ -129,7 +129,7 @@ sub cmt_title { # git->cat_async callback
sub do_cat_async {
my ($arg, $cb, @req) = @_;
- # favor git(1) over Gcf2 (libgit2) for SHA-256 support
+ # favor git(1) over Lg2 (libgit2) for SHA-256 support
my $ctx = ref $arg eq 'ARRAY' ? $arg->[0] : $arg;
$ctx->{git}->cat_async($_, $cb, $arg) for @req;
if ($ctx->{env}->{'pi-httpd.async'}) {
diff --git a/lib/PublicInbox/gcf2_libgit2.h b/lib/PublicInbox/lg2.h
similarity index 98%
rename from lib/PublicInbox/gcf2_libgit2.h
rename to lib/PublicInbox/lg2.h
index e1f0ef39..0b1a33e0 100644
--- a/lib/PublicInbox/gcf2_libgit2.h
+++ b/lib/PublicInbox/lg2.h
@@ -29,7 +29,7 @@ SV *new()
ref = newSViv((IV)odb);
self = newRV_noinc(ref);
- sv_bless(self, gv_stashpv("PublicInbox::Gcf2", GV_ADD));
+ sv_bless(self, gv_stashpv("PublicInbox::Lg2", GV_ADD));
SvREADONLY_on(ref);
return self;
diff --git a/t/gcf2.t b/t/gcf2.t
index 9f9e8e20..fc687058 100644
--- a/t/gcf2.t
+++ b/t/gcf2.t
@@ -10,14 +10,14 @@ use Fcntl qw(:seek);
use IO::Handle ();
use POSIX qw(_exit);
use Cwd qw(abs_path);
-require_mods('PublicInbox::Gcf2');
-use_ok 'PublicInbox::Gcf2';
+require_mods('PublicInbox::Lg2');
+use_ok 'PublicInbox::Lg2';
use PublicInbox::Syscall qw($F_SETPIPE_SZ);
use PublicInbox::Import;
my ($tmpdir, $for_destroy) = tmpdir();
-my $gcf2 = PublicInbox::Gcf2::new();
-is(ref($gcf2), 'PublicInbox::Gcf2', '::new works');
+my $gcf2 = PublicInbox::Lg2::new();
+is(ref($gcf2), 'PublicInbox::Lg2', '::new works');
my $COPYING = 'dba13ed2ddf783ee8118c6a581dbf75305f816a3';
open my $agpl, '<', 'COPYING' or BAIL_OUT "AGPL-3 missing: $!";
$agpl = do { local $/; <$agpl> };
@@ -102,7 +102,7 @@ SKIP: {
ok($gcf2->cat_oid(fileno($fh), $COPYING), 'cat_oid normal');
$ck_copying->('regular file');
- $gcf2 = PublicInbox::Gcf2::new();
+ $gcf2 = PublicInbox::Lg2::new();
$gcf2->add_alternate("$tmpdir/objects");
open $fh, '+>', undef or BAIL_OUT "open: $!";
ok($gcf2->cat_oid(fileno($fh), $COPYING), 'cat_oid alternate');
@@ -150,7 +150,7 @@ if ($nr) {
close $r;
my $broken = fileno($w);
for (1..$nr) {
- my $obj = PublicInbox::Gcf2::new();
+ my $obj = PublicInbox::Lg2::new();
if (defined($objdir)) {
$obj->add_alternate($objdir);
for (1..$cat) {
diff --git a/t/gcf2_client.t b/t/gcf2_client.t
index 33ee2c91..78921280 100644
--- a/t/gcf2_client.t
+++ b/t/gcf2_client.t
@@ -8,7 +8,7 @@ use autodie qw(open close);
use PublicInbox::Import;
use PublicInbox::DS;
-require_mods('PublicInbox::Gcf2');
+require_mods('PublicInbox::Lg2');
use_ok 'PublicInbox::Gcf2Client';
my ($tmpdir, $for_destroy) = tmpdir();
my $git_a = "$tmpdir/a.git";
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH 8/8] lg2: use cfgwr_commit to write to configs using libgit2
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
` (6 preceding siblings ...)
2025-02-15 11:10 ` [PATCH 7/8] rename Gcf2 -> Lg2 for general libgit2 use Eric Wong
@ 2025-02-15 11:10 ` Eric Wong
7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2025-02-15 11:10 UTC (permalink / raw)
To: meta
libgit2 allows us to avoid process spawning overhead when
writing to the config file. While the performance difference is
unlikely to matter in real-world use, it should improve
maintainability of tests by allowing us to write tests with
`public-inbox-init' with a smaller performance penalty (as
opposed to using `PublicInbox::IO::write_file' to setup
configs).
---
MANIFEST | 1 +
lib/PublicInbox/CfgWr.pm | 6 ++++
lib/PublicInbox/LEI.pm | 1 +
lib/PublicInbox/MultiGit.pm | 1 +
lib/PublicInbox/lg2.h | 58 ++++++++++++++++++++++++++++++++++-
t/lg2_cfg.t | 60 +++++++++++++++++++++++++++++++++++++
xt/check-run.t | 1 +
7 files changed, 127 insertions(+), 1 deletion(-)
create mode 100644 t/lg2_cfg.t
diff --git a/MANIFEST b/MANIFEST
index 8f35c1ad..d4535038 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -550,6 +550,7 @@ t/lei_saved_search.t
t/lei_store.t
t/lei_to_mail.t
t/lei_xsearch.t
+t/lg2_cfg.t
t/linkify.t
t/main-bin/spamc
t/mbox_lock.t
diff --git a/lib/PublicInbox/CfgWr.pm b/lib/PublicInbox/CfgWr.pm
index 2b3fd507..39f22e19 100644
--- a/lib/PublicInbox/CfgWr.pm
+++ b/lib/PublicInbox/CfgWr.pm
@@ -6,6 +6,10 @@ package PublicInbox::CfgWr;
use v5.12;
use PublicInbox::Git qw(git_exe);
use PublicInbox::Spawn qw(run_die run_wait);
+my $cfgwr_commit = eval {
+ require PublicInbox::Lg2;
+ PublicInbox::Lg2->can('cfgwr_commit');
+};
sub new {
my ($cls, $f) = @_;
@@ -38,6 +42,8 @@ sub unset_all {
sub commit {
my ($self, $opt) = @_;
+ my $todo = delete $self->{todo} // return;
+ return $cfgwr_commit->($self->{-f}, $todo) if $cfgwr_commit;
my @x = (git_exe, 'config', '-f', $self->{-f});
for my $c (@{delete $self->{todo} // []}) {
unshift @$c, @x;
diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index 1e8dc17f..94bac688 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -20,6 +20,7 @@ use Fcntl qw(SEEK_SET);
use PublicInbox::Config;
use PublicInbox::Syscall qw(EPOLLIN);
use PublicInbox::Spawn qw(run_wait popen_rd run_qx);
+eval { require PublicInbox::Lg2 }; # placate FindBin
use PublicInbox::Lock;
use PublicInbox::Eml;
use PublicInbox::Git qw(git_exe);
diff --git a/lib/PublicInbox/MultiGit.pm b/lib/PublicInbox/MultiGit.pm
index c99a11cb..1ef6bdb1 100644
--- a/lib/PublicInbox/MultiGit.pm
+++ b/lib/PublicInbox/MultiGit.pm
@@ -116,6 +116,7 @@ sub epoch_cfg_set {
if (-r $f) {
chomp(my $x = run_qx(\@cmd));
return if $x eq $v;
+ $? = 0; # don't influence _wq_done_wait in -clone
}
PublicInbox::CfgWr->new($f)->set('include.path', $v)->commit;
}
diff --git a/lib/PublicInbox/lg2.h b/lib/PublicInbox/lg2.h
index 0b1a33e0..fb771bd4 100644
--- a/lib/PublicInbox/lg2.h
+++ b/lib/PublicInbox/lg2.h
@@ -1,5 +1,5 @@
/*
- * 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>
*
* libgit2 for Inline::C
@@ -140,3 +140,59 @@ int cat_oid(SV *self, int fd, SV *oidsv)
return rc == GIT_OK;
}
+
+#define CFG_OP_EQ(op, cmd0, dlen) \
+ (dlen == (sizeof(op) - 1) && !memcmp(op, cmd0, sizeof(op) - 1))
+
+/* leaks on internal bugs: */
+#define FETCH_CSTR(var, ary, i) do { \
+ SV **s = av_fetch(ary, i, 0); \
+ if (!s) croak("BUG: " #var " = ary[%d] is NULL", i); \
+ var = SvPV_nolen(*s); \
+} while (0)
+
+void cfgwr_commit(const char *f, AV *todo)
+{
+ I32 i, todo_max = av_len(todo);
+ SV **sv;
+ git_config *cfg;
+ const git_error *e;
+ const char *cmd0, *name, *val, *re;
+ int rc = git_config_open_ondisk(&cfg, f);
+ STRLEN dlen;
+ AV *cmd;
+
+ for (i = 0; rc == GIT_OK && i <= todo_max; i++) {
+ sv = av_fetch(todo, i, 0);
+ /* leaks on internal bugs: */
+ if (!SvROK(*sv)) croak("BUG: not a ref");
+ cmd = (AV *)SvRV(*sv);
+ sv = av_fetch(cmd, 0, 0);
+ if (!sv) croak("BUG: cmd0 = $todo->[%d]->[0] is NULL", i);
+ cmd0 = SvPV(*sv, dlen);
+ if (CFG_OP_EQ("--add", cmd0, dlen)) {
+ FETCH_CSTR(name, cmd, 1);
+ FETCH_CSTR(val, cmd, 2);
+ rc = git_config_set_multivar(cfg, name, "$^", val);
+ } else if (CFG_OP_EQ("--unset-all", cmd0, dlen)) {
+ FETCH_CSTR(name, cmd, 1);
+ rc = git_config_delete_multivar(cfg, name, ".*");
+ if (rc == GIT_ENOTFOUND)
+ rc = GIT_OK;
+ } else if (CFG_OP_EQ("--replace-all", cmd0, dlen)) {
+ FETCH_CSTR(name, cmd, 1);
+ FETCH_CSTR(val, cmd, 2);
+ FETCH_CSTR(re, cmd, 3);
+ rc = git_config_set_multivar(cfg, name, re, val);
+ } else {
+ name = cmd0;
+ FETCH_CSTR(val, cmd, 1);
+ rc = git_config_set_string(cfg, name, val);
+ }
+ }
+ e = rc == GIT_OK ? NULL : giterr_last();
+ git_config_free(cfg);
+ if (rc != GIT_OK)
+ croak("config -f %s (%d %s)",
+ f, rc, e ? e->message : "unknown");
+}
diff --git a/t/lg2_cfg.t b/t/lg2_cfg.t
new file mode 100644
index 00000000..e8cdff4a
--- /dev/null
+++ b/t/lg2_cfg.t
@@ -0,0 +1,60 @@
+#!perl -w
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use PublicInbox::TestCommon;
+require_mods 'PublicInbox::Lg2';
+use PublicInbox::Git qw(git_exe);
+use PublicInbox::Spawn qw(run_die run_wait);
+use_ok 'PublicInbox::Lg2';
+use PublicInbox::Config;
+my $tmpdir = tmpdir;
+my $f = "$tmpdir/cfg";
+my $cfgwr_commit = $ENV{TEST_VALIDATE_GIT_BEHAVIOR} ? sub {
+ my ($file, $todo) = @_;
+ my @x = (git_exe, 'config', '-f', $file);
+ for my $c (@$todo) {
+ unshift @$c, @x;
+ if ($c->[scalar(@x)] eq '--unset-all') {
+ run_wait $c;
+ # ignore ret=5 if no matches (see git-config(1))
+ die "E: @$c \$?=$?" if ($? && ($? >> 8) != 5);
+ } else {
+ run_die $c;
+ }
+ }
+} : PublicInbox::Lg2->can('cfgwr_commit');
+
+my $cfg; # for explain() if needed
+my $get = sub {
+ my (@key) = @_;
+ $cfg = PublicInbox::Config->new($f);
+ @key > 1 ? (map { $_ => $cfg->{$_} } @key) : $cfg->{$key[0]};
+};
+
+$cfgwr_commit->($f, [ [ qw(a.b a) ] ]);
+is $get->('a.b'), 'a', 'config set works';
+
+$cfgwr_commit->($f, [ [ qw(--add a.b a) ] ]);
+is_xdeeply $get->('a.b'), [ qw(a a) ], 'config --add works to append';
+$cfgwr_commit->($f, [ [ qw(--add x.b c) ] ]);
+my %cfg = $get->('x.b', 'a.b');
+is $cfg{'x.b'}, 'c', 'config --add works to create';
+is_xdeeply $cfg{'a.b'}, [ qw(a a) ], 'config --add left existing alone';
+
+$cfgwr_commit->($f, [ [ qw(--unset-all a.b) ] ]);
+is $get->('a.b'), undef, 'config --unset-all works';
+
+$cfgwr_commit->($f, [ [ qw(--unset-all bogus.entry) ] ]);
+is $get->('bogus.entry'), undef, 'config --unset-all non-match';
+
+$cfgwr_commit->($f, [ [ qw(x.b d) ] ]);
+is $get->('x.b'), 'd', 'set clobbers existing value';
+
+eval { $cfgwr_commit->($f, []) };
+ok !$@, 'no exception or errors on empty todo';
+
+$cfgwr_commit->($f, [ [ qw(x.b d) ], [ qw(--add x.b e) ],
+ [ qw(--add x.b f) ] ]);
+is_xdeeply $get->('x.b'), [ qw(d e f) ], 'multiple changes chained';
+
+done_testing;
diff --git a/xt/check-run.t b/xt/check-run.t
index d12b925d..162c1acd 100755
--- a/xt/check-run.t
+++ b/xt/check-run.t
@@ -14,6 +14,7 @@ use v5.12;
use IO::Handle; # ->autoflush
use PublicInbox::TestCommon;
use PublicInbox::Spawn;
+eval { require PublicInbox::Lg2 }; # placate FindBin
use PublicInbox::DS; # already loaded by Spawn via PublicInbox::IO
use Getopt::Long qw(:config gnu_getopt no_ignore_case auto_abbrev);
use Errno qw(EINTR);
^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-02-15 11:10 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-15 11:10 [PATCH 0/8] use libgit2 for writing configs Eric Wong
2025-02-15 11:10 ` [PATCH 1/8] favor run_wait() over CORE::system() Eric Wong
2025-02-15 11:10 ` [PATCH 2/8] edit: use autodie and favor flush over autoflush Eric Wong
2025-02-15 11:10 ` [PATCH 3/8] import: clobber $? if global init.defaultBranch fails Eric Wong
2025-02-15 11:10 ` [PATCH 4/8] test_common: key2sub: detect syntax errors early Eric Wong
2025-02-15 11:10 ` [PATCH 5/8] t/watch_v1_v2_mix_no_modify: load PublicInbox::Config Eric Wong
2025-02-15 11:10 ` [PATCH 6/8] cfgwrite: new module to batch commit writes Eric Wong
2025-02-15 11:10 ` [PATCH 7/8] rename Gcf2 -> Lg2 for general libgit2 use Eric Wong
2025-02-15 11:10 ` [PATCH 8/8] lg2: use cfgwr_commit to write to configs using libgit2 Eric Wong
Code repositories for project(s) associated with this public inbox
https://80x24.org/public-inbox.git
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).