dumping ground for random patches and texts
 help / color / mirror / Atom feed
From: Eric Wong <e@80x24.org>
To: spew@80x24.org
Subject: [PATCH] wip
Date: Tue, 22 Dec 2015 00:15:33 +0000	[thread overview]
Message-ID: <20151222001533.6199-1-e@80x24.org> (raw)

---
 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 { "<a\nhref=\"${rel}blob?id=$_\"" } @ids;
+	map { "<a\nhref=\"${rel}tree?id=$_\"" } @ids;
 }
 
 sub git_blob_links {
diff --git a/lib/PublicInbox/RepoBrowseTree.pm b/lib/PublicInbox/RepoBrowseTree.pm
new file mode 100644
index 0000000..8427a5d
--- /dev/null
+++ b/lib/PublicInbox/RepoBrowseTree.pm
@@ -0,0 +1,146 @@
+# Copyright (C) 2015 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+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('<html><head><title></title></head><body>'.
+			 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('</body></html>');
+	$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('<table><tr><td>'.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('</pre></td><td><pre>');
+		foreach my $i (1..$n) {
+			$fh->write("<a id=n$i href='#n$i'>$i</a>\n");
+		}
+		$fh->write('</pre></td></tr></table>');
+		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 = "<b>$path/</b>" }
+		elsif ($m eq 'x') { $path = "<b>$path</b>" }
+		elsif ($m eq 'l') { $path = "<i>$path</i>" }
+
+		$ref = $pfx.PublicInbox::Hval::ascii_html($ref).$qs;
+		$fh->write("$m log raw $s <a\nhref=\"$ref\">$path</a>\n");
+	}
+	$fh->write('</pre>');
+}
+
+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 <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+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


             reply	other threads:[~2015-12-22  0:15 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-12-22  0:15 Eric Wong [this message]
  -- strict thread matches above, loose matches on Subject: below --
2016-06-26  3:46 [PATCH] wip Eric Wong
2016-08-18  2:16 Eric Wong
2016-08-23 20:07 Eric Wong
2017-04-05 18:40 Eric Wong
2017-04-12 20:17 Eric Wong
2017-07-15  1:42 [PATCH] WIP Eric Wong
2018-06-24  8:39 [PATCH] wip Eric Wong
2018-06-24 11:55 Eric Wong
2018-07-06 21:31 Eric Wong
2019-01-02  9:21 Eric Wong
2019-05-11 22:55 [PATCH] WIP Eric Wong
2020-01-13  9:24 Eric Wong
2020-04-20  7:14 [PATCH] wip Eric Wong
2020-04-23  4:27 Eric Wong
2020-11-15  7:35 Eric Wong
2020-12-27 11:36 [PATCH] WIP Eric Wong
2021-01-03 22:57 [PATCH] wip Eric Wong
2021-01-21  4:24 [PATCH] WIP Eric Wong
2021-03-08  7:11 [PATCH] wip Eric Wong
2021-04-05  7:42 Eric Wong
2021-06-05 19:58 Eric Wong
2021-10-27 20:16 Eric Wong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20151222001533.6199-1-e@80x24.org \
    --to=e@80x24.org \
    --cc=spew@80x24.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).