X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FGit.pm;h=96627daa19735106f31308be686d8c37961661b4;hb=44fbd4169f7434442dbf57851232edafa6fd0e46;hp=37342d7d10a473c77498596a2c7a8c8aa3f573e0;hpb=b04257f82cb4890a022a4d18ff15bcde939ba2ae;p=public-inbox.git diff --git a/lib/PublicInbox/Git.pm b/lib/PublicInbox/Git.pm index 37342d7d..96627daa 100644 --- a/lib/PublicInbox/Git.pm +++ b/lib/PublicInbox/Git.pm @@ -1,4 +1,4 @@ -# Copyright (C) 2014-2021 all contributors +# Copyright (C) all contributors # License: GPLv2 or later # # Used to read files from a git repository without excessive forking. @@ -28,8 +28,10 @@ our $in_cleanup; our $RDTIMEO = 60_000; # milliseconds our $async_warn; # true in read-only daemons -use constant MAX_INFLIGHT => (POSIX::PIPE_BUF * 3) / - 65; # SHA-256 hex size + "\n" in preparation for git using non-SHA1 +# 512: POSIX PIPE_BUF minimum (see pipe(7)) +# 3: @$inflight is flattened [ $OID, $cb, $arg ] +# 65: SHA-256 hex size + "\n" in preparation for git using non-SHA1 +use constant MAX_INFLIGHT => 512 * 3 / 65; my %GIT_ESC = ( a => "\a", @@ -66,12 +68,12 @@ sub new { $git_dir =~ tr!/!/!s; $git_dir =~ s!/*\z!!s; # may contain {-tmp} field for File::Temp::Dir - bless { git_dir => $git_dir, alt_st => '', -git_path => {} }, $class + bless { git_dir => $git_dir }, $class } sub git_path ($$) { my ($self, $path) = @_; - $self->{-git_path}->{$path} ||= do { + $self->{-git_path}->{$path} //= do { local $/ = "\n"; chomp(my $str = $self->qx(qw(rev-parse --git-path), $path)); @@ -90,10 +92,17 @@ sub alternates_changed { # can't rely on 'q' on some 32-bit builds, but `d' works my $st = pack('dd', $st[10], $st[7]); # 10: ctime, 7: size - return 0 if $self->{alt_st} eq $st; + return 0 if ($self->{alt_st} // '') eq $st; $self->{alt_st} = $st; # always a true value } +sub object_format { + $_[0]->{object_format} //= do { + my $fmt = $_[0]->qx(qw(config extensions.objectformat)); + $fmt eq "sha256\n" ? \'sha256' : \undef; + } +} + sub last_check_err { my ($self) = @_; my $fh = $self->{err_c} or return; @@ -114,7 +123,7 @@ sub _bidi_pipe { return; } pipe(my ($out_r, $out_w)) or $self->fail("pipe failed: $!"); - my $rdr = { 0 => $out_r }; + my $rdr = { 0 => $out_r, pgid => 0 }; my $gd = $self->{git_dir}; if ($gd =~ s!/([^/]+/[^/]+)\z!/!) { $rdr->{-C} = $gd; @@ -136,6 +145,7 @@ sub _bidi_pipe { fcntl($out_w, 1031, 4096); fcntl($in_r, 1031, 4096) if $batch eq '--batch-check'; } + $out_w->blocking(0); $self->{$out} = $out_w; $self->{$in} = $in_r; } @@ -158,7 +168,8 @@ sub my_read ($$$) { return; # unrecoverable error } } - \substr($$rbuf, 0, $len, ''); + my $no_pad = substr($$rbuf, 0, $len, ''); + \$no_pad; } sub my_readline ($$) { @@ -193,7 +204,9 @@ sub cat_async_retry ($$) { for (my $i = 0; $i < @$inflight; $i += 3) { $buf .= "$inflight->[$i]\n"; } + $self->{out}->blocking(1); # brand new pipe, should never block print { $self->{out} } $buf or $self->fail("write error: $!"); + $self->{out}->blocking(0); my $req = shift @$inflight; unshift(@$inflight, \$req); # \$ref to indicate retried @@ -295,13 +308,26 @@ sub check_async_begin ($) { $self->{inflight_c} = []; } +sub write_all { + my ($self, $out, $buf, $read_step, $inflight) = @_; + $read_step->($self, $inflight) while @$inflight >= MAX_INFLIGHT; + do { + my $w = syswrite($out, $buf); + if (defined $w) { + return if $w == length($buf); + substr($buf, 0, $w, ''); # sv_chop + } elsif ($! != EAGAIN) { + $self->fail("write: $!"); + } + $read_step->($self, $inflight); + } while (1); +} + sub check_async ($$$$) { my ($self, $oid, $cb, $arg) = @_; my $inflight_c = $self->{inflight_c} // check_async_begin($self); - while (scalar(@$inflight_c) >= MAX_INFLIGHT) { - check_async_step($self, $inflight_c); - } - print { $self->{out_c} } $oid, "\n" or $self->fail("write error: $!"); + write_all($self, $self->{out_c}, $oid."\n", + \&check_async_step, $inflight_c); push(@$inflight_c, $oid, $cb, $arg); } @@ -341,7 +367,7 @@ sub async_abort ($) { while (scalar(@{$self->{inflight_c} // []}) || scalar(@{$self->{inflight} // []})) { for my $c ('', '_c') { - my $q = $self->{"inflight$c"}; + my $q = $self->{"inflight$c"} or next; while (@$q) { my ($req, $cb, $arg) = splice(@$q, 0, 3); $req = $$req if ref($req); @@ -418,6 +444,7 @@ sub cleanup { scalar(@{$self->{inflight} // []})); local $in_cleanup = 1; delete $self->{async_cat}; + delete $self->{async_chk}; async_wait_all($self); delete $self->{inflight}; delete $self->{inflight_c}; @@ -442,30 +469,38 @@ sub packed_bytes { sub DESTROY { cleanup(@_) } sub local_nick ($) { - my ($self) = @_; - my $ret = '???'; # don't show full FS path, basename should be OK: - if ($self->{git_dir} =~ m!/([^/]+)(?:/*\.git/*)?\z!) { - $ret = "$1.git"; - } - wantarray ? ($ret) : $ret; + $_[0]->{nick} // ($_[0]->{git_dir} =~ m!/([^/]+?)(?:/*\.git/*)?\z! ? + "$1.git" : undef); } sub host_prefix_url ($$) { my ($env, $url) = @_; return $url if index($url, '//') >= 0; - my $scheme = $env->{'psgi.url_scheme'}; my $host_port = $env->{HTTP_HOST} // "$env->{SERVER_NAME}:$env->{SERVER_PORT}"; - "$scheme://$host_port". ($env->{SCRIPT_NAME} || '/') . $url; + my $sn = $env->{SCRIPT_NAME} // ''; + "$env->{'psgi.url_scheme'}://$host_port$sn/$url"; } +sub base_url { # for coderepos, PSGI-only + my ($self, $env) = @_; # env - PSGI env + my $nick = $self->{nick} // return undef; + my $url = host_prefix_url($env, ''); + # for mount in Plack::Builder + $url .= '/' if substr($url, -1, 1) ne '/'; + $url . $nick . '/'; +} + +sub isrch {} # TODO + sub pub_urls { my ($self, $env) = @_; if (my $urls = $self->{cgit_url}) { - return map { host_prefix_url($env, $_) } @$urls; + map { host_prefix_url($env, $_) } @$urls; + } else { + (base_url($self, $env) // '???'); } - local_nick($self); } sub cat_async_begin { @@ -479,10 +514,7 @@ sub cat_async_begin { sub cat_async ($$$;$) { my ($self, $oid, $cb, $arg) = @_; my $inflight = $self->{inflight} // cat_async_begin($self); - while (scalar(@$inflight) >= MAX_INFLIGHT) { - cat_async_step($self, $inflight); - } - print { $self->{out} } $oid, "\n" or $self->fail("write error: $!"); + write_all($self, $self->{out}, $oid."\n", \&cat_async_step, $inflight); push(@$inflight, $oid, $cb, $arg); } @@ -495,6 +527,33 @@ sub modified ($) { (split(/ /, <$fh> // time))[0] + 0; # integerize for JSON } +sub try_cat { + my ($path) = @_; + open(my $fh, '<', $path) or return ''; + local $/; + <$fh> // ''; +} + +sub cat_desc ($) { + my $desc = try_cat($_[0]); + chomp $desc; + utf8::decode($desc); + $desc =~ s/\s+/ /smg; + $desc eq '' ? undef : $desc; +} + +sub description { + cat_desc("$_[0]->{git_dir}/description") // 'Unnamed repository'; +} + +sub cloneurl { + my ($self, $env) = @_; + $self->{cloneurl} // do { + my @urls = split(/\s+/s, try_cat("$self->{git_dir}/cloneurl")); + scalar(@urls) ? ($self->{cloneurl} = \@urls) : undef; + } // [ substr(base_url($self, $env), 0, -1) ]; +} + # for grokmirror, which doesn't read gitweb.description # templates/hooks--update.sample and git-multimail in git.git # only match "Unnamed repository", not the full contents of @@ -517,14 +576,8 @@ sub manifest_entry { chomp(my $owner = $self->qx('config', 'gitweb.owner')); utf8::decode($owner); $ent->{owner} = $owner eq '' ? undef : $owner; - my $desc = ''; - if (open($fh, '<', "$git_dir/description")) { - local $/ = "\n"; - chomp($desc = <$fh>); - utf8::decode($desc); - } - $desc = 'Unnamed repository' if $desc eq ''; - if (defined $epoch && $desc =~ /\AUnnamed repository/) { + my $desc = description($self); + if (defined $epoch && index($desc, 'Unnamed repository') == 0) { $desc = "$default_desc [epoch $epoch]"; } $ent->{description} = $desc;