From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-2.6 required=3.0 tests=ALL_TRUSTED,AWL,BAYES_00, RP_MATCHES_RCVD,URIBL_BLOCKED shortcircuit=no autolearn=unavailable version=3.3.2 X-Original-To: spew@80x24.org Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 3E6A21F45E for ; Tue, 22 Dec 2015 00:15:33 +0000 (UTC) From: Eric Wong To: spew@80x24.org Subject: [PATCH] wip Date: Tue, 22 Dec 2015 00:15:33 +0000 Message-Id: <20151222001533.6199-1-e@80x24.org> List-Id: --- lib/PublicInbox/RepoBrowse.pm | 9 ++- lib/PublicInbox/RepoBrowseCommit.pm | 2 +- lib/PublicInbox/RepoBrowseTree.pm | 146 ++++++++++++++++++++++++++++++++++++ lib/PublicInbox/Spawn.pm | 19 +++++ 4 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 lib/PublicInbox/RepoBrowseTree.pm create mode 100644 lib/PublicInbox/Spawn.pm diff --git a/lib/PublicInbox/RepoBrowse.pm b/lib/PublicInbox/RepoBrowse.pm index 5865d65..0b3197e 100644 --- a/lib/PublicInbox/RepoBrowse.pm +++ b/lib/PublicInbox/RepoBrowse.pm @@ -23,7 +23,7 @@ use warnings; use URI::Escape qw(uri_escape_utf8 uri_unescape); use PublicInbox::RepoConfig; -my %CMD = map { lc($_) => $_ } qw(Log Commit); +my %CMD = map { lc($_) => $_ } qw(Log Commit Tree); sub new { my ($class, $file) = @_; @@ -39,9 +39,10 @@ sub run { # URL syntax: / repo [ / cmd [ / path ] ] # cmd: log | commit | diff | tree | view | blob | snapshot - # repo and path may both contain '/' + # repo and path (@extra) may both contain '/' my $rconfig = $self->{rconfig}; - my (undef, $repo_path, @extra) = split(m{/+}, $cgi->path_info, -1); + my $path_info = uri_unescape($cgi->path_info); + my (undef, $repo_path, @extra) = split(m{/+}, $path_info, -1); return r404() unless $repo_path; my $repo_info; @@ -53,7 +54,7 @@ sub run { my $req = { repo_info => $repo_info, - path => \@extra, + extra => \@extra, # path cgi => $cgi, rconfig => $rconfig, }; diff --git a/lib/PublicInbox/RepoBrowseCommit.pm b/lib/PublicInbox/RepoBrowseCommit.pm index 67f63a3..43f637a 100644 --- a/lib/PublicInbox/RepoBrowseCommit.pm +++ b/lib/PublicInbox/RepoBrowseCommit.pm @@ -110,7 +110,7 @@ sub call_git { sub git_blob_hrefs { my ($rel, @ids) = @_; - map { " +# License: AGPL-3.0+ +package PublicInbox::RepoBrowseTree; +use strict; +use warnings; +use base qw(PublicInbox::RepoBrowseBase); +use PublicInbox::Spawn qw(git); +use PublicInbox::GitCatFile; +use URI::Escape qw(uri_escape_utf8); +use Encode qw/find_encoding/; +my $enc_utf8 = find_encoding('UTF-8'); + +my %GIT_MODE = ( + '100644' => ' ', # blob + '100755' => 'x', # executable blob + '040000' => 'd', # tree + '120000' => 'l', # symlink + '160000' => 'g', # commit (gitlink) +); + +sub git_tree_stream { + my ($self, $req, $res) = @_; # res: Plack callback + my $repo_info = $req->{repo_info}; + my $dir = $repo_info->{path}; + my @extra = @{$req->{extra}}; + my $tslash; + if (@extra && $extra[-1] eq '') { # no trailing slash + pop @extra; + $tslash = 1; + } + my $tree_path = join('/', @extra); + my $q = PublicInbox::RepoBrowseQuery->new($req->{cgi}); + my $id = $q->{id}; + $id eq '' and $id = 'HEAD'; + + my $obj = "$id:$tree_path"; + my $git = $repo_info->{git} ||= PublicInbox::GitCatFile->new($dir); + my ($hex, $type, $size) = $git->check($obj); + + if (!defined($type) || ($type ne 'blob' && $type ne 'tree')) { + return $res->([404, ['Content-Type'=>'text/html'], + ['Not Found']]); + } + + my $fh = $res->([200, ['Content-Type'=>'text/html; charset=UTF-8']]); + $fh->write(''. + PublicInbox::Hval::PRE); + + if ($type eq 'tree') { + tree_show($fh, $git, $dir, $hex, $q, \@extra, $tslash); + } elsif ($type eq 'blob') { + blob_show($fh, $git, $hex); + } + $fh->write(''); + $fh->close; +} + +sub call_git { + my ($self, $req) = @_; + sub { git_tree_stream($self, $req, @_) }; +} + +sub blob_binary { + my ($fh) = @_; + $fh->write("Binary file cannot be displayed\n"); +} + +sub blob_show { + my ($fh, $git, $hex) = @_; + # ref: buffer_is_binary in git.git + my $to_read = 8000; # git uses this size to detect binary files + my $text_p; + $git->cat_file($hex, sub { + my ($cat, $left) = @_; # $$left == $size + my $n = 0; + $to_read = $$left if $to_read > $$left; + my $r = read($cat, my $buf, $to_read); + return unless defined($r) && $r > 0; + $$left -= $r; + + return blob_binary($fh) if (index($buf, "\0") >= 0); + + $text_p = 1; + $fh->write('
'.PublicInbox::Hval::PRE); + while (1) { + my @buf = split("\n", $buf, -1); + $buf = pop @buf; # last line, careful... + $n += scalar @buf; + foreach my $l (@buf) { + $l = $enc_utf8->decode($l); + $l = PublicInbox::Hval::ascii_html($l); + $l .= "\n"; + $fh->write($l); + } + last if ($$left == 0 || !defined $buf); + + $to_read = $$left if $to_read > $$left; + my $off = length $buf; # last line from previous read + $r = read($cat, $buf, $to_read, $off); + return unless defined($r) && $r > 0; + $$left -= $r; + } + + $fh->write('
');
+		foreach my $i (1..$n) {
+			$fh->write("$i\n");
+		}
+		$fh->write('
'); + 0; + }); +} + +sub tree_show { + my ($fh, $git, $dir, $hex, $q, $extra, $tslash) = @_; + + my $ls = git($dir, qw(ls-tree --abbrev=16 -l -z), $hex); + local $/ = "\0"; + my $pfx = $tslash ? './' : + (@$extra ? uri_escape_utf8($extra->[-1]).'/' : 'tree/'); + + my $qs = $q->qs; + while (defined(my $l = <$ls>)) { + chomp $l; + my ($m, $t, $x, $s, $path) = + ($l =~ /\A(\S+) (\S+) (\S+)( *\S+)\t(.+)\z/s); + $m = $GIT_MODE{$m} or next; + + my $ref = uri_escape_utf8($path); + $path = PublicInbox::Hval::ascii_html($path); + + if ($m eq 'g') { + # TODO: support cross-repository gitlinks + $fh->write('g' . (' ' x 18) . "$path @ $x\n"); + next; + } + elsif ($m eq 'd') { $path = "$path/" } + elsif ($m eq 'x') { $path = "$path" } + elsif ($m eq 'l') { $path = "$path" } + + $ref = $pfx.PublicInbox::Hval::ascii_html($ref).$qs; + $fh->write("$m log raw $s $path\n"); + } + $fh->write(''); +} + +1; diff --git a/lib/PublicInbox/Spawn.pm b/lib/PublicInbox/Spawn.pm new file mode 100644 index 0000000..6407a90 --- /dev/null +++ b/lib/PublicInbox/Spawn.pm @@ -0,0 +1,19 @@ +# Copyright (C) 2015 all contributors +# License: AGPL-3.0+ +package PublicInbox::Spawn; +use strict; +use warnings; +use base qw(Exporter); +our @EXPORT_OK = qw(git); + +sub git { + my ($git_dir, @cmd) = @_; + @cmd = ('git', "--git-dir=$git_dir", @cmd); + + open(my $fh, '-|', @cmd) or + die('open `'.join(' ', @cmd) . "' fork/pipe failed: $!\n"); + + $fh; +} + +1; -- EW