1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
# common git alternates + all.git||ALL.git management code
package PublicInbox::MultiGit;
use strict;
use v5.10.1;
use PublicInbox::Spawn qw(run_die);
use PublicInbox::Import;
use File::Temp 0.19;
use List::Util qw(max);
sub new {
my ($cls, $topdir, $all, $epfx) = @_;
bless {
topdir => $topdir, # inboxdir || extindex.*.topdir
all => $all, # all.git or ALL.git
epfx => $epfx, # "git" (inbox) or "local" (lei/store)
}, $cls;
}
sub read_alternates {
my ($self, $moderef, $prune) = @_;
my $objpfx = "$self->{topdir}/$self->{all}/objects/";
my $f = "${objpfx}info/alternates";
my %alt; # line => score
my %seen; # $st_dev\0$st_ino => count
my $other = 0;
if (open(my $fh, '<', $f)) {
my $is_edir = defined($self->{epfx}) ?
qr!\A\Q../../$self->{epfx}\E/([0-9]+)\.git/objects\z! :
undef;
$$moderef = (stat($fh))[2] & 07777;
for my $rel (split(/^/m, do { local $/; <$fh> })) {
chomp(my $dir = $rel);
my $score;
if (defined($is_edir) && $dir =~ $is_edir) {
$score = $1 + 0;
substr($dir, 0, 0) = $objpfx;
} else { # absolute paths, if any (extindex)
$score = --$other;
}
if (my @st = stat($dir)) {
next if $seen{"$st[0]\0$st[1]"}++;
$alt{$rel} = $score;
} else {
warn "W: stat($dir) failed: $! ($f)";
if ($prune) {
++$$prune;
} else {
$alt{$rel} = $score;
}
}
}
} elsif (!$!{ENOENT}) {
die "E: open($f): $!";
}
(\%alt, \%seen);
}
sub epoch_dir { "$_[0]->{topdir}/$_[0]->{epfx}" }
sub write_alternates {
my ($self, $mode, $alt, @new) = @_;
my $all_dir = "$self->{topdir}/$self->{all}";
PublicInbox::Import::init_bare($all_dir);
my $out = join('', sort { $alt->{$b} <=> $alt->{$a} } keys %$alt);
my $info_dir = "$all_dir/objects/info";
my $fh = File::Temp->new(TEMPLATE => 'alt-XXXX', DIR => $info_dir);
my $f = $fh->filename;
print $fh $out, @new or die "print($f): $!";
chmod($mode, $fh) or die "fchmod($f): $!";
close $fh or die "close($f): $!";
my $fn = "$info_dir/alternates";
rename($f, $fn) or die "rename($f, $fn): $!";
$fh->unlink_on_destroy(0);
}
# returns true if new epochs exist
sub merge_epochs {
my ($self, $alt, $seen) = @_;
my $epoch_dir = epoch_dir($self);
if (opendir my $dh, $epoch_dir) {
my $has_new;
for my $bn (grep(/\A[0-9]+\.git\z/, readdir($dh))) {
my $rel = "../../$self->{epfx}/$bn/objects\n";
next if exists($alt->{$rel});
if (my @st = stat("$epoch_dir/$bn/objects")) {
next if $seen->{"$st[0]\0$st[1]"}++;
$alt->{$rel} = substr($bn, 0, -4) + 0;
$has_new = 1;
} else {
warn "E: stat($epoch_dir/$bn/objects): $!";
}
}
$has_new;
} else {
$!{ENOENT} ? undef : die "opendir($epoch_dir): $!";
}
}
sub fill_alternates {
my ($self) = @_;
my ($alt, $seen) = read_alternates($self, \(my $mode = 0644));
merge_epochs($self, $alt, $seen) and
write_alternates($self, $mode, $alt);
}
sub epoch_cfg_set {
my ($self, $epoch_nr) = @_;
run_die([qw(git config -f), epoch_dir($self)."/$epoch_nr.git/config",
'include.path', "../../$self->{all}/config" ]);
}
sub add_epoch {
my ($self, $epoch_nr) = @_;
my $git_dir = epoch_dir($self)."/$epoch_nr.git";
my $f = "$git_dir/config";
my $existing = -f $f;
PublicInbox::Import::init_bare($git_dir);
epoch_cfg_set($self, $epoch_nr) unless $existing;
fill_alternates($self);
$git_dir;
}
sub git_epochs {
my ($self) = @_;
if (opendir(my $dh, epoch_dir($self))) {
my @epochs = map {
substr($_, 0, -4) + 0; # drop ".git" suffix
} grep(/\A[0-9]+\.git\z/, readdir($dh));
wantarray ? sort { $b <=> $a } @epochs : (max(@epochs) // 0);
} elsif ($!{ENOENT}) {
wantarray ? () : 0;
} else {
die(epoch_dir($self).": $!");
}
}
1;
|