From 375b3ccfd3ca978281cb3869b62fc91eebc60d6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 15 Sep 2021 11:26:17 +0000 Subject: multi_git: hoist out common epoch/alternates handling IMHO, this greatly improves code sharing and organization between v2, extindex, and lei/store. Common git-related logic for these is lightly-refactored and easier to reason about. The impetus for this big change was to ensure inboxes created+managed by public-inbox-{clone,fetch} could have alternates and configs setup properly without depending on SQLite (via V2Writable). This change does that while making old code shorter and better factored. --- lib/PublicInbox/MultiGit.pm | 136 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lib/PublicInbox/MultiGit.pm (limited to 'lib/PublicInbox/MultiGit.pm') diff --git a/lib/PublicInbox/MultiGit.pm b/lib/PublicInbox/MultiGit.pm new file mode 100644 index 00000000..91d7998a --- /dev/null +++ b/lib/PublicInbox/MultiGit.pm @@ -0,0 +1,136 @@ +# Copyright (C) all contributors +# License: AGPL-3.0+ + +# 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)"; + $alt{$rel} = $score unless $prune; + } + } + } 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; -- cgit v1.2.3-24-ge0c7