about summary refs log tree commit homepage
path: root/lib/PublicInbox/LEI.pm
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2023-09-24 05:42:13 +0000
committerEric Wong <e@80x24.org>2023-09-24 18:56:08 +0000
commitf170d220f8765e952c9a102dd35eb694810739df (patch)
treeab3baa8c8c2775f2b74da1f871980603da19586d /lib/PublicInbox/LEI.pm
parentb4b22d40c52584836d7c0c97a513dfac1c12fbee (diff)
downloadpublic-inbox-f170d220f8765e952c9a102dd35eb694810739df.tar.gz
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.
Diffstat (limited to 'lib/PublicInbox/LEI.pm')
-rw-r--r--lib/PublicInbox/LEI.pm95
1 files changed, 49 insertions, 46 deletions
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;