about summary refs log tree commit homepage
path: root/lib/PublicInbox/Admin.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/Admin.pm')
-rw-r--r--lib/PublicInbox/Admin.pm142
1 files changed, 76 insertions, 66 deletions
diff --git a/lib/PublicInbox/Admin.pm b/lib/PublicInbox/Admin.pm
index dcf17cf5..a1b1fc07 100644
--- a/lib/PublicInbox/Admin.pm
+++ b/lib/PublicInbox/Admin.pm
@@ -1,15 +1,15 @@
-# 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>
 
 # common stuff for administrative command-line tools
 # Unstable internal API
 package PublicInbox::Admin;
-use strict;
+use v5.12;
 use parent qw(Exporter);
-our @EXPORT_OK = qw(setup_signals);
+our @EXPORT_OK = qw(setup_signals fmt_localtime);
 use PublicInbox::Config;
 use PublicInbox::Inbox;
-use PublicInbox::Spawn qw(popen_rd);
+use PublicInbox::Spawn qw(run_qx);
 use PublicInbox::Eml;
 *rel2abs_collapsed = \&PublicInbox::Config::rel2abs_collapsed;
 
@@ -28,12 +28,12 @@ sub setup_signals {
         };
 }
 
-sub resolve_eidxdir {
-        my ($cd) = @_;
+sub resolve_any_idxdir ($$) {
+        my ($cd, $lock_bn) = @_;
         my $try = $cd // '.';
         my $root_dev_ino;
-        while (1) { # favor v2, first
-                if (-f "$try/ei.lock") {
+        while (1) {
+                if (-f "$try/$lock_bn") { # inbox.lock, ei.lock, cidx.lock
                         return rel2abs_collapsed($try);
                 } elsif (-d $try) {
                         my @try = stat _;
@@ -49,61 +49,47 @@ sub resolve_eidxdir {
         }
 }
 
+sub resolve_eidxdir ($) { resolve_any_idxdir($_[0], 'ei.lock') }
+sub resolve_cidxdir ($) { resolve_any_idxdir($_[0], 'cidx.lock') }
+
 sub resolve_inboxdir {
         my ($cd, $ver) = @_;
-        my $try = $cd // '.';
-        my $root_dev_ino;
-        while (1) { # favor v2, first
-                if (-f "$try/inbox.lock") {
-                        $$ver = 2 if $ver;
-                        return rel2abs_collapsed($try);
-                } elsif (-d $try) {
-                        my @try = stat _;
-                        $root_dev_ino //= do {
-                                my @root = stat('/') or die "stat /: $!\n";
-                                "$root[0]\0$root[1]";
-                        };
-                        last if "$try[0]\0$try[1]" eq $root_dev_ino;
-                        $try .= '/..'; # continue, cd up
-                } else {
-                        die "`$try' is not a directory\n";
-                }
-        }
-        # try v1 bare git dirs
-        my $cmd = [ qw(git rev-parse --git-dir) ];
-        my $fh = popen_rd($cmd, undef, {-C => $cd});
-        my $dir = do { local $/; <$fh> };
-        close $fh or die "error in @$cmd (cwd:${\($cd // '.')}): $!\n";
-        chomp $dir;
-        $$ver = 1 if $ver;
-        rel2abs_collapsed($dir eq '.' ? ($cd // $dir) : $dir);
+        my $dir;
+        if (defined($dir = resolve_any_idxdir($cd, 'inbox.lock'))) { # try v2
+                $$ver = 2 if $ver;
+        } elsif (defined($dir = resolve_git_dir($cd))) { # try v1
+                $$ver = 1 if $ver;
+        } # else: not an inbox at all
+        $dir;
 }
 
-# for unconfigured inboxes
-sub detect_indexlevel ($) {
-        my ($ibx) = @_;
-
-        my $over = $ibx->over;
-        my $srch = $ibx->search;
-        delete @$ibx{qw(over search)}; # don't leave open FDs lying around
-
-        # brand new or never before indexed inboxes default to full
-        return 'full' unless $over;
-        my $l = 'basic';
-        return $l unless $srch;
-        if (my $xdb = $srch->xdb) {
-                $l = 'full';
-                my $m = $xdb->get_metadata('indexlevel');
-                if ($m eq 'medium') {
-                        $l = $m;
-                } elsif ($m ne '') {
-                        warn <<"";
-$ibx->{inboxdir} has unexpected indexlevel in Xapian: $m
+sub valid_pwd {
+        my $pwd = $ENV{PWD} // return;
+        my @st_pwd = stat $pwd or return;
+        my @st_cwd = stat '.' or die "stat(.): $!";
+        "@st_pwd[1,0]" eq "@st_cwd[1,0]" ? $pwd : undef;
+}
 
-                }
-                $ibx->{-skip_docdata} = 1 if $xdb->get_metadata('skip_docdata');
+sub resolve_git_dir {
+        my ($cd) = @_; # cd may be `undef' for cwd
+        # try v1 bare git dirs
+        my $pwd = valid_pwd();
+        my $env;
+        defined($pwd) && substr($cd // '/', 0, 1) ne '/' and
+                $env->{PWD} = "$pwd/$cd";
+        my $cmd = [ qw(git rev-parse --git-dir) ];
+        my $dir = run_qx($cmd, $env, { -C => $cd });
+        die "error in @$cmd (cwd:${\($cd // '.')}): $?\n" if $?;
+        chomp $dir;
+        # --absolute-git-dir requires git v2.13.0+, and we want to
+        # respect symlinks when $ENV{PWD} if $ENV{PWD} ne abs_path('.')
+        # since we store absolute GIT_DIR paths in cindex.
+        if (substr($dir, 0, 1) ne '/') {
+                substr($cd // '/', 0, 1) eq '/' or
+                        $cd = File::Spec->rel2abs($cd, $pwd);
+                $dir = rel2abs_collapsed($dir, $cd);
         }
-        $l;
+        $dir;
 }
 
 sub unconfigured_ibx ($$) {
@@ -128,12 +114,22 @@ sub resolve_inboxes ($;$$) {
                 $cfg or die "--all specified, but $cfgfile not readable\n";
                 @$argv and die "--all specified, but directories specified\n";
         }
-        my (@old, @ibxs, @eidx);
+        my (@old, @ibxs, @eidx, @cidx);
+        if ($opt->{-cidx_ok}) {
+                require PublicInbox::CodeSearchIdx;
+                @$argv = grep {
+                        if (defined(my $d = resolve_cidxdir($_))) {
+                                push @cidx, PublicInbox::CodeSearchIdx->new(
+                                                        $d, $opt);
+                                undef;
+                        } else {
+                                1;
+                        }
+                } @$argv;
+        }
         if ($opt->{-eidx_ok}) {
                 require PublicInbox::ExtSearchIdx;
-                my $i = -1;
                 @$argv = grep {
-                        $i++;
                         if (defined(my $ei = resolve_eidxdir($_))) {
                                 $ei = PublicInbox::ExtSearchIdx->new($ei, $opt);
                                 push @eidx, $ei;
@@ -155,6 +151,7 @@ sub resolve_inboxes ($;$$) {
                                 warn "W: $ibx->{name} $ibx->{inboxdir}: $!\n";
                         }
                 });
+                # TODO: no way to configure cindex in config file, yet
         } else { # directories specified on the command-line
                 my @dirs = @$argv;
                 push @dirs, '.' if !@dirs && $opt->{-use_cwd};
@@ -195,7 +192,8 @@ sub resolve_inboxes ($;$$) {
                 die "-V$min_ver inboxes not supported by $0\n\t",
                     join("\n\t", @old), "\n";
         }
-        $opt->{-eidx_ok} ? (\@ibxs, \@eidx) : @ibxs;
+        ($opt->{-eidx_ok} || $opt->{-cidx_ok}) ? (\@ibxs, \@eidx, \@cidx)
+                                                : @ibxs;
 }
 
 my @base_mod = ();
@@ -203,13 +201,13 @@ my @over_mod = qw(DBD::SQLite DBI);
 my %mod_groups = (
         -index => [ @base_mod, @over_mod ],
         -base => \@base_mod,
-        -search => [ @base_mod, @over_mod, 'Search::Xapian' ],
+        -search => [ @base_mod, @over_mod, 'Xapian' ],
 );
 
 sub scan_ibx_modules ($$) {
         my ($mods, $ibx) = @_;
         if (!$ibx->{indexlevel} || $ibx->{indexlevel} ne 'basic') {
-                $mods->{'Search::Xapian'} = 1;
+                $mods->{'Xapian'} = 1;
         } else {
                 $mods->{$_} = 1 foreach @over_mod;
         }
@@ -221,10 +219,10 @@ sub check_require {
         while (my $mod = shift @mods) {
                 if (my $groups = $mod_groups{$mod}) {
                         push @mods, @$groups;
-                } elsif ($mod eq 'Search::Xapian') {
+                } elsif ($mod eq 'Xapian') {
                         require PublicInbox::Search;
                         PublicInbox::Search::load_xapian() or
-                                $err->{'Search::Xapian || Xapian'} = $@;
+                                $err->{'Xapian || Search::Xapian'} = $@;
                 } else {
                         eval "require $mod";
                         $err->{$mod} = $@ if $@;
@@ -320,7 +318,7 @@ sub progress_prepare ($;$) {
         } else {
                 $opt->{verbose} ||= 1;
                 $dst //= *STDERR{GLOB};
-                $opt->{-progress} = sub { print $dst @_ };
+                $opt->{-progress} = sub { print $dst '# ', @_ };
         }
 }
 
@@ -368,6 +366,10 @@ sub index_prepare ($$) {
                                         or die "`$git_key=$s' not boolean\n";
                 $opt->{$k} = $v;
         }
+        for my $k (qw(since until)) {
+                my $v = $opt->{$k} // next;
+                $opt->{reindex} or die "--$k=$v requires --reindex\n";
+        }
         $env;
 }
 
@@ -379,4 +381,12 @@ sub do_chdir ($) {
         }
 }
 
+sub fmt_localtime ($) {
+        require POSIX;
+        my @lt = localtime $_[0];
+        my (undef, $M, $H, $d, $m, $Y) = @lt;
+        sprintf('%u-%02u-%02u % 2u:%02u ', $Y + 1900, $m + 1, $d, $H, $M)
+                .POSIX::strftime('%z', @lt);
+}
+
 1;